Skip to content
Merged
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
8 changes: 7 additions & 1 deletion library/core/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2006,7 +2006,13 @@ impl<'a> Formatter<'a> {
// SAFETY: Per the precondition.
unsafe { self.write_formatted_parts(&formatted) }
} else {
let post_padding = self.padding(width - len as u16, Alignment::Right)?;
// Padding widths are capped at `u16`, so reaching this branch means
// the formatted output is also shorter than `u16::MAX`.
let len = match u16::try_from(len) {
Ok(len) => len,
Err(_) => unreachable!(),
};
let post_padding = self.padding(width - len, Alignment::Right)?;
// SAFETY: Per the precondition.
unsafe {
self.write_formatted_parts(&formatted)?;
Expand Down
59 changes: 38 additions & 21 deletions library/core/src/num/imp/flt2dec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,18 +244,21 @@ 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.
///
/// For example, `buf = b"123", exp = 3, frac_digits = 4` yields the parts for
/// `1.2300e2`.
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 +271,22 @@ fn digits_to_exp_str<'a>(
parts[n] = MaybeUninit::new(Part::Copy(&buf[..1]));
n += 1;

if buf.len() > 1 || min_ndigits > 1 {
// The first generated digit becomes the integral digit, anything after that is already part of
// the fractional portion we can emit verbatim.
let actual_frac_digits = buf.len() - 1;
if actual_frac_digits > 0 || frac_digits > 0 {
// Emit a decimal point either when we already have fractional digits or when the requested
// precision needs trailing zeroes after the radix point.
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 {
// format_exact exhausted the meaningful digits, so extend the fractional part with
// zeroes up to the requested precision.
parts[n] = MaybeUninit::new(Part::Zero(frac_digits - actual_frac_digits));
n += 1;
}
}
Expand Down Expand Up @@ -492,7 +505,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 +515,18 @@ 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 returned format is `[sign][digit][.fraction?][zero padding?][e|E][exponent]`.
///
/// 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 +536,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 +551,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 +572,14 @@ where
}
FullDecoded::Finite(ref decoded) => {
let maxlen = estimate_max_buf_len(decoded.exp);
assert!(buf.len() >= ndigits || buf.len() >= maxlen);

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) }
// Scratch space is only needed for the significant digits that `format_exact` can
// actually generate. Any remaining requested fractional precision becomes a trailing
// `Part::Zero`.
let sig_digits = if frac_digits < maxlen { frac_digits + 1 } else { maxlen };
assert!(buf.len() >= sig_digits);

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
9 changes: 7 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,14 @@ 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.
///
/// This matters on 16-bit targets, where exponential formatting can exceed
/// `usize::MAX` by emitting `u16::MAX` trailing zeroes plus `"1."` / `"e0"`.
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()))
}

/// 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