Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions library/core/src/fmt/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,7 @@ where
};

if let Some(precision) = fmt.options.get_precision() {
// 1 integral digit + `precision` fractional digits = `precision + 1` total digits
float_to_exponential_common_exact(fmt, num, sign, precision + 1, upper)
float_to_exponential_common_exact(fmt, num, sign, precision, upper)
} else {
float_to_exponential_common_shortest(fmt, num, sign, upper)
}
Expand Down
43 changes: 23 additions & 20 deletions library/core/src/num/imp/flt2dec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,18 +244,18 @@ fn digits_to_dec_str<'a>(
}

/// Formats the given decimal digits `0.<...buf...> * 10^exp` into the exponential
/// form with at least the given number of significant digits. When `upper` is `true`,
/// form with at least the given number of fractional digits. When `upper` is `true`,
/// the exponent will be prefixed by `E`; otherwise that's `e`. The result is
/// stored to the supplied parts array and a slice of written parts is returned.
///
/// `min_digits` can be less than the number of actual significant digits in `buf`;
/// `frac_digits` can be less than the number of actual fractional digits in `buf`;
/// it will be ignored and full digits will be printed. It is only used to print
/// additional zeroes after rendered digits. Thus, `min_digits == 0` means that
/// additional zeroes after rendered digits. Thus, `frac_digits == 0` means that
/// it will only print the given digits and nothing else.
fn digits_to_exp_str<'a>(
buf: &'a [u8],
exp: i16,
min_ndigits: usize,
frac_digits: usize,
upper: bool,
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> &'a [Part<'a>] {
Expand All @@ -268,12 +268,16 @@ fn digits_to_exp_str<'a>(
parts[n] = MaybeUninit::new(Part::Copy(&buf[..1]));
n += 1;

if buf.len() > 1 || min_ndigits > 1 {
let actual_frac_digits = buf.len() - 1;
if actual_frac_digits > 0 || frac_digits > 0 {
parts[n] = MaybeUninit::new(Part::Copy(b"."));
parts[n + 1] = MaybeUninit::new(Part::Copy(&buf[1..]));
n += 2;
if min_ndigits > buf.len() {
parts[n] = MaybeUninit::new(Part::Zero(min_ndigits - buf.len()));
n += 1;
if actual_frac_digits > 0 {
parts[n] = MaybeUninit::new(Part::Copy(&buf[1..]));
n += 1;
}
if frac_digits > actual_frac_digits {
parts[n] = MaybeUninit::new(Part::Zero(frac_digits - actual_frac_digits));
n += 1;
}
}
Expand Down Expand Up @@ -492,7 +496,7 @@ fn estimate_max_buf_len(exp: i16) -> usize {
}

/// Formats given floating point number into the exponential form with
/// exactly given number of significant digits. The result is stored to
/// exactly given number of fractional digits. The result is stored to
/// the supplied parts array while utilizing given byte buffer as a scratch.
/// `upper` is used to determine the case of the exponent prefix (`e` or `E`).
/// The first part to be rendered is always a `Part::Sign` (which can be
Expand All @@ -502,16 +506,16 @@ fn estimate_max_buf_len(exp: i16) -> usize {
/// It should return the part of the buffer that it initialized.
/// You probably would want `strategy::grisu::format_exact` for this.
///
/// The byte buffer should be at least `ndigits` bytes long unless `ndigits` is
/// so large that only the fixed number of digits will be ever written.
/// The byte buffer should be at least `frac_digits + 1` bytes long unless
/// `frac_digits` is so large that only the fixed number of digits will be ever written.
/// (The tipping point for `f64` is about 800, so 1000 bytes should be enough.)
/// There should be at least 6 parts available, due to the worst case like
/// `[+][1][.][2345][e][-][6]`.
pub fn to_exact_exp_str<'a, T, F>(
mut format_exact: F,
v: T,
sign: Sign,
ndigits: usize,
frac_digits: usize,
upper: bool,
buf: &'a mut [MaybeUninit<u8>],
parts: &'a mut [MaybeUninit<Part<'a>>],
Expand All @@ -521,7 +525,6 @@ where
F: FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
assert!(parts.len() >= 6);
assert!(ndigits > 0);

let (negative, full_decoded) = decode(v);
let sign = determine_sign(sign, &full_decoded, negative);
Expand All @@ -537,10 +540,10 @@ where
Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } }
}
FullDecoded::Zero => {
if ndigits > 1 {
if frac_digits > 0 {
// [0.][0000][e0]
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
parts[1] = MaybeUninit::new(Part::Zero(ndigits - 1));
parts[1] = MaybeUninit::new(Part::Zero(frac_digits));
parts[2] = MaybeUninit::new(Part::Copy(if upper { b"E0" } else { b"e0" }));
Formatted {
sign,
Expand All @@ -558,11 +561,11 @@ where
}
FullDecoded::Finite(ref decoded) => {
let maxlen = estimate_max_buf_len(decoded.exp);
assert!(buf.len() >= ndigits || buf.len() >= maxlen);
let sig_digits = if frac_digits < maxlen { frac_digits + 1 } else { maxlen };
assert!(buf.len() >= sig_digits);

let trunc = if ndigits < maxlen { ndigits } else { maxlen };
let (buf, exp) = format_exact(decoded, &mut buf[..trunc], i16::MIN);
Formatted { sign, parts: digits_to_exp_str(buf, exp, ndigits, upper, parts) }
let (buf, exp) = format_exact(decoded, &mut buf[..sig_digits], i16::MIN);
Formatted { sign, parts: digits_to_exp_str(buf, exp, frac_digits, upper, parts) }
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions library/core/src/num/imp/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ pub struct Formatted<'a> {
}

impl<'a> Formatted<'a> {
/// Returns the exact byte length of combined formatted result.
/// Returns the byte length of combined formatted result.
///
/// Saturates at `usize::MAX` if the actual length is larger.
pub fn len(&self) -> usize {
self.sign.len() + self.parts.iter().map(|part| part.len()).sum::<usize>()
self.parts.iter().fold(self.sign.len(), |len, part| len.saturating_add(part.len()))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to close the nearby overflow hazard in the same code path, since we have to touch this anyway. Let me know if you want it split out.

}

/// Writes all formatted parts into the supplied buffer.
Expand Down
68 changes: 68 additions & 0 deletions library/coretests/tests/fmt/float.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::fmt::{self, Write};

#[test]
fn test_format_f64() {
assert_eq!("1", format!("{:.0}", 1.0f64));
Expand Down Expand Up @@ -170,6 +172,72 @@ fn test_format_f32_rounds_ties_to_even() {
assert_eq!("-1.28E2", format!("{:.2E}", -128.5f32));
}

#[test]
fn test_format_f64_max_precision_exponential() {
struct ExactExpWriter {
prefix: &'static [u8],
zeroes_remaining: u32,
suffix: &'static [u8],
prefix_pos: usize,
suffix_pos: usize,
total_len: u32,
}

impl ExactExpWriter {
fn new(prefix: &'static str, suffix: &'static str) -> Self {
Self {
prefix: prefix.as_bytes(),
zeroes_remaining: u16::MAX.into(),
suffix: suffix.as_bytes(),
prefix_pos: 0,
suffix_pos: 0,
total_len: 0,
}
}

fn finish(self) {
assert_eq!(self.prefix_pos, self.prefix.len());
assert_eq!(self.zeroes_remaining, 0);
assert_eq!(self.suffix_pos, self.suffix.len());
assert_eq!(self.total_len, u32::from(u16::MAX) + 4);
}
}

impl Write for ExactExpWriter {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.total_len += 1;

if self.prefix_pos < self.prefix.len() {
assert_eq!(byte, self.prefix[self.prefix_pos]);
self.prefix_pos += 1;
} else if self.zeroes_remaining > 0 {
assert_eq!(byte, b'0');
self.zeroes_remaining -= 1;
} else {
assert!(self.suffix_pos < self.suffix.len());
assert_eq!(byte, self.suffix[self.suffix_pos]);
self.suffix_pos += 1;
}
}

Ok(())
}
}

fn assert_exact_exp(args: fmt::Arguments<'_>, prefix: &'static str, suffix: &'static str) {
let mut writer = ExactExpWriter::new(prefix, suffix);
fmt::write(&mut writer, args).unwrap();
writer.finish();
}

assert_exact_exp(format_args!("{:.65535e}", 0.0f64), "0.", "e0");
assert_exact_exp(format_args!("{:.65535e}", 1.0f64), "1.", "e0");
assert_exact_exp(format_args!("{:.65535E}", 0.0f64), "0.", "E0");
assert_exact_exp(format_args!("{:.65535E}", 1.0f64), "1.", "E0");
assert_exact_exp(format_args!("{:65535.65535e}", 1.0f64), "1.", "e0");
}

fn is_exponential(s: &str) -> bool {
s.contains("e") || s.contains("E")
}
10 changes: 9 additions & 1 deletion library/coretests/tests/num/flt2dec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,15 @@ where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
to_string_with_parts(|buf, parts| {
to_exact_exp_str(|d, b, l| f(d, b, l), v, sign, ndigits, upper, buf, parts)
to_exact_exp_str(
|d, b, l| f(d, b, l),
v,
sign,
ndigits.saturating_sub(1),
upper,
buf,
parts,
)
})
}

Expand Down
Loading