From 40d9dcb7751ffa5527e0c90de546445d37951105 Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Fri, 5 Sep 2025 13:34:20 +0200 Subject: [PATCH 1/2] formatted padding with clojure --- library/core/src/fmt/mod.rs | 184 ++++++++++++++++++------------------ library/core/src/fmt/num.rs | 19 ++-- 2 files changed, 104 insertions(+), 99 deletions(-) diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index e20109c3cc9a8..3f4e9b5f325c0 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -1777,19 +1777,12 @@ impl<'a> Formatter<'a> { // Helper methods used for padding and processing formatting arguments that // all formatting traits can use. - /// Performs the correct padding for an integer which has already been - /// emitted into a str. The str should *not* contain the sign for the - /// integer, that will be added by this method. - /// - /// # Arguments - /// - /// * is_nonnegative - whether the original integer was either positive or zero. - /// * prefix - if the '#' character (Alternate) is provided, this - /// is the prefix to put in front of the number. - /// * buf - the byte array that the number has been formatted into - /// - /// This function will correctly account for the flags provided as well as - /// the minimum width. It will not take precision into account. + /// Write a formatted integer to the Formatter. The `digits` string should + /// hold the representation of the absolute value, without sign. Whether a + /// sign ("+" or "-") is written depends both on format paramaters and on + /// `is_nonnegative`. The `prefix` (e.g., "0x") is written when alternate. + /// Padding may be included when width is Some. The precision is ignored + /// for integers. /// /// # Examples /// @@ -1822,8 +1815,8 @@ impl<'a> Formatter<'a> { /// assert_eq!(format!("{:0>#8}", Foo::new(-1)), "00-Foo 1"); /// ``` #[stable(feature = "rust1", since = "1.0.0")] - pub fn pad_integral(&mut self, is_nonnegative: bool, prefix: &str, buf: &str) -> Result { - let mut width = buf.len(); + pub fn pad_integral(&mut self, is_nonnegative: bool, prefix: &str, digits: &str) -> Result { + let mut width = digits.len(); let mut sign = None; if !is_nonnegative { @@ -1855,7 +1848,7 @@ impl<'a> Formatter<'a> { if width >= usize::from(min) { // We're over the minimum width, so then we can just write the bytes. write_prefix(self, sign, prefix)?; - self.buf.write_str(buf) + self.buf.write_str(digits) } else if self.sign_aware_zero_pad() { // The sign and prefix goes before the padding if the fill character // is zero @@ -1863,7 +1856,7 @@ impl<'a> Formatter<'a> { self.options.fill('0').align(Some(Alignment::Right)); write_prefix(self, sign, prefix)?; let post_padding = self.padding(min - width as u16, Alignment::Right)?; - self.buf.write_str(buf)?; + self.buf.write_str(digits)?; post_padding.write(self)?; self.options = old_options; Ok(()) @@ -1871,11 +1864,54 @@ impl<'a> Formatter<'a> { // Otherwise, the sign and prefix goes after the padding let post_padding = self.padding(min - width as u16, Alignment::Right)?; write_prefix(self, sign, prefix)?; - self.buf.write_str(buf)?; + self.buf.write_str(digits)?; post_padding.write(self) } } + /// Write a formatted number to the Formatter with a closure. The Fn should + /// write the representation of the absolute value, excluding `sign` (e.g., + /// "+" or "-"). The implementation should follow [precision] when present. + /// The output of `write_abs` must match exactly the `write_len` amount of + /// `char`s in size. Padding may be written when [width] applies. + fn pad_number(&mut self, sign: &str, write_len: usize, write_abs: F) -> Result + where + F: Fn(&mut Self) -> Result, + { + let out_len = write_len + sign.len(); + // Pad when the width is higher than the output length. + let pad = self.width().unwrap_or(0).saturating_sub(out_len); + + if pad == 0 || self.sign_aware_zero_pad() { + self.write_str(sign)?; + self.write_zeroes(pad)?; + return write_abs(self); + } + + // Numbers align to the right by default. + let align = self.align().unwrap_or(Alignment::Right); + let (pad_before, pad_after) = match align { + Alignment::Left => (0, pad), + Alignment::Right => (pad, 0), + Alignment::Center => { + let split = pad / 2; // may round down + (split, pad - split) + } + }; + + // Write output with padding. + let fill = self.fill(); + for _ in 0..pad_before { + self.write_char(fill)?; + } + self.write_str(sign)?; + write_abs(self)?; + for _ in 0..pad_after { + self.write_char(fill)?; + } + Ok(()) + } + /// Takes a string slice and emits it to the internal buffer after applying /// the relevant formatting flags specified. /// @@ -1978,50 +2014,6 @@ impl<'a> Formatter<'a> { /// /// Any `numfmt::Part::Copy` parts in `formatted` must contain valid UTF-8. unsafe fn pad_formatted_parts(&mut self, formatted: &numfmt::Formatted<'_>) -> Result { - if self.options.width == 0 { - // this is the common case and we take a shortcut - // SAFETY: Per the precondition. - unsafe { self.write_formatted_parts(formatted) } - } else { - // for the sign-aware zero padding, we render the sign first and - // behave as if we had no sign from the beginning. - let mut formatted = formatted.clone(); - let mut width = self.options.width; - let old_options = self.options; - if self.sign_aware_zero_pad() { - // a sign always goes first - let sign = formatted.sign; - self.buf.write_str(sign)?; - - // remove the sign from the formatted parts - formatted.sign = ""; - width = width.saturating_sub(sign.len() as u16); - self.options.fill('0').align(Some(Alignment::Right)); - } - - // remaining parts go through the ordinary padding process. - let len = formatted.len(); - let ret = if usize::from(width) <= len { - // no padding - // SAFETY: Per the precondition. - unsafe { self.write_formatted_parts(&formatted) } - } else { - let post_padding = self.padding(width - len as u16, Alignment::Right)?; - // SAFETY: Per the precondition. - unsafe { - self.write_formatted_parts(&formatted)?; - } - post_padding.write(self) - }; - self.options = old_options; - ret - } - } - - /// # Safety - /// - /// Any `numfmt::Part::Copy` parts in `formatted` must contain valid UTF-8. - unsafe fn write_formatted_parts(&mut self, formatted: &numfmt::Formatted<'_>) -> Result { unsafe fn write_bytes(buf: &mut dyn Write, s: &[u8]) -> Result { // SAFETY: This is used for `numfmt::Part::Num` and `numfmt::Part::Copy`. // It's safe to use for `numfmt::Part::Num` since every char `c` is between @@ -2030,39 +2022,51 @@ impl<'a> Formatter<'a> { buf.write_str(unsafe { str::from_utf8_unchecked(s) }) } - if !formatted.sign.is_empty() { - self.buf.write_str(formatted.sign)?; - } - for part in formatted.parts { - match *part { - numfmt::Part::Zero(mut nzeroes) => { - const ZEROES: &str = // 64 zeroes - "0000000000000000000000000000000000000000000000000000000000000000"; - while nzeroes > ZEROES.len() { - self.buf.write_str(ZEROES)?; - nzeroes -= ZEROES.len(); + let sign = formatted.sign; + let out_len = formatted.len() - sign.len(); + self.pad_number(sign, out_len, |f: &mut Self| { + for part in formatted.parts { + match *part { + numfmt::Part::Zero(nzeroes) => { + f.write_zeroes(nzeroes)?; } - if nzeroes > 0 { - self.buf.write_str(&ZEROES[..nzeroes])?; - } - } - numfmt::Part::Num(mut v) => { - let mut s = [0; 5]; - let len = part.len(); - for c in s[..len].iter_mut().rev() { - *c = b'0' + (v % 10) as u8; - v /= 10; + numfmt::Part::Num(mut v) => { + let mut s = [0; 5]; + let len = part.len(); + for c in s[..len].iter_mut().rev() { + *c = b'0' + (v % 10) as u8; + v /= 10; + } + // SAFETY: Per the precondition. + unsafe { + write_bytes(f.buf, &s[..len])?; + } } // SAFETY: Per the precondition. - unsafe { - write_bytes(self.buf, &s[..len])?; - } + numfmt::Part::Copy(buf) => unsafe { + write_bytes(f.buf, buf)?; + }, } - // SAFETY: Per the precondition. - numfmt::Part::Copy(buf) => unsafe { - write_bytes(self.buf, buf)?; - }, } + Ok(()) + }) + } + + /// Write n ASCII digits (U+0030) to the Formatter. + pub(crate) fn write_zeroes(&mut self, n: usize) -> Result { + #[cfg(feature = "optimize_for_size")] + { + for _ in 0..n { + self.write_char('0')?; + } + } + #[cfg(not(feature = "optimize_for_size"))] + { + const ZEROES: &str = "0000000000000000000000000000000000000000000000000000000000000000"; + for _ in 0..(n / 64) { + self.write_str(ZEROES)?; + } + self.write_str(&ZEROES[..(n % 64)])?; } Ok(()) } diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index 253a7b7587e49..06a5144b4e5f4 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -2,7 +2,6 @@ use crate::fmt::NumBuffer; use crate::mem::MaybeUninit; -use crate::num::fmt as numfmt; use crate::{fmt, str}; /// Formatting of integers with a non-decimal radix. @@ -531,11 +530,6 @@ macro_rules! impl_Exp { let as_str = unsafe { str::from_utf8_unchecked(text) }; f.pad_integral(is_nonnegative, "", as_str) } else { - let parts = &[ - numfmt::Part::Copy(&text[..coef_len]), - numfmt::Part::Zero(more_prec), - numfmt::Part::Copy(&text[coef_len..]), - ]; let sign = if !is_nonnegative { "-" } else if f.sign_plus() { @@ -543,9 +537,16 @@ macro_rules! impl_Exp { } else { "" }; - // SAFETY: Text is set with ASCII exclusively: either a decimal, - // or a LETTER_E, or a dot. ASCII implies valid UTF-8. - unsafe { f.pad_formatted_parts(&numfmt::Formatted { sign, parts }) } + f.pad_number(sign, text.len() + more_prec, |w| { + // SAFETY: Text is set with ASCII exclusively. + let (coef_str, scale_str) = unsafe{( + str::from_utf8_unchecked(&text[..coef_len]), + str::from_utf8_unchecked(&text[coef_len..]), + )}; + w.write_str(coef_str)?; + w.write_zeroes(more_prec)?; + w.write_str(scale_str) + }) } } From 1198db186824a0ecfa3c66ae7f04cefc53ca04ec Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Sat, 27 Sep 2025 18:12:40 +0200 Subject: [PATCH 2/2] fmt of floating points in plain code, defragemented --- library/core/src/fmt/float.rs | 523 +++-- library/core/src/fmt/mod.rs | 50 +- library/core/src/num/flt2dec/decoder.rs | 154 +- library/core/src/num/flt2dec/estimator.rs | 23 +- library/core/src/num/flt2dec/mod.rs | 647 +------ .../core/src/num/flt2dec/strategy/dragon.rs | 57 +- .../core/src/num/flt2dec/strategy/grisu.rs | 197 +- library/core/src/num/fmt.rs | 89 - library/core/src/num/mod.rs | 1 - library/coretests/benches/num/flt2dec/mod.rs | 8 - .../benches/num/flt2dec/strategy/dragon.rs | 56 +- .../benches/num/flt2dec/strategy/grisu.rs | 81 +- library/coretests/tests/lib.rs | 2 +- .../coretests/tests/num/flt2dec/estimator.rs | 74 +- library/coretests/tests/num/flt2dec/mod.rs | 1721 +++++++---------- library/coretests/tests/num/flt2dec/random.rs | 316 +-- .../tests/num/flt2dec/strategy/dragon.rs | 56 - .../tests/num/flt2dec/strategy/grisu.rs | 44 - ...onential_common.GVN.32bit.panic-abort.diff | 175 -- ...nential_common.GVN.32bit.panic-unwind.diff | 175 -- ...onential_common.GVN.64bit.panic-abort.diff | 175 -- ...nential_common.GVN.64bit.panic-unwind.diff | 175 -- tests/mir-opt/funky_arms.rs | 58 - 23 files changed, 1590 insertions(+), 3267 deletions(-) delete mode 100644 library/core/src/num/fmt.rs delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff delete mode 100644 tests/mir-opt/funky_arms.rs diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs index 556db239f2499..c406f260d6b70 100644 --- a/library/core/src/fmt/float.rs +++ b/library/core/src/fmt/float.rs @@ -1,232 +1,381 @@ use crate::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp}; use crate::mem::MaybeUninit; -use crate::num::{flt2dec, fmt as numfmt}; +use crate::num::FpCategory; +use crate::num::flt2dec::SHORT_DIGITS_MAX; +use crate::num::flt2dec::decoder::{Decoded64, decode_f16, decode_f32, decode_f64}; +use crate::num::flt2dec::strategy::{dragon, grisu}; -#[doc(hidden)] -trait GeneralFormat: PartialOrd { - /// Determines if a value should use exponential based on its magnitude, given the precondition - /// that it will not be rounded any further before it is displayed. - fn already_rounded_value_should_use_exponential(&self) -> bool; +/// The maximum return of enough_buf_for_fixed limits buffer allocation. +const FIXED_DIGITS_MAX: usize = 827; + +/// Both `format_fixed` functions will fill the entire buffer. The return is an +/// upper bound to limit the formatting of trailing zeroes. F64 has a worst case +/// of FIXED_DIGITS_MAX (when exp is -1074). +fn enough_digits_for_fixed(dec: &Decoded64) -> usize { + let e = dec.exp as isize; // base 2 + + // The exact digit count is either: + // (a) ⌈log₁₀(5⁻ᵉ × (2⁶⁴ − 1))⌉ when e < 0, or + // (b) ⌈log₁₀(2ᵉ × (2⁶⁴ − 1))⌉ when e ≥ 0. + // + // An upper bound is acquired by using the following approximations: + // ⌈log₁₀(2⁶⁴ − 1)⌉ ≤ 20 + // ⌈e × log₁₀(5)⌉ ≤ 1 + e × log₁₀(5) + // ⌈e × log₁₀(2)⌉ ≤ 1 + e × log₁₀(2) + // log₁₀(5) < 12/16 + // log₁₀(2) < 5/16 + let multiplier: isize = if e < 0 { + -12 // negates e + } else { + 5 + }; + // Division by 16 is done as a bit shift, explicitly. + 20 + 1 + ((multiplier * e) as usize >> 4) } -macro_rules! impl_general_format { - ($($t:ident)*) => { - $(impl GeneralFormat for $t { - fn already_rounded_value_should_use_exponential(&self) -> bool { - let abs = $t::abs(*self); - (abs != 0.0 && abs < 1e-4) || abs >= 1e+16 +/// Format to f in decimal notation given a value as "0." * 10^pow10. +/// The fractional part gets extended with trailing zeroes when the result has +/// less than min_nfrac decimal places. +fn fmt_digits( + f: &mut Formatter<'_>, + sign: &'static str, + digits: &str, + pow10: isize, + min_nfrac: usize, +) -> Result { + if pow10 <= 0 { + // All of the digits are in the fractional part. + const LEAD: &str = "0."; + let lead_zeroes = (-pow10) as usize; + let more_zeroes = min_nfrac.saturating_sub(lead_zeroes + digits.len()); + let out_len = LEAD.len() + lead_zeroes + digits.len() + more_zeroes; + f.pad_number(sign, out_len, |w| { + w.write_str(LEAD)?; + w.write_zeroes(lead_zeroes)?; + w.write_str(digits)?; + w.write_zeroes(more_zeroes) + }) + } else if (pow10 as usize) < digits.len() { + // Split the digits into an integer and a fractional part. + let (int, frac) = digits.as_bytes().split_at(pow10 as usize); + let more_zeroes = min_nfrac.saturating_sub(frac.len()); + let out_len = int.len() + ".".len() + frac.len() + more_zeroes; + f.pad_number(sign, out_len, |w| { + let (int_str, frac_str) = + // SAFETY: Digits contains single-byte characters, exclusively. + unsafe { (str::from_utf8_unchecked(int), str::from_utf8_unchecked(frac)) }; + w.write_str(int_str)?; + w.write_str(".")?; + w.write_str(frac_str)?; + w.write_zeroes(more_zeroes) + }) + } else { + // None of the digits are in the fractional part. + let more_zeroes = pow10 as usize - digits.len(); + let frac_len = if min_nfrac == 0 { 0 } else { ".".len() + min_nfrac }; + let out_len = digits.len() + more_zeroes + frac_len; + f.pad_number(sign, out_len, |w| { + w.write_str(digits)?; + w.write_zeroes(more_zeroes)?; + if min_nfrac != 0 { + w.write_str(".")?; + w.write_zeroes(min_nfrac)?; } - })* + Ok(()) + }) } } -#[cfg(target_has_reliable_f16)] -impl_general_format! { f16 } -impl_general_format! { f32 f64 } - -// Don't inline this so callers don't use the stack space this function -// requires unless they have to. -#[inline(never)] -fn float_to_decimal_common_exact( - fmt: &mut Formatter<'_>, - num: &T, - sign: flt2dec::Sign, - precision: u16, -) -> Result -where - T: flt2dec::DecodableFloat, -{ - let mut buf: [MaybeUninit; 1024] = [MaybeUninit::uninit(); 1024]; // enough for f32 and f64 - let mut parts: [MaybeUninit>; 4] = [MaybeUninit::uninit(); 4]; - let formatted = flt2dec::to_exact_fixed_str( - flt2dec::strategy::grisu::format_exact, - *num, - sign, - precision.into(), - &mut buf, - &mut parts, - ); - // SAFETY: `to_exact_fixed_str` and `format_exact` produce only ASCII characters. - unsafe { fmt.pad_formatted_parts(&formatted) } -} +/// Format in decimal notation to f with at least min_nfrac decimal places, yet +/// no more than needed for an exact representation of the decoded value. +/// Decoded64 must contain a finite, non-zero floating-point. +fn fmt_short( + f: &mut Formatter<'_>, + sign: &'static str, + dec: &Decoded64, + min_nfrac: usize, +) -> Result { + let mut buf = [MaybeUninit::::uninit(); SHORT_DIGITS_MAX]; + let (digits, pow10) = if let Some(res) = grisu::format_short(dec, &mut buf) { + res + } else { + dragon::format_short(dec, &mut buf) + }; + // The decoded number is presented as "0." * 10^pow10 -// Don't inline this so callers that call both this and the above won't wind -// up using the combined stack space of both functions in some cases. -#[inline(never)] -fn float_to_decimal_common_shortest( - fmt: &mut Formatter<'_>, - num: &T, - sign: flt2dec::Sign, - precision: u16, -) -> Result -where - T: flt2dec::DecodableFloat, -{ - // enough for f32 and f64 - let mut buf: [MaybeUninit; flt2dec::MAX_SIG_DIGITS] = - [MaybeUninit::uninit(); flt2dec::MAX_SIG_DIGITS]; - let mut parts: [MaybeUninit>; 4] = [MaybeUninit::uninit(); 4]; - let formatted = flt2dec::to_shortest_str( - flt2dec::strategy::grisu::format_shortest, - *num, - sign, - precision.into(), - &mut buf, - &mut parts, - ); - // SAFETY: `to_shortest_str` and `format_shortest` produce only ASCII characters. - unsafe { fmt.pad_formatted_parts(&formatted) } + fmt_digits(f, sign, digits, pow10, min_nfrac) } -fn float_to_decimal_display(fmt: &mut Formatter<'_>, num: &T) -> Result -where - T: flt2dec::DecodableFloat, -{ - let force_sign = fmt.sign_plus(); - let sign = match force_sign { - false => flt2dec::Sign::Minus, - true => flt2dec::Sign::MinusPlus, +/// Format in fixed-point notation to f with nfrac decimal places. +/// Decoded64 must contain a finite, non-zero floating-point. +#[inline(never)] // Only allocate the rather large stack-buffer when needed. +fn fmt_fixed(f: &mut Formatter<'_>, sign: &'static str, dec: &Decoded64, nfrac: usize) -> Result { + // BUG: An excessive number of decimal places gets replaced by trailing + // zeroes without warning. + let mut buf = [MaybeUninit::::uninit(); FIXED_DIGITS_MAX]; + let buf_enough = &mut buf[..enough_digits_for_fixed(dec)]; + let limit = -(nfrac as isize); // buf_enough is the hard upper-bound + + let (digits, pow10) = if let Some(res) = grisu::format_fixed(dec, buf_enough, limit) { + res + } else { + dragon::format_fixed(dec, &mut buf, limit) }; + // The decoded number is presented as "0." * 10^pow10 - if let Some(precision) = fmt.options.get_precision() { - float_to_decimal_common_exact(fmt, num, sign, precision) + if !digits.is_empty() { + fmt_digits(f, sign, digits, pow10, nfrac) } else { - let min_precision = 0; - float_to_decimal_common_shortest(fmt, num, sign, min_precision) + // The number rounds down to zero at nfrac precision. + if nfrac == 0 { + f.pad_number(sign, 1, |w| w.write_str("0")) + } else { + const LEAD: &str = "0."; + let out_len = LEAD.len() + nfrac; + f.pad_number(sign, out_len, |w| { + w.write_str(LEAD)?; + w.write_zeroes(nfrac) + }) + } } } -// Don't inline this so callers don't use the stack space this function -// requires unless they have to. -#[inline(never)] -fn float_to_exponential_common_exact( - fmt: &mut Formatter<'_>, - num: &T, - sign: flt2dec::Sign, - precision: u16, - upper: bool, -) -> Result -where - T: flt2dec::DecodableFloat, -{ - let mut buf: [MaybeUninit; 1024] = [MaybeUninit::uninit(); 1024]; // enough for f32 and f64 - let mut parts: [MaybeUninit>; 6] = [MaybeUninit::uninit(); 6]; - let formatted = flt2dec::to_exact_exp_str( - flt2dec::strategy::grisu::format_exact, - *num, - sign, - precision.into(), - upper, - &mut buf, - &mut parts, - ); - // SAFETY: `to_exact_exp_str` and `format_exact` produce only ASCII characters. - unsafe { fmt.pad_formatted_parts(&formatted) } -} +/// Format in E notation to f with the least amount of decimal places needed for +/// an exact representation of the decoded the value. +/// Decoded64 must contain a finite, non-zero floating-point. +fn fmt_enote_short( + f: &mut Formatter<'_>, + sign: &'static str, + dec: &Decoded64, + letter_e: u8, +) -> Result { + let mut buf = [MaybeUninit::::uninit(); SHORT_DIGITS_MAX]; + let (digits, pow10) = if let Some(res) = grisu::format_short(dec, &mut buf) { + res + } else { + dragon::format_short(dec, &mut buf) + }; + // The decoded number is presented as "0." * 10^pow10 + + // E-notation is formatted as [.]. + let (int, frac, sep_len) = if digits.len() > 1 { + let (first, remain) = digits.as_bytes().split_at(1); + // SAFETY: Digits contains single-byte characters, exclusively. + unsafe { (str::from_utf8_unchecked(first), str::from_utf8_unchecked(remain), ".".len()) } + } else { + (digits, "", 0) + }; -// Don't inline this so callers that call both this and the above won't wind -// up using the combined stack space of both functions in some cases. -#[inline(never)] -fn float_to_exponential_common_shortest( - fmt: &mut Formatter<'_>, - num: &T, - sign: flt2dec::Sign, - upper: bool, -) -> Result -where - T: flt2dec::DecodableFloat, -{ - // enough for f32 and f64 - let mut buf: [MaybeUninit; flt2dec::MAX_SIG_DIGITS] = - [MaybeUninit::uninit(); flt2dec::MAX_SIG_DIGITS]; - let mut parts: [MaybeUninit>; 6] = [MaybeUninit::uninit(); 6]; - let formatted = flt2dec::to_shortest_exp_str( - flt2dec::strategy::grisu::format_shortest, - *num, - sign, - (0, 0), - upper, - &mut buf, - &mut parts, - ); - // SAFETY: `to_shortest_exp_str` and `format_shortest` produce only ASCII characters. - unsafe { fmt.pad_formatted_parts(&formatted) } + let mut scale_buf = [MaybeUninit::::uninit(); 5]; + let scale = encode_scale(&mut scale_buf, pow10 - 1, letter_e); + + let out_len = int.len() + sep_len + frac.len() + scale.len(); + f.pad_number(sign, out_len, |w| { + w.write_str(int)?; + if !frac.is_empty() { + w.write_str(".")?; + w.write_str(frac)?; + } + w.write_str(scale) + }) } -// Common code of floating point LowerExp and UpperExp. -fn float_to_exponential_common(fmt: &mut Formatter<'_>, num: &T, upper: bool) -> Result -where - T: flt2dec::DecodableFloat, -{ - let force_sign = fmt.sign_plus(); - let sign = match force_sign { - false => flt2dec::Sign::Minus, - true => flt2dec::Sign::MinusPlus, +/// Format in E notation to f with a fixed amount of decimal places. +/// Decoded64 must contain a finite, non-zero floating-point. +#[inline(never)] // Only allocate the rather large stack-buffer when needed. +fn fmt_enote_fixed( + f: &mut Formatter<'_>, + sign: &'static str, + dec: &Decoded64, + letter_e: u8, + nfrac: usize, +) -> Result { + // BUG: An excessive number of decimal places gets replaced by trailing + // zeroes without warning. + let mut buf = [MaybeUninit::::uninit(); FIXED_DIGITS_MAX]; + let buf_enough = &mut buf[..enough_digits_for_fixed(dec).min(nfrac + 1)]; + let (digits, pow10) = if let Some(res) = grisu::format_fixed(dec, buf_enough, isize::MIN) { + res + } else { + dragon::format_fixed(dec, buf_enough, isize::MIN) }; + // The decoded number is presented as "0." * 10^pow10 - 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) + // E-notation is formatted as [.]. + let (int, frac) = if digits.len() > 1 { + let (first, remain) = digits.as_bytes().split_at(1); + // SAFETY: Digits contains single-byte characters, exclusively. + unsafe { (str::from_utf8_unchecked(first), str::from_utf8_unchecked(remain)) } } else { - float_to_exponential_common_shortest(fmt, num, sign, upper) - } + (digits, "") + }; + + let mut scale_buf = [MaybeUninit::::uninit(); 5]; + let scale = encode_scale(&mut scale_buf, pow10 - 1, letter_e); + + let frac_len = if nfrac == 0 { 0 } else { ".".len() + nfrac }; + let out_len = "1".len() + frac_len + scale.len(); + f.pad_number(sign, out_len, |w| { + w.write_str(int)?; + if nfrac != 0 { + w.write_str(".")?; + w.write_str(frac)?; + w.write_zeroes(nfrac.saturating_sub(frac.len()))?; + } + w.write_str(scale) + }) } -fn float_to_general_debug(fmt: &mut Formatter<'_>, num: &T) -> Result -where - T: flt2dec::DecodableFloat + GeneralFormat, -{ - let force_sign = fmt.sign_plus(); - let sign = match force_sign { - false => flt2dec::Sign::Minus, - true => flt2dec::Sign::MinusPlus, +// Encode the E notation into buf and slice the result as a string. +fn encode_scale<'a>(buf: &'a mut [MaybeUninit; 5], e: isize, letter_e: u8) -> &'a str { + assert!(letter_e == b'E' || letter_e == b'e'); + buf[0].write(letter_e); + let dig_start = if e < 0 { + buf[1].write(b'-'); + 2 + } else { + 1 }; - if let Some(precision) = fmt.options.get_precision() { - // this behavior of {:.PREC?} predates exponential formatting for {:?} - float_to_decimal_common_exact(fmt, num, sign, precision) + assert!(e > -1000 && e < 1000); + let n = e.abs(); + let ndec = if n < 10 { + buf[dig_start].write(b'0' + n as u8); + 1 + } else if n < 100 { + buf[dig_start + 0].write(b'0' + (n / 10) as u8); + buf[dig_start + 1].write(b'0' + (n % 10) as u8); + 2 } else { - // since there is no precision, there will be no rounding - if num.already_rounded_value_should_use_exponential() { - let upper = false; - float_to_exponential_common_shortest(fmt, num, sign, upper) - } else { - let min_precision = 1; - float_to_decimal_common_shortest(fmt, num, sign, min_precision) - } - } + buf[dig_start + 0].write(b'0' + (n / 100) as u8); + let cent = n % 100; + buf[dig_start + 1].write(b'0' + (cent / 10) as u8); + buf[dig_start + 2].write(b'0' + (cent % 10) as u8); + 3 + }; + + // SAFETY: All bytes up until dig_start + ndec have been set with ASCII. + unsafe { str::from_utf8_unchecked(buf[..dig_start + ndec].assume_init_ref()) } } macro_rules! floating { - ($($ty:ident)*) => { + ($($T:ident)*) => { $( - #[stable(feature = "rust1", since = "1.0.0")] - impl Debug for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_general_debug(fmt, self) - } + + fn ${concat(sign_for_, $T)}(f: &Formatter<'_>, v: $T) -> &'static str { + if v.is_sign_negative() { + "-" + } else if f.sign_plus() { + "+" + } else { + "" } + } + + #[stable(feature = "rust1", since = "1.0.0")] + impl Debug for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let sign: &'static str = ${concat(sign_for_, $T)}(f, *self); + match self.classify() { + FpCategory::Nan => f.pad_number("", 3, | w | w.write_str("NaN")), + FpCategory::Infinite => f.pad_number(sign, 3, | w | w.write_str("inf")), + FpCategory::Zero => match f.precision() { + None | Some(1) => f.pad_number(sign, 3, | w | w.write_str("0.0")), + Some(0) => f.pad_number(sign, 1, | w | w.write_str("0")), + Some(n) => f.pad_number(sign, "0.".len() + n, | w | { + w.write_str("0.")?; + w.write_zeroes(n) + }), + }, + FpCategory::Subnormal | FpCategory::Normal => { + let dec = ${concat(decode_, $T)}(*self); - #[stable(feature = "rust1", since = "1.0.0")] - impl Display for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_decimal_display(fmt, self) + // The appliance of precision predates the LowerExp mode + // for big and small values, as done next. On the other + // hand, Debug does declare output as “not stable”. FIX? + if let Some(n) = f.precision() { + return fmt_fixed(f, sign, &dec, n.into()); + } + + // Use E notation for small and large values. + let abs = self.abs(); + if abs < 1e-4 || abs >= 1e+16 { + fmt_enote_short(f, sign, &dec, b'e') + } else { + fmt_short(f, sign, &dec, 1) + } + }, } } + } - #[stable(feature = "rust1", since = "1.0.0")] - impl LowerExp for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_exponential_common(fmt, self, false) + #[stable(feature = "rust1", since = "1.0.0")] + impl Display for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let sign: &'static str = ${concat(sign_for_, $T)}(f, *self); + match self.classify() { + FpCategory::Nan => f.pad_number("", 3, | w | w.write_str("NaN")), + FpCategory::Infinite => f.pad_number(sign, 3, | w | w.write_str("inf")), + FpCategory::Zero => match f.precision() { + None | Some(0) => f.pad_number(sign, 1, | w | w.write_str("0")), + Some(n) => f.pad_number(sign, "0.".len() + n, | w | { + w.write_str("0.")?; + w.write_zeroes(n) + }), + }, + FpCategory::Subnormal | FpCategory::Normal => { + let dec = ${concat(decode_, $T)}(*self); + if let Some(n) = f.precision() { + fmt_fixed(f, sign, &dec, n.into()) + } else { + fmt_short(f, sign, &dec, 0) + } + }, } } + } - #[stable(feature = "rust1", since = "1.0.0")] - impl UpperExp for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_exponential_common(fmt, self, true) - } + #[stable(feature = "rust1", since = "1.0.0")] + impl LowerExp for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + ${concat(fmt_exp_, $T)}(f, *self, b'e') + } + } + + #[stable(feature = "rust1", since = "1.0.0")] + impl UpperExp for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + ${concat(fmt_exp_, $T)}(f, *self, b'E') + } + } + + fn ${concat(fmt_exp_, $T)}(f: &mut Formatter<'_>, v: $T, letter_e: u8) -> Result { + let sign: &'static str = ${concat(sign_for_, $T)}(f, v); + match v.classify() { + FpCategory::Nan => f.pad_number("", 3, | w | w.write_str("NaN")), + FpCategory::Infinite => f.pad_number(sign, 3, | w | w.write_str("inf")), + FpCategory::Zero => { + let fix = if letter_e == b'E' { "0E0" } else { "0e0" }; + match f.precision() { + None | Some(0) => f.pad_number(sign, fix.len(), | w | w.write_str(fix)), + Some(n) => f.pad_number(sign, "0.".len() + n + "E0".len(), | w | { + w.write_str("0.")?; + w.write_zeroes(n - 1)?; + w.write_str(&fix) + }), + } + }, + FpCategory::Subnormal | FpCategory::Normal => { + let dec = ${concat(decode_, $T)}(v); + if let Some(n) = f.precision() { + fmt_enote_fixed(f, sign, &dec, letter_e, n) + } else { + fmt_enote_short(f, sign, &dec, letter_e) + } + }, } + } + )* }; } diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 3f4e9b5f325c0..fffeac60e316e 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -6,7 +6,6 @@ use crate::cell::{Cell, Ref, RefCell, RefMut, SyncUnsafeCell, UnsafeCell}; use crate::char::EscapeDebugExtArgs; use crate::hint::assert_unchecked; use crate::marker::{PhantomData, PointeeSized}; -use crate::num::fmt as numfmt; use crate::ops::Deref; use crate::ptr::NonNull; use crate::{iter, mem, result, str}; @@ -1779,7 +1778,7 @@ impl<'a> Formatter<'a> { /// Write a formatted integer to the Formatter. The `digits` string should /// hold the representation of the absolute value, without sign. Whether a - /// sign ("+" or "-") is written depends both on format paramaters and on + /// sign ("+" or "-") is written depends both on format parameters and on /// `is_nonnegative`. The `prefix` (e.g., "0x") is written when alternate. /// Padding may be included when width is Some. The precision is ignored /// for integers. @@ -2005,53 +2004,6 @@ impl<'a> Formatter<'a> { Ok(PostPadding::new(fill, padding - padding_left)) } - /// Takes the formatted parts and applies the padding. - /// - /// Assumes that the caller already has rendered the parts with required precision, - /// so that `self.precision` can be ignored. - /// - /// # Safety - /// - /// Any `numfmt::Part::Copy` parts in `formatted` must contain valid UTF-8. - unsafe fn pad_formatted_parts(&mut self, formatted: &numfmt::Formatted<'_>) -> Result { - unsafe fn write_bytes(buf: &mut dyn Write, s: &[u8]) -> Result { - // SAFETY: This is used for `numfmt::Part::Num` and `numfmt::Part::Copy`. - // It's safe to use for `numfmt::Part::Num` since every char `c` is between - // `b'0'` and `b'9'`, which means `s` is valid UTF-8. It's safe to use for - // `numfmt::Part::Copy` due to this function's precondition. - buf.write_str(unsafe { str::from_utf8_unchecked(s) }) - } - - let sign = formatted.sign; - let out_len = formatted.len() - sign.len(); - self.pad_number(sign, out_len, |f: &mut Self| { - for part in formatted.parts { - match *part { - numfmt::Part::Zero(nzeroes) => { - f.write_zeroes(nzeroes)?; - } - numfmt::Part::Num(mut v) => { - let mut s = [0; 5]; - let len = part.len(); - for c in s[..len].iter_mut().rev() { - *c = b'0' + (v % 10) as u8; - v /= 10; - } - // SAFETY: Per the precondition. - unsafe { - write_bytes(f.buf, &s[..len])?; - } - } - // SAFETY: Per the precondition. - numfmt::Part::Copy(buf) => unsafe { - write_bytes(f.buf, buf)?; - }, - } - } - Ok(()) - }) - } - /// Write n ASCII digits (U+0030) to the Formatter. pub(crate) fn write_zeroes(&mut self, n: usize) -> Result { #[cfg(feature = "optimize_for_size")] diff --git a/library/core/src/num/flt2dec/decoder.rs b/library/core/src/num/flt2dec/decoder.rs index bd6e2cdbafec8..2bdbf953c2e23 100644 --- a/library/core/src/num/flt2dec/decoder.rs +++ b/library/core/src/num/flt2dec/decoder.rs @@ -1,107 +1,93 @@ //! Decodes a floating-point value into individual parts and error ranges. -use crate::num::FpCategory; -use crate::num::dec2flt::float::RawFloat; +use crate::mem::size_of; -/// Decoded unsigned finite value, such that: -/// -/// - The original value equals to `mant * 2^exp`. -/// -/// - Any number from `(mant - minus) * 2^exp` to `(mant + plus) * 2^exp` will -/// round to the original value. The range is inclusive only when -/// `inclusive` is `true`. +/// Generic decoding of floating points up to 64-bit wide such that its absolute +/// finite value matches mant * 2^exp. Values in range (mant - minus) * 2^exp up +/// to (mant + plus) * 2^exp will all round to the same value. The range with +/// minus and plus is inclusive only when `inclusive` is true. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Decoded { - /// The scaled mantissa. +pub struct Decoded64 { + /// Scaled Mantissa pub mant: u64, - /// The lower error range. + /// Lower Error Range pub minus: u64, - /// The upper error range. + /// Upper Error Range pub plus: u64, - /// The shared exponent in base 2. - pub exp: i16, - /// True when the error range is inclusive. - /// - /// In IEEE 754, this is true when the original mantissa was even. + /// Shared Exponent In Base 2 + pub exp: isize, + /// Flag For Error Range pub inclusive: bool, } -/// Decoded unsigned value. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum FullDecoded { - /// Not-a-number. - Nan, - /// Infinities, either positive or negative. - Infinite, - /// Zero, either positive or negative. - Zero, - /// Finite numbers with further decoded fields. - Finite(Decoded), -} +macro_rules! floats { + ($($T:ident)*) => { + $( -/// A floating point type which can be `decode`d. -pub trait DecodableFloat: RawFloat + Copy { - /// The minimum positive normalized value. - fn min_pos_norm_value() -> Self; -} + /// Decode a floating-point into its integer components. The tuple in + /// return contains the mantissa m and exponent e, such that original + /// value equals m × 2^e, ignoring the sign. + /// + /// For normal numbers: mantissa includes the implied leading 1. + /// For denormal numbers: mantissa is shifted to maintain the equation. + const fn ${concat(mant_and_exp_, $T)}(v: $T) -> (u64, isize) { + const ENC_BITS: usize = size_of::<$T>() * 8; + // The encoding of the sign resides in the most significant bit. + const SIGN_ENC_BITS: usize = 1; + // The encoding of the mantissa resides in the least-significant + // bits. + const MANT_ENC_BITS: usize = $T::MANTISSA_DIGITS as usize - 1; + // The encoding of the exponent resides in the remaining bits, + // inbetween sign and the mantissa. + const EXP_ENC_BITS: usize = ENC_BITS - (SIGN_ENC_BITS + MANT_ENC_BITS); -#[cfg(target_has_reliable_f16)] -impl DecodableFloat for f16 { - fn min_pos_norm_value() -> Self { - f16::MIN_POSITIVE - } -} + let enc = v.to_bits(); + let exp_enc = (enc << SIGN_ENC_BITS) >> (SIGN_ENC_BITS + MANT_ENC_BITS); + let mant_enc = enc & ((1 << MANT_ENC_BITS) - 1); -impl DecodableFloat for f32 { - fn min_pos_norm_value() -> Self { - f32::MIN_POSITIVE - } -} + const EXP_BIAS: isize = (1 << (EXP_ENC_BITS - 1)) - 1; + let exp = exp_enc as isize - (EXP_BIAS + MANT_ENC_BITS as isize); -impl DecodableFloat for f64 { - fn min_pos_norm_value() -> Self { - f64::MIN_POSITIVE - } -} + let mant = if exp_enc != 0 { + // Normal numbers have an implied leading 1 to the mantissa + // bits. + mant_enc | 1 << MANT_ENC_BITS + } else { + // Denormal numbers use a special exponent of 1 − bias instead + // of −bias. + mant_enc << 1 + }; -/// Returns a sign (true when negative) and `FullDecoded` value -/// from given floating point number. -pub fn decode(v: T) -> (/*negative?*/ bool, FullDecoded) { - let (mant, exp, sign) = v.integer_decode(); - let even = (mant & 1) == 0; - let decoded = match v.classify() { - FpCategory::Nan => FullDecoded::Nan, - FpCategory::Infinite => FullDecoded::Infinite, - FpCategory::Zero => FullDecoded::Zero, - FpCategory::Subnormal => { - // neighbors: (mant - 2, exp) -- (mant, exp) -- (mant + 2, exp) - // Float::integer_decode always preserves the exponent, - // so the mantissa is scaled for subnormals. - FullDecoded::Finite(Decoded { mant, minus: 1, plus: 1, exp, inclusive: even }) + const _: () = assert!(ENC_BITS <= 64); + (mant as u64, exp) } - FpCategory::Normal => { - let minnorm = ::min_pos_norm_value().integer_decode(); - if mant == minnorm.0 { + + /// Parse a finite value into the generic structure. + pub fn ${concat(decode_, $T)}(v: $T) -> Decoded64 { + let (mant, exp) = ${concat(mant_and_exp_, $T)}(v); + let is_even = (mant & 1) == 0; + + if v.is_subnormal() { + // neighbors: (mant - 2, exp) -- (mant, exp) -- (mant + 2, exp) + return Decoded64 { mant: mant, minus: 1, plus: 1, exp: exp, inclusive: is_even }; + } + debug_assert!(v.is_normal()); + + const MIN_POS_MANT: u64 = ${concat(mant_and_exp_, $T)}($T::MIN_POSITIVE).0; + const MIN_NEG_MANT: u64 = ${concat(mant_and_exp_, $T)}(-$T::MIN_POSITIVE).0; + const _: () = assert!(MIN_POS_MANT == MIN_NEG_MANT); + if mant == MIN_POS_MANT { // neighbors: (maxmant, exp - 1) -- (minnormmant, exp) -- (minnormmant + 1, exp) - // where maxmant = minnormmant * 2 - 1 - FullDecoded::Finite(Decoded { - mant: mant << 2, - minus: 1, - plus: 2, - exp: exp - 2, - inclusive: even, - }) + // where maxmant = minnorm.mant * 2 - 1 + Decoded64 { mant: mant << 2, minus: 1, plus: 2, exp: exp - 2, inclusive: is_even } } else { // neighbors: (mant - 1, exp) -- (mant, exp) -- (mant + 1, exp) - FullDecoded::Finite(Decoded { - mant: mant << 1, - minus: 1, - plus: 1, - exp: exp - 1, - inclusive: even, - }) + Decoded64 { mant: mant << 1, minus: 1, plus: 1, exp: exp - 1, inclusive: is_even } } } + + )* }; - (sign < 0, decoded) } + +floats! { f16 f32 f64 } diff --git a/library/core/src/num/flt2dec/estimator.rs b/library/core/src/num/flt2dec/estimator.rs index 50e2f70528383..68699bb1d157b 100644 --- a/library/core/src/num/flt2dec/estimator.rs +++ b/library/core/src/num/flt2dec/estimator.rs @@ -1,14 +1,21 @@ //! The exponent estimator. -/// Finds `k_0` such that `10^(k_0-1) < mant * 2^exp <= 10^(k_0+1)`. +/// Estimates the base-10 scaling factor `k = ceil(log10(mant * 2^exp))`. /// -/// This is used to approximate `k = ceil(log_10 (mant * 2^exp))`; -/// the true `k` is either `k_0` or `k_0+1`. +/// Returns a lower-bound estimate `r` such that: +/// +/// k ∈ { r, r + 1 } #[doc(hidden)] -pub fn estimate_scaling_factor(mant: u64, exp: i16) -> i16 { - // 2^(nbits-1) < mant <= 2^nbits if mant > 0 +pub fn estimate_scaling_factor(mant: u64, exp: isize) -> isize { + // 2^(nbits - 1) < mant <= 2^nbits if mant > 0 let nbits = 64 - (mant - 1).leading_zeros() as i64; - // 1292913986 = floor(2^32 * log_10 2) - // therefore this always underestimates (or is exact), but not much. - (((nbits + exp as i64) * 1292913986) >> 32) as i16 + let n = nbits + exp as i64; + // log₁₀(2ⁿ) = n × log₁₀(2) + // + // To multiply with log₁₀(2) as an integer, the fraction (≈0.3) is scaled. + // + // n × log₁₀(2) = (n × log₁₀(2) × C) ÷ C + // + // With C = 2³², and ⌊log₁₀(2) × 2³²⌋ = 1292913986, we can compute: + ((n * 1292913986) >> 32) as isize } diff --git a/library/core/src/num/flt2dec/mod.rs b/library/core/src/num/flt2dec/mod.rs index e79a00a865969..de0003aa867d9 100644 --- a/library/core/src/num/flt2dec/mod.rs +++ b/library/core/src/num/flt2dec/mod.rs @@ -1,131 +1,63 @@ /*! -Floating-point number to decimal conversion routines. +# Floating-Point Number To Decimal Conversion -# Problem statement +Given a floating-point f as `Decoded64`, we compute its decimal representation +with n significant digits stored in array d[0..n-1], and a base-10 exponent k, +such that: -We are given the floating-point number `v = f * 2^e` with an integer `f`, -and its bounds `minus` and `plus` such that any number between `v - minus` and -`v + plus` will be rounded to `v`. For the simplicity we assume that -this range is exclusive. Then we would like to get the unique decimal -representation `V = 0.d[0..n-1] * 10^k` such that: + v = (0.d₀d₁…dₙ₋₁) × 10ᵏ -- `d[0]` is non-zero. +where d₀ ≠ 0, ensuring 0.1 ≤ mantissa < 1. -- It's correctly rounded when parsed back: `v - minus < V < v + plus`. - Furthermore it is shortest such one, i.e., there is no representation - with less than `n` digits that is correctly rounded. +The computed v must be the closest such n-digit decimal to f: -- It's closest to the original value: `abs(V - v) <= 10^(k-n) / 2`. Note that - there might be two representations satisfying this uniqueness requirement, - in which case some tie-breaking mechanism is used. + |f − v| ≤ 10ᵏ⁻ⁿ / 2 -We will call this mode of operation as to the *shortest* mode. This mode is used -when there is no additional constraint, and can be thought as a "natural" mode -as it matches the ordinary intuition (it at least prints `0.1f32` as "0.1"). +If two n-digit decimals are equally close, then some tie-breaking mechanism is +used. This ensures that parsing v back recovers f exactly. -We have two more modes of operation closely related to each other. In these modes -we are given either the number of significant digits `n` or the last-digit -limitation `limit` (which determines the actual `n`), and we would like to get -the representation `V = 0.d[0..n-1] * 10^k` such that: +In *short* mode, the smallest n ≥ 1 is taken which still satisfies the above, +thus yielding the minimal decimal which round‑trips correctly. Such formatting +matches ordinary intuition. Note how 0.1f32 prints as "0.1". -- `d[0]` is non-zero, unless `n` was zero in which case only `k` is returned. - -- It's closest to the original value: `abs(V - v) <= 10^(k-n) / 2`. Again, - there might be some tie-breaking mechanism. - -When `limit` is given but not `n`, we set `n` such that `k - n = limit` -so that the last digit `d[n-1]` is scaled by `10^(k-n) = 10^limit`. -If such `n` is negative, we clip it to zero so that we will only get `k`. -We are also limited by the supplied buffer. This limitation is used to print -the number up to given number of fractional digits without knowing -the correct `k` beforehand. - -We will call the mode of operation requiring `n` as to the *exact* mode, -and one requiring `limit` as to the *fixed* mode. The exact mode is a subset of -the fixed mode: the sufficiently large last-digit limitation will eventually fill -the supplied buffer and let the algorithm to return. +In *fixed* mode, n is limited by buffer size. The `resolution` parameter may +further restrict n by requiring v to be an integer multiple of 10^{resolution}. +For example, a resolution of -3 causes rounding to 3 decimal places, i.e., have +multiples of 0.001. # Implementation overview -It is easy to get the floating point printing correct but slow (Russ Cox has -[demonstrated](https://research.swtch.com/ftoa) how it's easy), or incorrect but -fast (naïve division and modulo). But it is surprisingly hard to print -floating point numbers correctly *and* efficiently. - -There are two classes of algorithms widely known to be correct. - -- The "Dragon" family of algorithm is first described by Guy L. Steele Jr. and - Jon L. White. They rely on the fixed-size big integer for their correctness. - A slight improvement was found later, which is posthumously described by - Robert G. Burger and R. Kent Dybvig. David Gay's `dtoa.c` routine is - a popular implementation of this strategy. - -- The "Grisu" family of algorithm is first described by Florian Loitsch. - They use very cheap integer-only procedure to determine the close-to-correct - representation which is at least guaranteed to be shortest. The variant, - Grisu3, actively detects if the resulting representation is incorrect. - -We implement both algorithms with necessary tweaks to suit our requirements. -In particular, published literatures are short of the actual implementation -difficulties like how to avoid arithmetic overflows. Each implementation, -available in `strategy::dragon` and `strategy::grisu` respectively, -extensively describes all necessary justifications and many proofs for them. -(It is still difficult to follow though. You have been warned.) - -Both implementations expose two public functions: - -- `format_shortest(decoded, buf)`, which always needs at least - `MAX_SIG_DIGITS` digits of buffer. Implements the shortest mode. - -- `format_exact(decoded, buf, limit)`, which accepts as small as - one digit of buffer. Implements exact and fixed modes. +Floating‑point to decimal conversion is deceptively simple to implement +incorrectly. Russ Cox [demonstrated](https://research.swtch.com/ftoa) how a +slow but correct algorithm can be concise — yet a fast version risks subtle +rounding errors if based on naïve division and modulo. -They try to fill the `u8` buffer with digits and returns the number of digits -written and the exponent `k`. They are total for all finite `f32` and `f64` -inputs (Grisu internally falls back to Dragon if necessary). +There are two widely‑known classes of correct algorithms: -The rendered digits are formatted into the actual string form with -four functions: +- The **Dragon** family, first described by Guy L. Steele Jr. and Jon L. White, + relies on arbitrary‑precision integer arithmetic to guarantee correct rounding. + A later improvement was published posthumously by Robert G. Burger and + R. Kent Dybvig. David Gay’s `dtoa.c` is a well‑known implementation of this + strategy. -- `to_shortest_str` prints the shortest representation, which can be padded by - zeroes to make *at least* given number of fractional digits. +- The **Grisu** family, introduced by Florian Loitsch, uses a fast, integer‑only + procedure to produce a decimal representation that is **always shortest**. + The Grisu3 variant actively detects when the result might not satisfy the + exact rounding condition (|f − v| ≤ 10ᵏ⁻ⁿ / 2). -- `to_shortest_exp_str` prints the shortest representation, which can be - padded by zeroes when its exponent is in the specified ranges, - or can be printed in the exponential form such as `1.23e45`. - -- `to_exact_exp_str` prints the exact representation with given number of - digits in the exponential form. - -- `to_exact_fixed_str` prints the fixed representation with *exactly* - given number of fractional digits. - -They all return a slice of preallocated `Part` array, which corresponds to -the individual part of strings: a fixed string, a part of rendered digits, -a number of zeroes or a small (`u16`) number. The caller is expected to -provide a large enough buffer and `Part` array, and to assemble the final -string from resulting `Part`s itself. - -All algorithms and formatting functions are accompanied by extensive tests -in `coretests::num::flt2dec` module. It also shows how to use individual -functions. +Our Grisu implementation uses `Option` `None` to indicate uncertainty. A fall +back to Dragon delivers both performance and guaranteed correctness. */ -// while this is extensively documented, this is in principle private which is -// only made public for testing. do not expose us. #![doc(hidden)] #![unstable( feature = "flt2dec", - reason = "internal routines only exposed for testing", + reason = "internals for use within library exclusively", issue = "none" )] -pub use self::decoder::{DecodableFloat, Decoded, FullDecoded, decode}; -use super::fmt::{Formatted, Part}; -use crate::mem::MaybeUninit; - pub mod decoder; pub mod estimator; @@ -135,12 +67,10 @@ pub mod strategy { pub mod grisu; } -/// The minimum size of buffer necessary for the shortest mode. -/// -/// It is a bit non-trivial to derive, but this is one plus the maximal number of -/// significant decimal digits from formatting algorithms with the shortest result. -/// The exact formula is `ceil(# bits in mantissa * log_10 2 + 1)`. -pub const MAX_SIG_DIGITS: usize = 17; +/// The buffer size needed for digits in shortest mode can be calculated as: +/// ceil(bits_in_mantissa * log10(2)) + 1. Decoded64 is limited to f64 (with +/// 53 mantissa bits), thus the value is set to ceil(15.95) + 1. +pub const SHORT_DIGITS_MAX: usize = 17; /// When `d` contains decimal digits, increase the last digit and propagate carry. /// Returns a next digit when it causes the length to change. @@ -165,504 +95,3 @@ pub fn round_up(d: &mut [u8]) -> Option { } } } - -/// Formats given decimal digits `0.<...buf...> * 10^exp` into the decimal form -/// with at least given number of fractional digits. The result is stored to -/// the supplied parts array and a slice of written parts is returned. -/// -/// `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 `frac_digits` of 0 means that -/// it will only print given digits and nothing else. -fn digits_to_dec_str<'a>( - buf: &'a [u8], - exp: i16, - frac_digits: usize, - parts: &'a mut [MaybeUninit>], -) -> &'a [Part<'a>] { - assert!(!buf.is_empty()); - assert!(buf[0] > b'0'); - assert!(parts.len() >= 4); - - // if there is the restriction on the last digit position, `buf` is assumed to be - // left-padded with the virtual zeroes. the number of virtual zeroes, `nzeroes`, - // equals to `max(0, exp + frac_digits - buf.len())`, so that the position of - // the last digit `exp - buf.len() - nzeroes` is no more than `-frac_digits`: - // - // |<-virtual->| - // |<---- buf ---->| zeroes | exp - // 0. 1 2 3 4 5 6 7 8 9 _ _ _ _ _ _ x 10 - // | | | - // 10^exp 10^(exp-buf.len()) 10^(exp-buf.len()-nzeroes) - // - // `nzeroes` is individually calculated for each case in order to avoid overflow. - - if exp <= 0 { - // the decimal point is before rendered digits: [0.][000...000][1234][____] - let minus_exp = -(exp as i32) as usize; - parts[0] = MaybeUninit::new(Part::Copy(b"0.")); - parts[1] = MaybeUninit::new(Part::Zero(minus_exp)); - parts[2] = MaybeUninit::new(Part::Copy(buf)); - if frac_digits > buf.len() && frac_digits - buf.len() > minus_exp { - parts[3] = MaybeUninit::new(Part::Zero((frac_digits - buf.len()) - minus_exp)); - // SAFETY: we just initialized the elements `..4`. - unsafe { parts[..4].assume_init_ref() } - } else { - // SAFETY: we just initialized the elements `..3`. - unsafe { parts[..3].assume_init_ref() } - } - } else { - let exp = exp as usize; - if exp < buf.len() { - // the decimal point is inside rendered digits: [12][.][34][____] - parts[0] = MaybeUninit::new(Part::Copy(&buf[..exp])); - parts[1] = MaybeUninit::new(Part::Copy(b".")); - parts[2] = MaybeUninit::new(Part::Copy(&buf[exp..])); - if frac_digits > buf.len() - exp { - parts[3] = MaybeUninit::new(Part::Zero(frac_digits - (buf.len() - exp))); - // SAFETY: we just initialized the elements `..4`. - unsafe { parts[..4].assume_init_ref() } - } else { - // SAFETY: we just initialized the elements `..3`. - unsafe { parts[..3].assume_init_ref() } - } - } else { - // the decimal point is after rendered digits: [1234][____0000] or [1234][__][.][__]. - parts[0] = MaybeUninit::new(Part::Copy(buf)); - parts[1] = MaybeUninit::new(Part::Zero(exp - buf.len())); - if frac_digits > 0 { - parts[2] = MaybeUninit::new(Part::Copy(b".")); - parts[3] = MaybeUninit::new(Part::Zero(frac_digits)); - // SAFETY: we just initialized the elements `..4`. - unsafe { parts[..4].assume_init_ref() } - } else { - // SAFETY: we just initialized the elements `..2`. - unsafe { parts[..2].assume_init_ref() } - } - } - } -} - -/// 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`, -/// 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`; -/// 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 -/// it will only print the given digits and nothing else. -fn digits_to_exp_str<'a>( - buf: &'a [u8], - exp: i16, - min_ndigits: usize, - upper: bool, - parts: &'a mut [MaybeUninit>], -) -> &'a [Part<'a>] { - assert!(!buf.is_empty()); - assert!(buf[0] > b'0'); - assert!(parts.len() >= 6); - - let mut n = 0; - - parts[n] = MaybeUninit::new(Part::Copy(&buf[..1])); - n += 1; - - if buf.len() > 1 || min_ndigits > 1 { - 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; - } - } - - // 0.1234 x 10^exp = 1.234 x 10^(exp-1) - let exp = exp as i32 - 1; // avoid underflow when exp is i16::MIN - if exp < 0 { - parts[n] = MaybeUninit::new(Part::Copy(if upper { b"E-" } else { b"e-" })); - parts[n + 1] = MaybeUninit::new(Part::Num(-exp as u16)); - } else { - parts[n] = MaybeUninit::new(Part::Copy(if upper { b"E" } else { b"e" })); - parts[n + 1] = MaybeUninit::new(Part::Num(exp as u16)); - } - // SAFETY: we just initialized the elements `..n + 2`. - unsafe { parts[..n + 2].assume_init_ref() } -} - -/// Sign formatting options. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Sign { - /// Prints `-` for any negative value. - Minus, // -inf -1 -0 0 1 inf nan - /// Prints `-` for any negative value, or `+` otherwise. - MinusPlus, // -inf -1 -0 +0 +1 +inf nan -} - -/// Returns the static byte string corresponding to the sign to be formatted. -/// It can be either `""`, `"+"` or `"-"`. -fn determine_sign(sign: Sign, decoded: &FullDecoded, negative: bool) -> &'static str { - match (*decoded, sign) { - (FullDecoded::Nan, _) => "", - (_, Sign::Minus) => { - if negative { - "-" - } else { - "" - } - } - (_, Sign::MinusPlus) => { - if negative { - "-" - } else { - "+" - } - } - } -} - -/// Formats the given floating point number into the decimal form with at least -/// given number of fractional digits. The result is stored to the supplied parts -/// array while utilizing given byte buffer as a scratch. `upper` is currently -/// unused but left for the future decision to change the case of non-finite values, -/// i.e., `inf` and `nan`. The first part to be rendered is always a `Part::Sign` -/// (which can be an empty string if no sign is rendered). -/// -/// `format_shortest` should be the underlying digit-generation function. -/// It should return the part of the buffer that it initialized. -/// You probably would want `strategy::grisu::format_shortest` for this. -/// -/// `frac_digits` can be less than the number of actual fractional digits in `v`; -/// it will be ignored and full digits will be printed. It is only used to print -/// additional zeroes after rendered digits. Thus `frac_digits` of 0 means that -/// it will only print given digits and nothing else. -/// -/// The byte buffer should be at least `MAX_SIG_DIGITS` bytes long. -/// There should be at least 4 parts available, due to the worst case like -/// `[+][0.][0000][2][0000]` with `frac_digits = 10`. -pub fn to_shortest_str<'a, T, F>( - mut format_shortest: F, - v: T, - sign: Sign, - frac_digits: usize, - buf: &'a mut [MaybeUninit], - parts: &'a mut [MaybeUninit>], -) -> Formatted<'a> -where - T: DecodableFloat, - F: FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - assert!(parts.len() >= 4); - assert!(buf.len() >= MAX_SIG_DIGITS); - - let (negative, full_decoded) = decode(v); - let sign = determine_sign(sign, &full_decoded, negative); - match full_decoded { - FullDecoded::Nan => { - parts[0] = MaybeUninit::new(Part::Copy(b"NaN")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Infinite => { - parts[0] = MaybeUninit::new(Part::Copy(b"inf")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Zero => { - if frac_digits > 0 { - // [0.][0000] - parts[0] = MaybeUninit::new(Part::Copy(b"0.")); - parts[1] = MaybeUninit::new(Part::Zero(frac_digits)); - Formatted { - sign, - // SAFETY: we just initialized the elements `..2`. - parts: unsafe { parts[..2].assume_init_ref() }, - } - } else { - parts[0] = MaybeUninit::new(Part::Copy(b"0")); - Formatted { - sign, - // SAFETY: we just initialized the elements `..1`. - parts: unsafe { parts[..1].assume_init_ref() }, - } - } - } - FullDecoded::Finite(ref decoded) => { - let (buf, exp) = format_shortest(decoded, buf); - Formatted { sign, parts: digits_to_dec_str(buf, exp, frac_digits, parts) } - } - } -} - -/// Formats the given floating point number into the decimal form or -/// the exponential form, depending on the resulting exponent. 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 non-finite values -/// (`inf` and `nan`) or the case of the exponent prefix (`e` or `E`). -/// The first part to be rendered is always a `Part::Sign` (which can be -/// an empty string if no sign is rendered). -/// -/// `format_shortest` should be the underlying digit-generation function. -/// It should return the part of the buffer that it initialized. -/// You probably would want `strategy::grisu::format_shortest` for this. -/// -/// The `dec_bounds` is a tuple `(lo, hi)` such that the number is formatted -/// as decimal only when `10^lo <= V < 10^hi`. Note that this is the *apparent* `V` -/// instead of the actual `v`! Thus any printed exponent in the exponential form -/// cannot be in this range, avoiding any confusion. -/// -/// The byte buffer should be at least `MAX_SIG_DIGITS` bytes long. -/// There should be at least 6 parts available, due to the worst case like -/// `[+][1][.][2345][e][-][6]`. -pub fn to_shortest_exp_str<'a, T, F>( - mut format_shortest: F, - v: T, - sign: Sign, - dec_bounds: (i16, i16), - upper: bool, - buf: &'a mut [MaybeUninit], - parts: &'a mut [MaybeUninit>], -) -> Formatted<'a> -where - T: DecodableFloat, - F: FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - assert!(parts.len() >= 6); - assert!(buf.len() >= MAX_SIG_DIGITS); - assert!(dec_bounds.0 <= dec_bounds.1); - - let (negative, full_decoded) = decode(v); - let sign = determine_sign(sign, &full_decoded, negative); - match full_decoded { - FullDecoded::Nan => { - parts[0] = MaybeUninit::new(Part::Copy(b"NaN")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Infinite => { - parts[0] = MaybeUninit::new(Part::Copy(b"inf")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Zero => { - parts[0] = if dec_bounds.0 <= 0 && 0 < dec_bounds.1 { - MaybeUninit::new(Part::Copy(b"0")) - } else { - MaybeUninit::new(Part::Copy(if upper { b"0E0" } else { b"0e0" })) - }; - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Finite(ref decoded) => { - let (buf, exp) = format_shortest(decoded, buf); - let vis_exp = exp as i32 - 1; - let parts = if dec_bounds.0 as i32 <= vis_exp && vis_exp < dec_bounds.1 as i32 { - digits_to_dec_str(buf, exp, 0, parts) - } else { - digits_to_exp_str(buf, exp, 0, upper, parts) - }; - Formatted { sign, parts } - } - } -} - -/// Returns a rather crude approximation (upper bound) for the maximum buffer size -/// calculated from the given decoded exponent. -/// -/// The exact limit is: -/// -/// - when `exp < 0`, the maximum length is `ceil(log_10 (5^-exp * (2^64 - 1)))`. -/// - when `exp >= 0`, the maximum length is `ceil(log_10 (2^exp * (2^64 - 1)))`. -/// -/// `ceil(log_10 (x^exp * (2^64 - 1)))` is less than `ceil(log_10 (2^64 - 1)) + -/// ceil(exp * log_10 x)`, which is in turn less than `20 + (1 + exp * log_10 x)`. -/// We use the facts that `log_10 2 < 5/16` and `log_10 5 < 12/16`, which is -/// enough for our purposes. -/// -/// Why do we need this? `format_exact` functions will fill the entire buffer -/// unless limited by the last digit restriction, but it is possible that -/// the number of digits requested is ridiculously large (say, 30,000 digits). -/// The vast majority of buffer will be filled with zeroes, so we don't want to -/// allocate all the buffer beforehand. Consequently, for any given arguments, -/// 826 bytes of buffer should be sufficient for `f64`. Compare this with -/// the actual number for the worst case: 770 bytes (when `exp = -1074`). -fn estimate_max_buf_len(exp: i16) -> usize { - 21 + ((if exp < 0 { -12 } else { 5 } * exp as i32) as usize >> 4) -} - -/// Formats given floating point number into the exponential form with -/// exactly given number of significant 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 -/// an empty string if no sign is rendered). -/// -/// `format_exact` should be the underlying digit-generation function. -/// 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 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, - upper: bool, - buf: &'a mut [MaybeUninit], - parts: &'a mut [MaybeUninit>], -) -> Formatted<'a> -where - T: DecodableFloat, - F: FnMut(&Decoded, &'a mut [MaybeUninit], 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); - match full_decoded { - FullDecoded::Nan => { - parts[0] = MaybeUninit::new(Part::Copy(b"NaN")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Infinite => { - parts[0] = MaybeUninit::new(Part::Copy(b"inf")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Zero => { - if ndigits > 1 { - // [0.][0000][e0] - parts[0] = MaybeUninit::new(Part::Copy(b"0.")); - parts[1] = MaybeUninit::new(Part::Zero(ndigits - 1)); - parts[2] = MaybeUninit::new(Part::Copy(if upper { b"E0" } else { b"e0" })); - Formatted { - sign, - // SAFETY: we just initialized the elements `..3`. - parts: unsafe { parts[..3].assume_init_ref() }, - } - } else { - parts[0] = MaybeUninit::new(Part::Copy(if upper { b"0E0" } else { b"0e0" })); - Formatted { - sign, - // SAFETY: we just initialized the elements `..1`. - parts: unsafe { parts[..1].assume_init_ref() }, - } - } - } - 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) } - } - } -} - -/// Formats given floating point number into the decimal form with 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 currently -/// unused but left for the future decision to change the case of non-finite values, -/// i.e., `inf` and `nan`. The first part to be rendered is always a `Part::Sign` -/// (which can be an empty string if no sign is rendered). -/// -/// `format_exact` should be the underlying digit-generation function. -/// 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 enough for the output 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, and 1000 bytes should be enough.) -/// There should be at least 4 parts available, due to the worst case like -/// `[+][0.][0000][2][0000]` with `frac_digits = 10`. -pub fn to_exact_fixed_str<'a, T, F>( - mut format_exact: F, - v: T, - sign: Sign, - frac_digits: usize, - buf: &'a mut [MaybeUninit], - parts: &'a mut [MaybeUninit>], -) -> Formatted<'a> -where - T: DecodableFloat, - F: FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - assert!(parts.len() >= 4); - - let (negative, full_decoded) = decode(v); - let sign = determine_sign(sign, &full_decoded, negative); - match full_decoded { - FullDecoded::Nan => { - parts[0] = MaybeUninit::new(Part::Copy(b"NaN")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Infinite => { - parts[0] = MaybeUninit::new(Part::Copy(b"inf")); - // SAFETY: we just initialized the elements `..1`. - Formatted { sign, parts: unsafe { parts[..1].assume_init_ref() } } - } - FullDecoded::Zero => { - if frac_digits > 0 { - // [0.][0000] - parts[0] = MaybeUninit::new(Part::Copy(b"0.")); - parts[1] = MaybeUninit::new(Part::Zero(frac_digits)); - Formatted { - sign, - // SAFETY: we just initialized the elements `..2`. - parts: unsafe { parts[..2].assume_init_ref() }, - } - } else { - parts[0] = MaybeUninit::new(Part::Copy(b"0")); - Formatted { - sign, - // SAFETY: we just initialized the elements `..1`. - parts: unsafe { parts[..1].assume_init_ref() }, - } - } - } - FullDecoded::Finite(ref decoded) => { - let maxlen = estimate_max_buf_len(decoded.exp); - assert!(buf.len() >= maxlen); - - // it *is* possible that `frac_digits` is ridiculously large. - // `format_exact` will end rendering digits much earlier in this case, - // because we are strictly limited by `maxlen`. - let limit = if frac_digits < 0x8000 { -(frac_digits as i16) } else { i16::MIN }; - let (buf, exp) = format_exact(decoded, &mut buf[..maxlen], limit); - if exp <= limit { - // the restriction couldn't been met, so this should render like zero no matter - // `exp` was. this does not include the case that the restriction has been met - // only after the final rounding-up; it's a regular case with `exp = limit + 1`. - debug_assert_eq!(buf.len(), 0); - if frac_digits > 0 { - // [0.][0000] - parts[0] = MaybeUninit::new(Part::Copy(b"0.")); - parts[1] = MaybeUninit::new(Part::Zero(frac_digits)); - Formatted { - sign, - // SAFETY: we just initialized the elements `..2`. - parts: unsafe { parts[..2].assume_init_ref() }, - } - } else { - parts[0] = MaybeUninit::new(Part::Copy(b"0")); - Formatted { - sign, - // SAFETY: we just initialized the elements `..1`. - parts: unsafe { parts[..1].assume_init_ref() }, - } - } - } else { - Formatted { sign, parts: digits_to_dec_str(buf, exp, frac_digits, parts) } - } - } - } -} diff --git a/library/core/src/num/flt2dec/strategy/dragon.rs b/library/core/src/num/flt2dec/strategy/dragon.rs index dd73e4b4846d5..dc50decab354a 100644 --- a/library/core/src/num/flt2dec/strategy/dragon.rs +++ b/library/core/src/num/flt2dec/strategy/dragon.rs @@ -7,8 +7,9 @@ use crate::cmp::Ordering; use crate::mem::MaybeUninit; use crate::num::bignum::{Big32x40 as Big, Digit32 as Digit}; +use crate::num::flt2dec::decoder::Decoded64; use crate::num::flt2dec::estimator::estimate_scaling_factor; -use crate::num::flt2dec::{Decoded, MAX_SIG_DIGITS, round_up}; +use crate::num::flt2dec::{SHORT_DIGITS_MAX, round_up}; static POW10: [Digit; 10] = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; @@ -98,11 +99,11 @@ fn div_rem_upto_16<'a>( (d, x) } -/// The shortest mode implementation for Dragon. -pub fn format_shortest<'a>( - d: &Decoded, - buf: &'a mut [MaybeUninit], -) -> (/*digits*/ &'a [u8], /*exp*/ i16) { +/// Implementation of the short mode for Dragon. +pub fn format_short<'a>( + d: &Decoded64, + buf: &'a mut [MaybeUninit; SHORT_DIGITS_MAX], +) -> (/*digits*/ &'a str, /*pow10*/ isize) { // the number `v` to format is known to be: // - equal to `mant * 2^exp`; // - preceded by `(mant - 2 * minus) * 2^exp` in the original type; and @@ -120,7 +121,6 @@ pub fn format_shortest<'a>( assert!(d.plus > 0); assert!(d.mant.checked_add(d.plus).is_some()); assert!(d.mant.checked_sub(d.minus).is_some()); - assert!(buf.len() >= MAX_SIG_DIGITS); // `a.cmp(&b) < rounding` is `if d.inclusive {a <= b} else {a < b}` let rounding = if d.inclusive { Ordering::Greater } else { Ordering::Equal }; @@ -254,16 +254,19 @@ pub fn format_shortest<'a>( } } - // SAFETY: we initialized that memory above. - (unsafe { buf[..i].assume_init_ref() }, k) + // SAFETY: We initialized the memory above. + let written = unsafe { buf[..i].assume_init_ref() }; + // SAFETY: Only ASCII digits were written. + let digits = unsafe { str::from_utf8_unchecked(written) }; + (digits, k) } -/// The exact and fixed mode implementation for Dragon. -pub fn format_exact<'a>( - d: &Decoded, +/// Implementation of the fixed mode for Dragon. +pub fn format_fixed<'a>( + d: &Decoded64, buf: &'a mut [MaybeUninit], - limit: i16, -) -> (/*digits*/ &'a [u8], /*exp*/ i16) { + resolution: isize, +) -> (/*digits*/ &'a str, /*pow10*/ isize) { assert!(d.mant > 0); assert!(d.minus > 0); assert!(d.plus > 0); @@ -303,14 +306,14 @@ pub fn format_exact<'a>( // if we are working with the last-digit limitation, we need to shorten the buffer // before the actual rendering in order to avoid double rounding. // note that we have to enlarge the buffer again when rounding up happens! - let mut len = if k < limit { + let mut len = if k < resolution { // oops, we cannot even produce *one* digit. // this is possible when, say, we've got something like 9.5 and it's being rounded to 10. // we return an empty buffer, with an exception of the later rounding-up case - // which occurs when `k == limit` and has to produce exactly one digit. + // which occurs when `k == resolution` and has to produce exactly one digit. 0 - } else if ((k as i32 - limit as i32) as usize) < buf.len() { - (k - limit) as usize + } else if ((k - resolution) as usize) < buf.len() { + (k - resolution) as usize } else { buf.len() }; @@ -332,8 +335,11 @@ pub fn format_exact<'a>( for c in &mut buf[i..len] { *c = MaybeUninit::new(b'0'); } - // SAFETY: we initialized that memory above. - return (unsafe { buf[..len].assume_init_ref() }, k); + // SAFETY: We initialized the memory above. + let written = unsafe { buf[..len].assume_init_ref() }; + // SAFETY: Only ASCII digits were written. + let digits = unsafe { str::from_utf8_unchecked(written) }; + return (digits, k); } let mut d = 0; @@ -375,15 +381,18 @@ pub fn format_exact<'a>( if let Some(c) = round_up(unsafe { buf[..len].assume_init_mut() }) { // ...unless we've been requested the fixed precision instead. // we also need to check that, if the original buffer was empty, - // the additional digit can only be added when `k == limit` (edge case). + // the additional digit can only be added when `k == resolution` (edge case). k += 1; - if k > limit && len < buf.len() { + if k > resolution && len < buf.len() { buf[len] = MaybeUninit::new(c); len += 1; } } } - // SAFETY: we initialized that memory above. - (unsafe { buf[..len].assume_init_ref() }, k) + // SAFETY: We initialized the memory above. + let written = unsafe { buf[..len].assume_init_ref() }; + // SAFETY: Only ASCII digits were written. + let digits = unsafe { str::from_utf8_unchecked(written) }; + (digits, k) } diff --git a/library/core/src/num/flt2dec/strategy/grisu.rs b/library/core/src/num/flt2dec/strategy/grisu.rs index d3bbb0934e0ff..67c43f0219600 100644 --- a/library/core/src/num/flt2dec/strategy/grisu.rs +++ b/library/core/src/num/flt2dec/strategy/grisu.rs @@ -7,9 +7,10 @@ use crate::mem::MaybeUninit; use crate::num::diy_float::Fp; -use crate::num::flt2dec::{Decoded, MAX_SIG_DIGITS, round_up}; +use crate::num::flt2dec::decoder::Decoded64; +use crate::num::flt2dec::{SHORT_DIGITS_MAX, round_up}; -// see the comments in `format_shortest_opt` for the rationale. +// see the comments in `format_short` for the rationale. #[doc(hidden)] pub const ALPHA: i16 = -60; #[doc(hidden)] @@ -117,14 +118,14 @@ pub const CACHED_POW10_FIRST_E: i16 = -1087; pub const CACHED_POW10_LAST_E: i16 = 1039; #[doc(hidden)] -pub fn cached_power(alpha: i16, gamma: i16) -> (i16, Fp) { +pub fn cached_power(alpha: i16, gamma: i16) -> (isize, Fp) { let offset = CACHED_POW10_FIRST_E as i32; let range = (CACHED_POW10.len() as i32) - 1; let domain = (CACHED_POW10_LAST_E - CACHED_POW10_FIRST_E) as i32; let idx = ((gamma as i32) - offset) * range / domain; let (f, e, k) = CACHED_POW10[idx as usize]; debug_assert!(alpha <= e && e <= gamma); - (k, Fp { f, e }) + (k as isize, Fp { f, e }) } /// Given `x > 0`, returns `(k, 10^k)` such that `10^k <= x < 10^(k+1)`. @@ -159,25 +160,23 @@ pub fn max_pow10_no_more_than(x: u32) -> (u8, u32) { } } -/// The shortest mode implementation for Grisu. -/// +/// Implementation of the short mode for Grisu. /// It returns `None` when it would return an inexact representation otherwise. -pub fn format_shortest_opt<'a>( - d: &Decoded, - buf: &'a mut [MaybeUninit], -) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> { +pub fn format_short<'a>( + d: &Decoded64, + buf: &'a mut [MaybeUninit; SHORT_DIGITS_MAX], +) -> Option<(/*digits*/ &'a str, /*pow10*/ isize)> { assert!(d.mant > 0); assert!(d.minus > 0); assert!(d.plus > 0); assert!(d.mant.checked_add(d.plus).is_some()); assert!(d.mant.checked_sub(d.minus).is_some()); - assert!(buf.len() >= MAX_SIG_DIGITS); assert!(d.mant + d.plus < (1 << 61)); // we need at least three bits of additional precision // start with the normalized values with the shared exponent - let plus = Fp { f: d.mant + d.plus, e: d.exp }.normalize(); - let minus = Fp { f: d.mant - d.minus, e: d.exp }.normalize_to(plus.e); - let v = Fp { f: d.mant, e: d.exp }.normalize_to(plus.e); + let plus = Fp { f: d.mant + d.plus, e: d.exp as i16 }.normalize(); + let minus = Fp { f: d.mant - d.minus, e: d.exp as i16 }.normalize_to(plus.e); + let v = Fp { f: d.mant, e: d.exp as i16 }.normalize_to(plus.e); // find any `cached = 10^minusk` such that `ALPHA <= minusk + plus.e + 64 <= GAMMA`. // since `plus` is normalized, this means `2^(62 + ALPHA) <= plus * cached < 2^(64 + GAMMA)`; @@ -238,7 +237,7 @@ pub fn format_shortest_opt<'a>( let (max_kappa, max_ten_kappa) = max_pow10_no_more_than(plus1int); let mut i = 0; - let exp = max_kappa as i16 - minusk + 1; + let pow10 = max_kappa as isize - minusk + 1; // Theorem 6.2: if `k` is the greatest integer s.t. `0 <= y mod 10^k <= y - x`, // then `V = floor(y / 10^k) * 10^k` is in `[x, y]` and one of the shortest @@ -273,16 +272,18 @@ pub fn format_shortest_opt<'a>( if plus1rem < delta1 { // `plus1 % 10^kappa < delta1 = plus1 - minus1`; we've found the correct `kappa`. let ten_kappa = (ten_kappa as u64) << e; // scale 10^kappa back to the shared exponent - return round_and_weed( - // SAFETY: we initialized that memory above. - unsafe { buf[..i].assume_init_mut() }, - exp, - plus1rem, - delta1, - plus1 - v.f, - ten_kappa, - 1, - ); + // SAFETY: Wrote only ASCII digits up until i. + return unsafe { + round_and_weed( + buf[..i].assume_init_mut(), + pow10, + plus1rem, + delta1, + plus1 - v.f, + ten_kappa, + 1, + ) + }; } // break the loop when we have rendered all integral digits. @@ -322,16 +323,18 @@ pub fn format_shortest_opt<'a>( if r < threshold { let ten_kappa = 1 << e; // implicit divisor - return round_and_weed( - // SAFETY: we initialized that memory above. - unsafe { buf[..i].assume_init_mut() }, - exp, - r, - threshold, - (plus1 - v.f) * ulp, - ten_kappa, - ulp, - ); + // SAFETY: Wrote only ASCII digits up until i. + return unsafe { + round_and_weed( + buf[..i].assume_init_mut(), + pow10, + r, + threshold, + (plus1 - v.f) * ulp, + ten_kappa, + ulp, + ) + }; } // restore invariants @@ -354,15 +357,17 @@ pub fn format_shortest_opt<'a>( // - `plus1v = (plus1 - v) * k` (and also, `threshold > plus1v` from prior invariants) // - `ten_kappa = 10^kappa * k` // - `ulp = 2^-e * k` - fn round_and_weed( + // + // SAFETY: The `buf` must contain ASCII exclusively. + unsafe fn round_and_weed( buf: &mut [u8], - exp: i16, + pow10: isize, remainder: u64, threshold: u64, plus1v: u64, ten_kappa: u64, ulp: u64, - ) -> Option<(&[u8], i16)> { + ) -> Option<(&str, isize)> { assert!(!buf.is_empty()); // produce two approximations to `v` (actually `plus1 - v`) within 1.5 ulps. @@ -444,41 +449,29 @@ pub fn format_shortest_opt<'a>( // this is too liberal, though, so we reject any `w(n)` not between `plus0` and `minus0`, // i.e., `plus1 - plus1w(n) <= minus0` or `plus1 - plus1w(n) >= plus0`. we utilize the facts // that `threshold = plus1 - minus1` and `plus1 - plus0 = minus0 - minus1 = 2 ulp`. - if 2 * ulp <= plus1w && plus1w <= threshold - 4 * ulp { Some((buf, exp)) } else { None } - } -} - -/// The shortest mode implementation for Grisu with Dragon fallback. -/// -/// This should be used for most cases. -pub fn format_shortest<'a>( - d: &Decoded, - buf: &'a mut [MaybeUninit], -) -> (/*digits*/ &'a [u8], /*exp*/ i16) { - use crate::num::flt2dec::strategy::dragon::format_shortest as fallback; - // SAFETY: The borrow checker is not smart enough to let us use `buf` - // in the second branch, so we launder the lifetime here. But we only re-use - // `buf` if `format_shortest_opt` returned `None` so this is okay. - match format_shortest_opt(d, unsafe { &mut *(buf as *mut _) }) { - Some(ret) => ret, - None => fallback(d, buf), + if 2 * ulp <= plus1w && plus1w <= threshold - 4 * ulp { + // SAFETY: Caller guarantees all ASCII. + let digits = unsafe { str::from_utf8_unchecked(buf) }; + Some((digits, pow10)) + } else { + None + } } } -/// The exact and fixed mode implementation for Grisu. -/// +/// Implementation of the fixed mode for Grisu. /// It returns `None` when it would return an inexact representation otherwise. -pub fn format_exact_opt<'a>( - d: &Decoded, +pub fn format_fixed<'a>( + d: &Decoded64, buf: &'a mut [MaybeUninit], - limit: i16, -) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> { + resolution: isize, +) -> Option<(/*digits*/ &'a str, /*pow10*/ isize)> { assert!(d.mant > 0); assert!(d.mant < (1 << 61)); // we need at least three bits of additional precision assert!(!buf.is_empty()); // normalize and scale `v`. - let v = Fp { f: d.mant, e: d.exp }.normalize(); + let v = Fp { f: d.mant, e: d.exp as i16 }.normalize(); let (minusk, cached) = cached_power(ALPHA - v.e - 64, GAMMA - v.e - 64); let v = v.mul(cached); @@ -521,12 +514,12 @@ pub fn format_exact_opt<'a>( let (max_kappa, max_ten_kappa) = max_pow10_no_more_than(vint); let mut i = 0; - let exp = max_kappa as i16 - minusk + 1; + let pow10 = max_kappa as isize - minusk + 1; // if we are working with the last-digit limitation, we need to shorten the buffer // before the actual rendering in order to avoid double rounding. // note that we have to enlarge the buffer again when rounding up happens! - let len = if exp <= limit { + let len = if pow10 <= resolution { // oops, we cannot even produce *one* digit. // this is possible when, say, we've got something like 9.5 and it's being rounded to 10. // @@ -538,10 +531,18 @@ pub fn format_exact_opt<'a>( // // SAFETY: `len=0`, so the obligation of having initialized this memory is trivial. return unsafe { - possibly_round(buf, 0, exp, limit, v.f / 10, (max_ten_kappa as u64) << e, err << e) + possibly_round( + buf, + 0, + pow10, + resolution, + v.f / 10, + (max_ten_kappa as u64) << e, + err << e, + ) }; - } else if ((exp as i32 - limit as i32) as usize) < buf.len() { - (exp - limit) as usize + } else if ((pow10 - resolution) as usize) < buf.len() { + (pow10 - resolution) as usize } else { buf.len() }; @@ -549,7 +550,7 @@ pub fn format_exact_opt<'a>( // render integral parts. // the error is entirely fractional, so we don't need to check it in this part. - let mut kappa = max_kappa as i16; + let mut kappa = max_kappa as isize; let mut ten_kappa = max_ten_kappa; // 10^kappa let mut remainder = vint; // digits yet to be rendered loop { @@ -571,7 +572,7 @@ pub fn format_exact_opt<'a>( let vrem = ((r as u64) << e) + vfrac; // == (v % 10^kappa) * 2^e // SAFETY: we have initialized `len` many bytes. return unsafe { - possibly_round(buf, len, exp, limit, vrem, (ten_kappa as u64) << e, err << e) + possibly_round(buf, len, pow10, resolution, vrem, (ten_kappa as u64) << e, err << e) }; } @@ -623,7 +624,7 @@ pub fn format_exact_opt<'a>( // is the buffer full? run the rounding pass with the remainder. if i == len { // SAFETY: we have initialized `len` many bytes. - return unsafe { possibly_round(buf, len, exp, limit, r, 1 << e, err) }; + return unsafe { possibly_round(buf, len, pow10, resolution, r, 1 << e, err) }; } // restore invariants @@ -644,16 +645,16 @@ pub fn format_exact_opt<'a>( // - `ten_kappa = 10^kappa * k` // - `ulp = 2^-e * k` // - // SAFETY: the first `len` bytes of `buf` must be initialized. + // SAFETY: The first `len` bytes of `buf` must be initialized with ASCII exclusively. unsafe fn possibly_round( buf: &mut [MaybeUninit], mut len: usize, - mut exp: i16, - limit: i16, + mut pow10: isize, + resolution: isize, remainder: u64, ten_kappa: u64, ulp: u64, - ) -> Option<(&[u8], i16)> { + ) -> Option<(&str, isize)> { debug_assert!(remainder < ten_kappa); // 10^kappa @@ -712,8 +713,11 @@ pub fn format_exact_opt<'a>( // we've already verified that `ulp < 10^kappa / 2`, so as long as // `10^kappa` did not overflow after all, the second check is fine. if ten_kappa - remainder > remainder && ten_kappa - 2 * remainder >= 2 * ulp { - // SAFETY: our caller initialized that memory. - return Some((unsafe { buf[..len].assume_init_ref() }, exp)); + // SAFETY: Caller guarantees write up to len. + let written = unsafe { buf[..len].assume_init_ref() }; + // SAFETY: Caller guarantees all ASCII. + let digits = unsafe { str::from_utf8_unchecked(written) }; + return Some((digits, pow10)); } // :<------- remainder ------>| : @@ -734,21 +738,24 @@ pub fn format_exact_opt<'a>( // as `10^kappa` is never zero). also note that `remainder - ulp <= 10^kappa`, // so the second check does not overflow. if remainder > ulp && ten_kappa - (remainder - ulp) <= remainder - ulp { - if let Some(c) = - // SAFETY: our caller must have initialized that memory. - round_up(unsafe { buf[..len].assume_init_mut() }) - { + // SAFETY: Caller guarantees write up to len. + let written = unsafe { buf[..len].assume_init_mut() }; + if let Some(c) = round_up(written) { // only add an additional digit when we've been requested the fixed precision. // we also need to check that, if the original buffer was empty, - // the additional digit can only be added when `exp == limit` (edge case). - exp += 1; - if exp > limit && len < buf.len() { + // the additional digit can only be added when `pow10 == resolution` (edge case). + pow10 += 1; + if pow10 > resolution && len < buf.len() { buf[len] = MaybeUninit::new(c); len += 1; } } - // SAFETY: we and our caller initialized that memory. - return Some((unsafe { buf[..len].assume_init_ref() }, exp)); + // SAFETY: Any `len` addition by `round_up` is written too. + let total = unsafe { buf[..len].assume_init_ref() }; + // SAFETY: Caller guarantees all ASCII. + // Any addition from `round_up` is an ASCII digit. + let digits = unsafe { str::from_utf8_unchecked(total) }; + return Some((digits, pow10)); } // otherwise we are doomed (i.e., some values between `v - 1 ulp` and `v + 1 ulp` are @@ -756,21 +763,3 @@ pub fn format_exact_opt<'a>( None } } - -/// The exact and fixed mode implementation for Grisu with Dragon fallback. -/// -/// This should be used for most cases. -pub fn format_exact<'a>( - d: &Decoded, - buf: &'a mut [MaybeUninit], - limit: i16, -) -> (/*digits*/ &'a [u8], /*exp*/ i16) { - use crate::num::flt2dec::strategy::dragon::format_exact as fallback; - // SAFETY: The borrow checker is not smart enough to let us use `buf` - // in the second branch, so we launder the lifetime here. But we only re-use - // `buf` if `format_exact_opt` returned `None` so this is okay. - match format_exact_opt(d, unsafe { &mut *(buf as *mut _) }, limit) { - Some(ret) => ret, - None => fallback(d, buf, limit), - } -} diff --git a/library/core/src/num/fmt.rs b/library/core/src/num/fmt.rs deleted file mode 100644 index 0e4b2844d8192..0000000000000 --- a/library/core/src/num/fmt.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Shared utilities used by both float and integer formatting. -#![doc(hidden)] -#![unstable( - feature = "numfmt", - reason = "internal routines only exposed for testing", - issue = "none" -)] - -/// Formatted parts. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Part<'a> { - /// Given number of zero digits. - Zero(usize), - /// A literal number up to 5 digits. - Num(u16), - /// A verbatim copy of given bytes. - Copy(&'a [u8]), -} - -impl<'a> Part<'a> { - /// Returns the exact byte length of given part. - pub fn len(&self) -> usize { - match *self { - Part::Zero(nzeroes) => nzeroes, - Part::Num(v) => v.checked_ilog10().unwrap_or_default() as usize + 1, - Part::Copy(buf) => buf.len(), - } - } - - /// Writes a part into the supplied buffer. - /// Returns the number of written bytes, or `None` if the buffer is not enough. - /// (It may still leave partially written bytes in the buffer; do not rely on that.) - pub fn write(&self, out: &mut [u8]) -> Option { - let len = self.len(); - if out.len() >= len { - match *self { - Part::Zero(nzeroes) => { - for c in &mut out[..nzeroes] { - *c = b'0'; - } - } - Part::Num(mut v) => { - for c in out[..len].iter_mut().rev() { - *c = b'0' + (v % 10) as u8; - v /= 10; - } - } - Part::Copy(buf) => { - out[..buf.len()].copy_from_slice(buf); - } - } - Some(len) - } else { - None - } - } -} - -/// Formatted result containing one or more parts. -/// This can be written to the byte buffer or converted to the allocated string. -#[allow(missing_debug_implementations)] -#[derive(Clone)] -pub struct Formatted<'a> { - /// A byte slice representing a sign, either `""`, `"-"` or `"+"`. - pub sign: &'static str, - /// Formatted parts to be rendered after a sign and optional zero padding. - pub parts: &'a [Part<'a>], -} - -impl<'a> Formatted<'a> { - /// Returns the exact byte length of combined formatted result. - pub fn len(&self) -> usize { - self.sign.len() + self.parts.iter().map(|part| part.len()).sum::() - } - - /// Writes all formatted parts into the supplied buffer. - /// Returns the number of written bytes, or `None` if the buffer is not enough. - /// (It may still leave partially written bytes in the buffer; do not rely on that.) - pub fn write(&self, out: &mut [u8]) -> Option { - out.get_mut(..self.sign.len())?.copy_from_slice(self.sign.as_bytes()); - - let mut written = self.sign.len(); - for part in self.parts { - let len = part.write(&mut out[written..])?; - written += len; - } - Some(written) - } -} diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 35141dfeb3a6d..c61c0ec9440e2 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -36,7 +36,6 @@ pub mod dec2flt; pub mod diy_float; #[cfg(not(no_fp_fmt_parse))] pub mod flt2dec; -pub mod fmt; #[macro_use] mod int_macros; // import int_impl! diff --git a/library/coretests/benches/num/flt2dec/mod.rs b/library/coretests/benches/num/flt2dec/mod.rs index 428d0bbbbfbfa..541cd21d988b3 100644 --- a/library/coretests/benches/num/flt2dec/mod.rs +++ b/library/coretests/benches/num/flt2dec/mod.rs @@ -3,18 +3,10 @@ mod strategy { mod grisu; } -use core::num::flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; use std::io::Write; use test::{Bencher, black_box}; -pub fn decode_finite(v: T) -> Decoded { - match decode(v).1 { - FullDecoded::Finite(decoded) => decoded, - full_decoded => panic!("expected finite, got {full_decoded:?} instead"), - } -} - #[bench] fn bench_small_shortest(b: &mut Bencher) { let mut buf = Vec::with_capacity(20); diff --git a/library/coretests/benches/num/flt2dec/strategy/dragon.rs b/library/coretests/benches/num/flt2dec/strategy/dragon.rs index 4526697140365..362814aaa1a63 100644 --- a/library/coretests/benches/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/benches/num/flt2dec/strategy/dragon.rs @@ -1,76 +1,78 @@ +use core::num::flt2dec::SHORT_DIGITS_MAX; +use core::num::flt2dec::decoder::decode_f64; use core::num::flt2dec::strategy::dragon::*; use std::mem::MaybeUninit; -use super::super::*; +use test::{Bencher, black_box}; #[bench] -fn bench_small_shortest(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); - let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; +fn bench_small_short(b: &mut Bencher) { + let decoded = decode_f64(3.141592); + let mut buf = [MaybeUninit::new(0); SHORT_DIGITS_MAX]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf); }); } #[bench] -fn bench_big_shortest(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); - let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; +fn bench_big_short(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); + let mut buf = [MaybeUninit::new(0); SHORT_DIGITS_MAX]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf); }); } #[bench] -fn bench_small_exact_3(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); +fn bench_small_fixed_3(b: &mut Bencher) { + let decoded = decode_f64(3.141592); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_big_exact_3(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); +fn bench_big_fixed_3(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_small_exact_12(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); +fn bench_small_fixed_12(b: &mut Bencher) { + let decoded = decode_f64(3.141592); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_big_exact_12(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); +fn bench_big_fixed_12(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_small_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); +fn bench_small_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(3.141592); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_big_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); +fn bench_big_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } diff --git a/library/coretests/benches/num/flt2dec/strategy/grisu.rs b/library/coretests/benches/num/flt2dec/strategy/grisu.rs index d20f9b02f7e74..511906a9e0a02 100644 --- a/library/coretests/benches/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/benches/num/flt2dec/strategy/grisu.rs @@ -1,110 +1,105 @@ +use core::num::flt2dec::SHORT_DIGITS_MAX; +use core::num::flt2dec::decoder::decode_f64; use core::num::flt2dec::strategy::grisu::*; use std::mem::MaybeUninit; -use super::super::*; - -pub fn decode_finite(v: T) -> Decoded { - match decode(v).1 { - FullDecoded::Finite(decoded) => decoded, - full_decoded => panic!("expected finite, got {full_decoded:?} instead"), - } -} +use test::{Bencher, black_box}; #[bench] -fn bench_small_shortest(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); - let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; +fn bench_small_short(b: &mut Bencher) { + let decoded = decode_f64(3.141592); + let mut buf = [MaybeUninit::new(0); SHORT_DIGITS_MAX]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf); }); } #[bench] -fn bench_big_shortest(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); - let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; +fn bench_big_short(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); + let mut buf = [MaybeUninit::new(0); SHORT_DIGITS_MAX]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf); }); } #[bench] -fn bench_small_exact_3(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); +fn bench_small_fixed_3(b: &mut Bencher) { + let decoded = decode_f64(3.141592); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_big_exact_3(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); +fn bench_big_fixed_3(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_small_exact_12(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); +fn bench_small_fixed_12(b: &mut Bencher) { + let decoded = decode_f64(3.141592); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_big_exact_12(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); +fn bench_big_fixed_12(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_small_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); +fn bench_small_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(3.141592); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_big_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); +fn bench_big_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(f64::MAX); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_one_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(1.0); +fn bench_one_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(1.0); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_trailing_zero_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(250.000000000000000000000000); +fn bench_trailing_zero_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(250.000000000000000000000000); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } #[bench] -fn bench_halfway_point_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(1.00000000000000011102230246251565404236316680908203125); +fn bench_halfway_point_fixed_inf(b: &mut Bencher) { + let decoded = decode_f64(1.00000000000000011102230246251565404236316680908203125); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, isize::MIN); }); } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 0387b442562db..5e1cc162e3aa3 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -80,6 +80,7 @@ #![feature(iterator_try_reduce)] #![feature(layout_for_ptr)] #![feature(lazy_get)] +#![feature(macro_metavar_expr_concat)] #![feature(maybe_uninit_fill)] #![feature(maybe_uninit_uninit_array_transpose)] #![feature(min_specialization)] @@ -87,7 +88,6 @@ #![feature(new_range_api)] #![feature(next_index)] #![feature(non_exhaustive_omitted_patterns_lint)] -#![feature(numfmt)] #![feature(one_sided_range)] #![feature(option_reduce)] #![feature(pattern)] diff --git a/library/coretests/tests/num/flt2dec/estimator.rs b/library/coretests/tests/num/flt2dec/estimator.rs index f53282611f686..3e60746fc4907 100644 --- a/library/coretests/tests/num/flt2dec/estimator.rs +++ b/library/coretests/tests/num/flt2dec/estimator.rs @@ -4,61 +4,49 @@ use crate::num::ldexp_f64; #[test] fn test_estimate_scaling_factor() { - macro_rules! assert_almost_eq { - ($actual:expr, $expected:expr) => {{ - let actual = $actual; - let expected = $expected; - println!( - "{} - {} = {} - {} = {}", - stringify!($expected), - stringify!($actual), - expected, - actual, - expected - actual - ); - assert!( - expected == actual || expected == actual + 1, - "expected {}, actual {}", - expected, - actual - ); - }}; + fn assert_estimate(mant: u64, exp: isize, want: isize) { + let got = estimate_scaling_factor(mant, exp); + let tolerate = want - 1; + assert!( + got == want || got == tolerate, + "mant {mant} and exp {exp} got {got}, want {want} or tolerate {tolerate}", + ); } - assert_almost_eq!(estimate_scaling_factor(1, 0), 0); - assert_almost_eq!(estimate_scaling_factor(2, 0), 1); - assert_almost_eq!(estimate_scaling_factor(10, 0), 1); - assert_almost_eq!(estimate_scaling_factor(11, 0), 2); - assert_almost_eq!(estimate_scaling_factor(100, 0), 2); - assert_almost_eq!(estimate_scaling_factor(101, 0), 3); - assert_almost_eq!(estimate_scaling_factor(10000000000000000000, 0), 19); - assert_almost_eq!(estimate_scaling_factor(10000000000000000001, 0), 20); + assert_estimate(1, 0, 0); + assert_estimate(2, 0, 1); + assert_estimate(10, 0, 1); + assert_estimate(11, 0, 2); + assert_estimate(100, 0, 2); + assert_estimate(101, 0, 3); + assert_estimate(10000000000000000000, 0, 19); + assert_estimate(10000000000000000001, 0, 20); // 1/2^20 = 0.00000095367... - assert_almost_eq!(estimate_scaling_factor(1 * 1048576 / 1000000, -20), -6); - assert_almost_eq!(estimate_scaling_factor(1 * 1048576 / 1000000 + 1, -20), -5); - assert_almost_eq!(estimate_scaling_factor(10 * 1048576 / 1000000, -20), -5); - assert_almost_eq!(estimate_scaling_factor(10 * 1048576 / 1000000 + 1, -20), -4); - assert_almost_eq!(estimate_scaling_factor(100 * 1048576 / 1000000, -20), -4); - assert_almost_eq!(estimate_scaling_factor(100 * 1048576 / 1000000 + 1, -20), -3); - assert_almost_eq!(estimate_scaling_factor(1048575, -20), 0); - assert_almost_eq!(estimate_scaling_factor(1048576, -20), 0); - assert_almost_eq!(estimate_scaling_factor(1048577, -20), 1); - assert_almost_eq!(estimate_scaling_factor(10485759999999999999, -20), 13); - assert_almost_eq!(estimate_scaling_factor(10485760000000000000, -20), 13); - assert_almost_eq!(estimate_scaling_factor(10485760000000000001, -20), 14); + assert_estimate(1 * 1048576 / 1000000, -20, -6); + assert_estimate(1 * 1048576 / 1000000 + 1, -20, -5); + assert_estimate(10 * 1048576 / 1000000, -20, -5); + assert_estimate(10 * 1048576 / 1000000 + 1, -20, -4); + assert_estimate(100 * 1048576 / 1000000, -20, -4); + assert_estimate(100 * 1048576 / 1000000 + 1, -20, -3); + assert_estimate(1048575, -20, 0); + assert_estimate(1048576, -20, 0); + assert_estimate(1048577, -20, 1); + assert_estimate(10485759999999999999, -20, 13); + assert_estimate(10485760000000000000, -20, 13); + assert_estimate(10485760000000000001, -20, 14); // extreme values: // 2^-1074 = 4.94065... * 10^-324 // (2^53-1) * 2^971 = 1.79763... * 10^308 - assert_almost_eq!(estimate_scaling_factor(1, -1074), -323); - assert_almost_eq!(estimate_scaling_factor(0x1fffffffffffff, 971), 309); + assert_estimate(1, -1074, -323); + assert_estimate(0x1fffffffffffff, 971, 309); // Miri is too slow let step = if cfg!(miri) { 37 } else { 1 }; for i in (-1074..972).step_by(step) { - let expected = ldexp_f64(1.0, i).log10().ceil(); - assert_almost_eq!(estimate_scaling_factor(1, i as i16), expected as i16); + let want = ldexp_f64(1.0, i).log10().ceil(); + assert_estimate(1, i as isize, want as isize); } } diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index 4e73bd1f12ee9..7314143972d8f 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -1,10 +1,7 @@ -use core::num::flt2dec::{ - DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, Sign, decode, round_up, to_exact_exp_str, - to_exact_fixed_str, to_shortest_exp_str, to_shortest_str, -}; -use core::num::fmt::{Formatted, Part}; +use core::num::flt2dec::decoder::{Decoded64, decode_f16, decode_f32, decode_f64}; +use core::num::flt2dec::strategy::{dragon, grisu}; use std::mem::MaybeUninit; -use std::{fmt, str}; +use std::str; use crate::num::{ldexp_f32, ldexp_f64}; @@ -15,205 +12,130 @@ mod strategy { } mod random; -pub fn decode_finite(v: T) -> Decoded { - match decode(v).1 { - FullDecoded::Finite(decoded) => decoded, - full_decoded => panic!("expected finite, got {full_decoded:?} instead for {v:?}"), +fn try_short(label: &str, dec: &Decoded64, want_digits: &str, want_exp: isize) { + let want = (want_digits, want_exp); + let mut buf = [MaybeUninit::::uninit(); 17]; + + let got = dragon::format_short(dec, &mut buf); + assert_eq!(got, want, "format short of {label} with dragon"); + + if let Some(got) = grisu::format_short(dec, &mut buf) { + assert_eq!(got, want, "format short of {label} with grisu"); } } -macro_rules! check_shortest { - ($f:ident($v:expr) => $buf:expr, $exp:expr) => ( - check_shortest!($f($v) => $buf, $exp; - "shortest mismatch for v={v}: actual {actual:?}, expected {expected:?}", - v = stringify!($v)) - ); - - ($f:ident{$($k:ident: $v:expr),+} => $buf:expr, $exp:expr) => ( - check_shortest!($f{$($k: $v),+} => $buf, $exp; - "shortest mismatch for {v:?}: actual {actual:?}, expected {expected:?}", - v = Decoded { $($k: $v),+ }) - ); +fn try_fixed_unlimited(label: &str, dec: &Decoded64, want_digits: &str, want_exp: isize) { + let want = (want_digits, want_exp); + let mut buf = [MaybeUninit::::uninit(); 1024]; + let sized_buf = &mut buf[..want_digits.len()]; - ($f:ident($v:expr) => $buf:expr, $exp:expr; $fmt:expr, $($key:ident = $val:expr),*) => ({ - let mut buf = [MaybeUninit::new(b'_'); MAX_SIG_DIGITS]; - let (buf, k) = $f(&decode_finite($v), &mut buf); - assert!((buf, k) == ($buf, $exp), - $fmt, actual = (str::from_utf8(buf).unwrap(), k), - expected = (str::from_utf8($buf).unwrap(), $exp), - $($key = $val),*); - }); - - ($f:ident{$($k:ident: $v:expr),+} => $buf:expr, $exp:expr; - $fmt:expr, $($key:ident = $val:expr),*) => ({ - let mut buf = [MaybeUninit::new(b'_'); MAX_SIG_DIGITS]; - let (buf, k) = $f(&Decoded { $($k: $v),+ }, &mut buf); - assert!((buf, k) == ($buf, $exp), - $fmt, actual = (str::from_utf8(buf).unwrap(), k), - expected = (str::from_utf8($buf).unwrap(), $exp), - $($key = $val),*); - }) -} + let got = dragon::format_fixed(dec, sized_buf, isize::MIN); + assert_eq!(got, want, "format fixed of {label} with dragon"); -macro_rules! try_exact { - ($f:ident($decoded:expr) => $buf:expr, $expected:expr, $expectedk:expr; - $fmt:expr, $($key:ident = $val:expr),*) => ({ - let (buf, k) = $f($decoded, &mut $buf[..$expected.len()], i16::MIN); - assert!((buf, k) == ($expected, $expectedk), - $fmt, actual = (str::from_utf8(buf).unwrap(), k), - expected = (str::from_utf8($expected).unwrap(), $expectedk), - $($key = $val),*); - }) + if let Some(got) = grisu::format_fixed(dec, sized_buf, isize::MIN) { + assert_eq!(got, want, "format fixed of {label} with grisu"); + } } -macro_rules! try_fixed { - ($f:ident($decoded:expr) => $buf:expr, $request:expr, $expected:expr, $expectedk:expr; - $fmt:expr, $($key:ident = $val:expr),*) => ({ - let (buf, k) = $f($decoded, &mut $buf[..], $request); - assert!((buf, k) == ($expected, $expectedk), - $fmt, actual = (str::from_utf8(buf).unwrap(), k), - expected = (str::from_utf8($expected).unwrap(), $expectedk), - $($key = $val),*); - }) +fn try_fixed( + label: &str, + dec: &Decoded64, + buf: &mut [std::mem::MaybeUninit; 1024], + limit: isize, + want_digits: &str, + want_exp: isize, +) { + let want = (want_digits, want_exp); + + let got = dragon::format_fixed(dec, buf, limit); + assert_eq!(got, want, "format fixed of {label}, limit {limit}, with dragon"); + + if let Some(got) = grisu::format_fixed(dec, buf, limit) { + assert_eq!(got, want, "format fixed of {label}, limit {limit}, with grisu"); + } } -fn check_exact(mut f: F, v: T, vstr: &str, expected: &[u8], expectedk: i16) -where - T: DecodableFloat, - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - // use a large enough buffer +fn check_fixed(label: &str, dec: &Decoded64, want_digits: &str, want_exp: isize) { + try_fixed_unlimited(label, &dec, want_digits, want_exp); let mut buf = [MaybeUninit::new(b'_'); 1024]; - let mut expected_ = [b'_'; 1024]; - - let decoded = decode_finite(v); - let cut = expected.iter().position(|&c| c == b' '); - - // check significant digits - for i in 1..cut.unwrap_or(expected.len() - 1) { - expected_[..i].copy_from_slice(&expected[..i]); - let mut expectedk_ = expectedk; - if expected[i] >= b'5' { - // check if this is a rounding-to-even case. - // we avoid rounding ...x5000... (with infinite zeroes) to ...(x+1) when x is even. - if !(i + 1 < expected.len() - && expected[i - 1] & 1 == 0 - && expected[i] == b'5' - && expected[i + 1] == b' ') - { - // if this returns true, expected_[..i] is all `9`s and being rounded up. - // we should always return `100..00` (`i` digits) instead, since that's - // what we can came up with `i` digits anyway. `round_up` assumes that - // the adjustment to the length is done by caller, which we simply ignore. - if let Some(_) = round_up(&mut expected_[..i]) { - expectedk_ += 1; - } - } - } - - try_exact!(f(&decoded) => &mut buf, &expected_[..i], expectedk_; - "exact sigdigit mismatch for v={v}, i={i}: \ - actual {actual:?}, expected {expected:?}", - v = vstr, i = i); - try_fixed!(f(&decoded) => &mut buf, expectedk_ - i as i16, &expected_[..i], expectedk_; - "fixed sigdigit mismatch for v={v}, i={i}: \ - actual {actual:?}, expected {expected:?}", - v = vstr, i = i); - } + try_fixed(label, &dec, &mut buf, want_exp - want_digits.len() as isize, want_digits, want_exp); // check exact rounding for zero- and negative-width cases - let start; - if expected[0] > b'5' { - try_fixed!(f(&decoded) => &mut buf, expectedk, b"1", expectedk + 1; - "zero-width rounding-up mismatch for v={v}: \ - actual {actual:?}, expected {expected:?}", - v = vstr); - start = 1; + let start = if want_digits.chars().next().unwrap() > '5' { + try_fixed(label, &dec, &mut buf, want_exp, "1", want_exp + 1); + 1 } else { - start = 0; - } + 0 + }; for i in start..-10 { - try_fixed!(f(&decoded) => &mut buf, expectedk - i, b"", expectedk; - "rounding-down mismatch for v={v}, i={i}: \ - actual {actual:?}, expected {expected:?}", - v = vstr, i = -i); + try_fixed(label, &dec, &mut buf, want_exp - i, "", want_exp); } +} - // check infinite zero digits - if let Some(cut) = cut { - for i in cut..expected.len() - 1 { - expected_[..cut].copy_from_slice(&expected[..cut]); - for c in &mut expected_[cut..i] { - *c = b'0'; - } - - try_exact!(f(&decoded) => &mut buf, &expected_[..i], expectedk; - "exact infzero mismatch for v={v}, i={i}: \ - actual {actual:?}, expected {expected:?}", - v = vstr, i = i); - try_fixed!(f(&decoded) => &mut buf, expectedk - i as i16, &expected_[..i], expectedk; - "fixed infzero mismatch for v={v}, i={i}: \ - actual {actual:?}, expected {expected:?}", - v = vstr, i = i); +macro_rules! check_types { + ($($T:ident)*) => { + $( + + macro_rules! ${concat(check_short_, $T)} { + ($v:expr => $want_digits:expr, $want_exp:expr) => { + assert!(($v as $T).is_finite()); + let dec = ${concat(decode_, $T)}($v); + try_short(stringify!($v), &dec, $want_digits, $want_exp); + }; } - } -} -trait TestableFloat: DecodableFloat + fmt::Display { - /// Returns `x * 2^exp`. Almost same to `std::{f32,f64}::ldexp`. - /// This is used for testing. - fn ldexpi(f: i64, exp: isize) -> Self; -} + macro_rules! ${concat(check_fixed_, $T)} { + ($v:expr => $want_digits:expr, $want_exp:expr) => { + assert!(($v as $T).is_finite()); + let dec = ${concat(decode_, $T)}($v); + check_fixed(stringify!($v), &dec, $want_digits, $want_exp); + }; + } -#[cfg(target_has_reliable_f16)] -impl TestableFloat for f16 { - fn ldexpi(f: i64, exp: isize) -> Self { - f as Self * (exp as Self).exp2() - } -} + #[allow(unused_macros)] + macro_rules! ${concat(check_fixed_infz_, $T)} { + ($v:expr => $want_digits:expr, $want_exp:expr) => { + assert!(($v as $T).is_finite()); + let dec = ${concat(decode_, $T)}($v); + let digits: &str = $want_digits; + check_fixed(stringify!($v), &dec, digits, $want_exp); + + let label = concat!(stringify!($v), " with zero trail"); + let mut buf = [MaybeUninit::::uninit(); 1024]; + + // Verify "infinite" zero digits. + let mut zero_trail = [b'0'; 1024]; + zero_trail[..digits.len()].copy_from_slice(digits.as_bytes()); + let with_zeroes = str::from_utf8(&zero_trail).unwrap(); + for ndig in (digits.len() + 1)..zero_trail.len() { + try_fixed(label, &dec, &mut buf, $want_exp - ndig as isize, &with_zeroes[..ndig], $want_exp); + } + }; + } -impl TestableFloat for f32 { - fn ldexpi(f: i64, exp: isize) -> Self { - f as Self * (exp as Self).exp2() - } -} + #[allow(unused_macros)] + macro_rules! ${concat(check_fixed_one_, $T)} { + ($x:expr, $e:expr => $want_digits:expr, $want_exp:expr) => { + let label = concat!(stringify!($x), " * 2^", stringify!($e), " as ", stringify!($T)); + let x: $T = $x; + let e: isize = $e; + let dec = ${concat(decode_, $T)}(x * (e as $T).exp2()); -impl TestableFloat for f64 { - fn ldexpi(f: i64, exp: isize) -> Self { - f as Self * (exp as Self).exp2() - } -} + try_fixed_unlimited(label, &dec, $want_digits, $want_exp); -fn check_exact_one(mut f: F, x: i64, e: isize, tstr: &str, expected: &[u8], expectedk: i16) -where - T: TestableFloat, - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - // use a large enough buffer - let mut buf = [MaybeUninit::new(b'_'); 1024]; - let v: T = TestableFloat::ldexpi(x, e); - let decoded = decode_finite(v); - - try_exact!(f(&decoded) => &mut buf, &expected, expectedk; - "exact mismatch for v={x}p{e}{t}: actual {actual:?}, expected {expected:?}", - x = x, e = e, t = tstr); - try_fixed!(f(&decoded) => &mut buf, expectedk - expected.len() as i16, &expected, expectedk; - "fixed mismatch for v={x}p{e}{t}: actual {actual:?}, expected {expected:?}", - x = x, e = e, t = tstr); -} + let limit = $want_exp - $want_digits.len() as isize; + let mut buf = [MaybeUninit::new(b'_'); 1024]; + try_fixed(label, &dec, &mut buf, limit, $want_digits, $want_exp); + }; + } -macro_rules! check_exact { - ($f:ident($v:expr) => $buf:expr, $exp:expr) => { - check_exact(|d, b, k| $f(d, b, k), $v, stringify!($v), $buf, $exp) - }; -} -macro_rules! check_exact_one { - ($f:ident($x:expr, $e:expr; $t:ty) => $buf:expr, $exp:expr) => { - check_exact_one::<_, $t>(|d, b, k| $f(d, b, k), $x, $e, stringify!($t), $buf, $exp) + )* }; } +check_types! { f16 f32 f64 } + // in the following comments, three numbers are spaced by 1 ulp apart, // and the second one is being formatted. // @@ -223,726 +145,607 @@ macro_rules! check_exact_one { // ftp://ftp.ee.lbl.gov/testbase-report.ps.Z // or https://www.icir.org/vern/papers/testbase-report.pdf +#[test] #[cfg(target_has_reliable_f16)] -pub fn f16_shortest_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +pub fn f16_short_sanity_test() { // 0.0999145507813 // 0.0999755859375 // 0.100036621094 - check_shortest!(f(0.1f16) => b"1", 0); + check_short_f16!(0.1 => "1", 0); // 0.3330078125 // 0.333251953125 (1/3 in the default rounding) // 0.33349609375 - check_shortest!(f(1.0f16/3.0) => b"3333", 0); + check_short_f16!(1.0/3.0 => "3333", 0); // 10^1 * 0.3138671875 // 10^1 * 0.3140625 // 10^1 * 0.3142578125 - check_shortest!(f(3.14f16) => b"314", 1); + check_short_f16!(3.14 => "314", 1); // 10^18 * 0.31415916243714048 // 10^18 * 0.314159196796878848 // 10^18 * 0.314159231156617216 - check_shortest!(f(3.1415e4f16) => b"3141", 5); + check_short_f16!(3.1415e4 => "3141", 5); // regression test for decoders // 10^2 * 0.31984375 // 10^2 * 0.32 // 10^2 * 0.3203125 - check_shortest!(f(crate::num::ldexp_f16(1.0, 5)) => b"32", 2); + check_short_f16!(crate::num::ldexp_f16(1.0, 5) => "32", 2); // 10^5 * 0.65472 // 10^5 * 0.65504 // 10^5 * 0.65536 - check_shortest!(f(f16::MAX) => b"655", 5); + check_short_f16!(f16::MAX => "655", 5); // 10^-4 * 0.60975551605224609375 // 10^-4 * 0.6103515625 // 10^-4 * 0.61094760894775390625 - check_shortest!(f(f16::MIN_POSITIVE) => b"6104", -4); + check_short_f16!(f16::MIN_POSITIVE => "6104", -4); // 10^-9 * 0 // 10^-9 * 0.59604644775390625 // 10^-8 * 0.11920928955078125 - let minf16 = crate::num::ldexp_f16(1.0, -24); - check_shortest!(f(minf16) => b"6", -7); + check_short_f16!(crate::num::ldexp_f16(1.0, -24) => "6", -7); } +#[test] #[cfg(target_has_reliable_f16)] -pub fn f16_exact_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - let minf16 = crate::num::ldexp_f16(1.0, -24); - - check_exact!(f(0.1f16) => b"999755859375 ", -1); - check_exact!(f(0.5f16) => b"5 ", 0); - check_exact!(f(1.0f16/3.0) => b"333251953125 ", 0); - check_exact!(f(3.141f16) => b"3140625 ", 1); - check_exact!(f(3.141e4f16) => b"31408 ", 5); - check_exact!(f(f16::MAX) => b"65504 ", 5); - check_exact!(f(f16::MIN_POSITIVE) => b"6103515625 ", -4); - check_exact!(f(minf16) => b"59604644775390625", -7); - - // FIXME(f16_f128): these should gain the check_exact_one tests like `f32` and `f64` have, +#[cfg_attr(miri, ignore)] // Miri is too slow +pub fn f16_exact_sanity_test() { + check_fixed_infz_f16!(0.1 => "999755859375", -1); + check_fixed_infz_f16!(0.5 => "5", 0); + check_fixed_infz_f16!(1.0/3.0 => "333251953125", 0); + check_fixed_infz_f16!(3.141 => "3140625", 1); + check_fixed_infz_f16!(3.141e4 => "31408", 5); + check_fixed_infz_f16!(f16::MAX => "65504", 5); + check_fixed_infz_f16!(f16::MIN_POSITIVE => "6103515625", -4); + check_fixed_f16!(crate::num::ldexp_f16(1.0, -24) => "59604644775390625", -7); + + // FIXME(f16_f128): these should gain the check_fixed_one tests like `f32` and `f64` have, // but these values are not easy to generate. The algorithm from the Paxon paper [1] needs // to be adapted to binary16. } -pub fn f32_shortest_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +#[test] +pub fn f32_short_sanity_test() { // 0.0999999940395355224609375 // 0.100000001490116119384765625 // 0.10000000894069671630859375 - check_shortest!(f(0.1f32) => b"1", 0); + check_short_f32!(0.1 => "1", 0); // 0.333333313465118408203125 // 0.3333333432674407958984375 (1/3 in the default rounding) // 0.33333337306976318359375 - check_shortest!(f(1.0f32/3.0) => b"33333334", 0); + check_short_f32!(1.0/3.0 => "33333334", 0); // 10^1 * 0.31415917873382568359375 // 10^1 * 0.31415920257568359375 // 10^1 * 0.31415922641754150390625 - check_shortest!(f(3.141592f32) => b"3141592", 1); + check_short_f32!(3.141592 => "3141592", 1); // 10^18 * 0.31415916243714048 // 10^18 * 0.314159196796878848 // 10^18 * 0.314159231156617216 - check_shortest!(f(3.141592e17f32) => b"3141592", 18); + check_short_f32!(3.141592e17 => "3141592", 18); // regression test for decoders // 10^8 * 0.3355443 // 10^8 * 0.33554432 // 10^8 * 0.33554436 - check_shortest!(f(ldexp_f32(1.0, 25)) => b"33554432", 8); + check_short_f32!(ldexp_f32(1.0, 25) => "33554432", 8); // 10^39 * 0.340282326356119256160033759537265639424 // 10^39 * 0.34028234663852885981170418348451692544 // 10^39 * 0.340282366920938463463374607431768211456 - check_shortest!(f(f32::MAX) => b"34028235", 39); + check_short_f32!(f32::MAX => "34028235", 39); // 10^-37 * 0.1175494210692441075487029444849287348827... // 10^-37 * 0.1175494350822287507968736537222245677818... // 10^-37 * 0.1175494490952133940450443629595204006810... - check_shortest!(f(f32::MIN_POSITIVE) => b"11754944", -37); + check_short_f32!(f32::MIN_POSITIVE => "11754944", -37); // 10^-44 * 0 // 10^-44 * 0.1401298464324817070923729583289916131280... // 10^-44 * 0.2802596928649634141847459166579832262560... - let minf32 = ldexp_f32(1.0, -149); - check_shortest!(f(minf32) => b"1", -44); + check_short_f32!(ldexp_f32(1.0, -149) => "1", -44); } -pub fn f32_exact_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - let minf32 = ldexp_f32(1.0, -149); - - check_exact!(f(0.1f32) => b"100000001490116119384765625 ", 0); - check_exact!(f(0.5f32) => b"5 ", 0); - check_exact!(f(1.0f32/3.0) => b"3333333432674407958984375 ", 0); - check_exact!(f(3.141592f32) => b"31415920257568359375 ", 1); - check_exact!(f(3.141592e17f32) => b"314159196796878848 ", 18); - check_exact!(f(f32::MAX) => b"34028234663852885981170418348451692544 ", 39); - check_exact!(f(f32::MIN_POSITIVE) => b"1175494350822287507968736537222245677818", -37); - check_exact!(f(minf32) => b"1401298464324817070923729583289916131280", -44); +#[test] +#[cfg_attr(miri, ignore)] // Miri is too slow +pub fn f32_exact_sanity_test() { + check_fixed_infz_f32!(0.1 => "100000001490116119384765625", 0); + check_fixed_infz_f32!(0.5 => "5", 0); + check_fixed_infz_f32!(1.0/3.0 => "3333333432674407958984375", 0); + check_fixed_infz_f32!(3.141592 => "31415920257568359375", 1); + check_fixed_infz_f32!(3.141592e17 => "314159196796878848", 18); + check_fixed_infz_f32!(f32::MAX => "34028234663852885981170418348451692544", 39); + check_fixed_f32!(f32::MIN_POSITIVE => "1175494350822287507968736537222245677819", -37); + check_fixed_f32!(ldexp_f32(1.0, -149) => "1401298464324817070923729583289916131280", -44); // [1], Table 16: Stress Inputs for Converting 24-bit Binary to Decimal, < 1/2 ULP - check_exact_one!(f(12676506, -102; f32) => b"2", -23); - check_exact_one!(f(12676506, -103; f32) => b"12", -23); - check_exact_one!(f(15445013, 86; f32) => b"119", 34); - check_exact_one!(f(13734123, -138; f32) => b"3941", -34); - check_exact_one!(f(12428269, -130; f32) => b"91308", -32); - check_exact_one!(f(15334037, -146; f32) => b"171900", -36); - check_exact_one!(f(11518287, -41; f32) => b"5237910", -5); - check_exact_one!(f(12584953, -145; f32) => b"28216440", -36); - check_exact_one!(f(15961084, -125; f32) => b"375243281", -30); - check_exact_one!(f(14915817, -146; f32) => b"1672120916", -36); - check_exact_one!(f(10845484, -102; f32) => b"21388945814", -23); - check_exact_one!(f(16431059, -61; f32) => b"712583594561", -11); + check_fixed_one_f32!(12676506.0, -102 => "2", -23); + check_fixed_one_f32!(12676506.0, -103 => "12", -23); + check_fixed_one_f32!(15445013.0, 86 => "119", 34); + check_fixed_one_f32!(13734123.0, -138 => "3941", -34); + check_fixed_one_f32!(12428269.0, -130 => "91308", -32); + check_fixed_one_f32!(15334037.0, -146 => "171900", -36); + check_fixed_one_f32!(11518287.0, -41 => "5237910", -5); + check_fixed_one_f32!(12584953.0, -145 => "28216440", -36); + check_fixed_one_f32!(15961084.0, -125 => "375243281", -30); + check_fixed_one_f32!(14915817.0, -146 => "1672120916", -36); + check_fixed_one_f32!(10845484.0, -102 => "21388945814", -23); + check_fixed_one_f32!(16431059.0, -61 => "712583594561", -11); // [1], Table 17: Stress Inputs for Converting 24-bit Binary to Decimal, > 1/2 ULP - check_exact_one!(f(16093626, 69; f32) => b"1", 29); - check_exact_one!(f( 9983778, 25; f32) => b"34", 15); - check_exact_one!(f(12745034, 104; f32) => b"259", 39); - check_exact_one!(f(12706553, 72; f32) => b"6001", 29); - check_exact_one!(f(11005028, 45; f32) => b"38721", 21); - check_exact_one!(f(15059547, 71; f32) => b"355584", 29); - check_exact_one!(f(16015691, -99; f32) => b"2526831", -22); - check_exact_one!(f( 8667859, 56; f32) => b"62458507", 24); - check_exact_one!(f(14855922, -82; f32) => b"307213267", -17); - check_exact_one!(f(14855922, -83; f32) => b"1536066333", -17); - check_exact_one!(f(10144164, -110; f32) => b"78147796834", -26); - check_exact_one!(f(13248074, 95; f32) => b"524810279937", 36); + check_fixed_one_f32!(16093626.0, 69 => "1", 29); + check_fixed_one_f32!( 9983778.0, 25 => "34", 15); + check_fixed_one_f32!(12745034.0, 104 => "259", 39); + check_fixed_one_f32!(12706553.0, 72 => "6001", 29); + check_fixed_one_f32!(11005028.0, 45 => "38721", 21); + check_fixed_one_f32!(15059547.0, 71 => "355584", 29); + check_fixed_one_f32!(16015691.0, -99 => "2526831", -22); + check_fixed_one_f32!( 8667859.0, 56 => "62458507", 24); + check_fixed_one_f32!(14855922.0, -82 => "307213267", -17); + check_fixed_one_f32!(14855922.0, -83 => "1536066333", -17); + check_fixed_one_f32!(10144164.0, -110 => "78147796834", -26); + check_fixed_one_f32!(13248074.0, 95 => "524810279937", 36); } -pub fn f64_shortest_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +#[test] +pub fn f64_short_sanity_test() { // 0.0999999999999999777955395074968691915273... // 0.1000000000000000055511151231257827021181... // 0.1000000000000000333066907387546962127089... - check_shortest!(f(0.1f64) => b"1", 0); + check_short_f64!(0.1 => "1", 0); // this example is explicitly mentioned in the paper. // 10^3 * 0.0999999999999999857891452847979962825775... // 10^3 * 0.1 (exact) // 10^3 * 0.1000000000000000142108547152020037174224... - check_shortest!(f(100.0f64) => b"1", 3); + check_short_f64!(100.0 => "1", 3); // 0.3333333333333332593184650249895639717578... // 0.3333333333333333148296162562473909929394... (1/3 in the default rounding) // 0.3333333333333333703407674875052180141210... - check_shortest!(f(1.0f64/3.0) => b"3333333333333333", 0); + check_short_f64!(1.0/3.0 => "3333333333333333", 0); // explicit test case for equally closest representations. // Dragon has its own tie-breaking rule; Grisu should fall back. // 10^1 * 0.1000007629394531027955395074968691915273... // 10^1 * 0.100000762939453125 (exact) // 10^1 * 0.1000007629394531472044604925031308084726... - check_shortest!(f(1.00000762939453125f64) => b"10000076293945313", 1); + check_short_f64!(1.00000762939453125 => "10000076293945313", 1); // 10^1 * 0.3141591999999999718085064159822650253772... // 10^1 * 0.3141592000000000162174274009885266423225... // 10^1 * 0.3141592000000000606263483859947882592678... - check_shortest!(f(3.141592f64) => b"3141592", 1); + check_short_f64!(3.141592 => "3141592", 1); // 10^18 * 0.314159199999999936 // 10^18 * 0.3141592 (exact) // 10^18 * 0.314159200000000064 - check_shortest!(f(3.141592e17f64) => b"3141592", 18); + check_short_f64!(3.141592e17 => "3141592", 18); // regression test for decoders // 10^20 * 0.18446744073709549568 // 10^20 * 0.18446744073709551616 // 10^20 * 0.18446744073709555712 - check_shortest!(f(ldexp_f64(1.0, 64)) => b"18446744073709552", 20); + check_short_f64!(ldexp_f64(1.0, 64) => "18446744073709552", 20); // pathological case: high = 10^23 (exact). tie breaking should always prefer that. // 10^24 * 0.099999999999999974834176 // 10^24 * 0.099999999999999991611392 // 10^24 * 0.100000000000000008388608 - check_shortest!(f(1.0e23f64) => b"1", 24); + check_short_f64!(1.0e23 => "1", 24); // 10^309 * 0.1797693134862315508561243283845062402343... // 10^309 * 0.1797693134862315708145274237317043567980... // 10^309 * 0.1797693134862315907729305190789024733617... - check_shortest!(f(f64::MAX) => b"17976931348623157", 309); + check_short_f64!(f64::MAX => "17976931348623157", 309); // 10^-307 * 0.2225073858507200889024586876085859887650... // 10^-307 * 0.2225073858507201383090232717332404064219... // 10^-307 * 0.2225073858507201877155878558578948240788... - check_shortest!(f(f64::MIN_POSITIVE) => b"22250738585072014", -307); + check_short_f64!(f64::MIN_POSITIVE => "22250738585072014", -307); // 10^-323 * 0 // 10^-323 * 0.4940656458412465441765687928682213723650... // 10^-323 * 0.9881312916824930883531375857364427447301... - let minf64 = ldexp_f64(1.0, -1074); - check_shortest!(f(minf64) => b"5", -323); + check_short_f64!(ldexp_f64(1.0, -1074) => "5", -323); } -pub fn f64_exact_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - let minf64 = ldexp_f64(1.0, -1074); - - check_exact!(f(0.1f64) => b"1000000000000000055511151231257827021181", 0); - check_exact!(f(0.45f64) => b"4500000000000000111022302462515654042363", 0); - check_exact!(f(0.5f64) => b"5 ", 0); - check_exact!(f(0.95f64) => b"9499999999999999555910790149937383830547", 0); - check_exact!(f(100.0f64) => b"1 ", 3); - check_exact!(f(999.5f64) => b"9995000000000000000000000000000000000000", 3); - check_exact!(f(1.0f64/3.0) => b"3333333333333333148296162562473909929394", 0); - check_exact!(f(3.141592f64) => b"3141592000000000162174274009885266423225", 1); - check_exact!(f(3.141592e17f64) => b"3141592 ", 18); - check_exact!(f(1.0e23f64) => b"99999999999999991611392 ", 23); - check_exact!(f(f64::MAX) => b"1797693134862315708145274237317043567980", 309); - check_exact!(f(f64::MIN_POSITIVE) => b"2225073858507201383090232717332404064219", -307); - check_exact!(f(minf64) => b"4940656458412465441765687928682213723650\ - 5980261432476442558568250067550727020875\ - 1865299836361635992379796564695445717730\ - 9266567103559397963987747960107818781263\ - 0071319031140452784581716784898210368871\ - 8636056998730723050006387409153564984387\ - 3124733972731696151400317153853980741262\ - 3856559117102665855668676818703956031062\ - 4931945271591492455329305456544401127480\ - 1297099995419319894090804165633245247571\ - 4786901472678015935523861155013480352649\ - 3472019379026810710749170333222684475333\ - 5720832431936092382893458368060106011506\ - 1698097530783422773183292479049825247307\ - 7637592724787465608477820373446969953364\ - 7017972677717585125660551199131504891101\ - 4510378627381672509558373897335989936648\ - 0994116420570263709027924276754456522908\ - 7538682506419718265533447265625 ", -323); +#[test] +// This test ends up running what I can only assume is some corner-ish case +// of the `exp2` library function, defined in whatever C runtime we're +// using. In VS 2013 this function apparently had a bug as this test fails +// when linked, but with VS 2015 the bug appears fixed as the test runs just +// fine. +// +// The bug seems to be a difference in return value of `exp2(-1057)`, where +// in VS 2013 it returns a double with the bit pattern 0x2 and in VS 2015 it +// returns 0x20000. +// +// For now just ignore this test entirely on MSVC as it's tested elsewhere +// anyway and we're not super interested in testing each platform's exp2 +// implementation. +#[cfg_attr(target_env = "msvc", ignore)] +#[cfg_attr(miri, ignore)] // Miri is too slow +pub fn f64_exact_sanity_test() { + check_fixed_f64!(0.1 => "1000000000000000055511151231257827021182", 0); + check_fixed_f64!(0.45 => "4500000000000000111022302462515654042363", 0); + check_fixed_infz_f64!(0.5 => "5", 0); + check_fixed_f64!(0.95 => "9499999999999999555910790149937383830547", 0); + check_fixed_infz_f64!(100.0 => "1", 3); + check_fixed_f64!(999.5 => "9995000000000000000000000000000000000000", 3); + check_fixed_f64!(1.0/3.0 => "3333333333333333148296162562473909929395", 0); + check_fixed_f64!(3.141592 => "3141592000000000162174274009885266423225", 1); + check_fixed_infz_f64!(3.141592e17 => "3141592", 18); + check_fixed_infz_f64!(1.0e23 => "99999999999999991611392", 23); + check_fixed_f64!(f64::MAX => "1797693134862315708145274237317043567981", 309); + check_fixed_f64!(f64::MIN_POSITIVE => "2225073858507201383090232717332404064219", -307); + + check_fixed_f64!(ldexp_f64(1.0, -1074) => + "4940656458412465441765687928682213723650\ + 5980261432476442558568250067550727020875\ + 1865299836361635992379796564695445717730\ + 9266567103559397963987747960107818781263\ + 0071319031140452784581716784898210368871\ + 8636056998730723050006387409153564984387\ + 3124733972731696151400317153853980741262\ + 3856559117102665855668676818703956031062\ + 4931945271591492455329305456544401127480\ + 1297099995419319894090804165633245247571\ + 4786901472678015935523861155013480352649\ + 3472019379026810710749170333222684475333\ + 5720832431936092382893458368060106011506\ + 1698097530783422773183292479049825247307\ + 7637592724787465608477820373446969953364\ + 7017972677717585125660551199131504891101\ + 4510378627381672509558373897335989936648\ + 0994116420570263709027924276754456522908\ + 7538682506419718265533447265625", -323); // [1], Table 3: Stress Inputs for Converting 53-bit Binary to Decimal, < 1/2 ULP - check_exact_one!(f(8511030020275656, -342; f64) => b"9", -87); - check_exact_one!(f(5201988407066741, -824; f64) => b"46", -232); - check_exact_one!(f(6406892948269899, 237; f64) => b"141", 88); - check_exact_one!(f(8431154198732492, 72; f64) => b"3981", 38); - check_exact_one!(f(6475049196144587, 99; f64) => b"41040", 46); - check_exact_one!(f(8274307542972842, 726; f64) => b"292084", 235); - check_exact_one!(f(5381065484265332, -456; f64) => b"2891946", -121); - check_exact_one!(f(6761728585499734, -1057; f64) => b"43787718", -302); - check_exact_one!(f(7976538478610756, 376; f64) => b"122770163", 130); - check_exact_one!(f(5982403858958067, 377; f64) => b"1841552452", 130); - check_exact_one!(f(5536995190630837, 93; f64) => b"54835744350", 44); - check_exact_one!(f(7225450889282194, 710; f64) => b"389190181146", 230); - check_exact_one!(f(7225450889282194, 709; f64) => b"1945950905732", 230); - check_exact_one!(f(8703372741147379, 117; f64) => b"14460958381605", 52); - check_exact_one!(f(8944262675275217, -1001; f64) => b"417367747458531", -285); - check_exact_one!(f(7459803696087692, -707; f64) => b"1107950772878888", -196); - check_exact_one!(f(6080469016670379, -381; f64) => b"12345501366327440", -98); - check_exact_one!(f(8385515147034757, 721; f64) => b"925031711960365024", 233); - check_exact_one!(f(7514216811389786, -828; f64) => b"4198047150284889840", -233); - check_exact_one!(f(8397297803260511, -345; f64) => b"11716315319786511046", -87); - check_exact_one!(f(6733459239310543, 202; f64) => b"432810072844612493629", 77); - check_exact_one!(f(8091450587292794, -473; f64) => b"3317710118160031081518", -126); + check_fixed_one_f64!(8511030020275656.0, -342 => "9", -87); + check_fixed_one_f64!(5201988407066741.0, -824 => "46", -232); + check_fixed_one_f64!(6406892948269899.0, 237 => "141", 88); + check_fixed_one_f64!(8431154198732492.0, 72 => "3981", 38); + check_fixed_one_f64!(6475049196144587.0, 99 => "41040", 46); + check_fixed_one_f64!(8274307542972842.0, 726 => "292084", 235); + check_fixed_one_f64!(5381065484265332.0, -456 => "2891946", -121); + check_fixed_one_f64!(6761728585499734.0, -1057 => "43787718", -302); + check_fixed_one_f64!(7976538478610756.0, 376 => "122770163", 130); + check_fixed_one_f64!(5982403858958067.0, 377 => "1841552452", 130); + check_fixed_one_f64!(5536995190630837.0, 93 => "54835744350", 44); + check_fixed_one_f64!(7225450889282194.0, 710 => "389190181146", 230); + check_fixed_one_f64!(7225450889282194.0, 709 => "1945950905732", 230); + check_fixed_one_f64!(8703372741147379.0, 117 => "14460958381605", 52); + check_fixed_one_f64!(8944262675275217.0, -1001 => "417367747458531", -285); + check_fixed_one_f64!(7459803696087692.0, -707 => "1107950772878888", -196); + check_fixed_one_f64!(6080469016670379.0, -381 => "12345501366327440", -98); + check_fixed_one_f64!(8385515147034757.0, 721 => "925031711960365024", 233); + check_fixed_one_f64!(7514216811389786.0, -828 => "4198047150284889840", -233); + check_fixed_one_f64!(8397297803260511.0, -345 => "11716315319786511046", -87); + check_fixed_one_f64!(6733459239310543.0, 202 => "432810072844612493629", 77); + check_fixed_one_f64!(8091450587292794.0, -473 => "3317710118160031081518", -126); // [1], Table 4: Stress Inputs for Converting 53-bit Binary to Decimal, > 1/2 ULP - check_exact_one!(f(6567258882077402, 952; f64) => b"3", 303); - check_exact_one!(f(6712731423444934, 535; f64) => b"76", 177); - check_exact_one!(f(6712731423444934, 534; f64) => b"378", 177); - check_exact_one!(f(5298405411573037, -957; f64) => b"4350", -272); - check_exact_one!(f(5137311167659507, -144; f64) => b"23037", -27); - check_exact_one!(f(6722280709661868, 363; f64) => b"126301", 126); - check_exact_one!(f(5344436398034927, -169; f64) => b"7142211", -35); - check_exact_one!(f(8369123604277281, -853; f64) => b"13934574", -240); - check_exact_one!(f(8995822108487663, -780; f64) => b"141463449", -218); - check_exact_one!(f(8942832835564782, -383; f64) => b"4539277920", -99); - check_exact_one!(f(8942832835564782, -384; f64) => b"22696389598", -99); - check_exact_one!(f(8942832835564782, -385; f64) => b"113481947988", -99); - check_exact_one!(f(6965949469487146, -249; f64) => b"7700366561890", -59); - check_exact_one!(f(6965949469487146, -250; f64) => b"38501832809448", -59); - check_exact_one!(f(6965949469487146, -251; f64) => b"192509164047238", -59); - check_exact_one!(f(7487252720986826, 548; f64) => b"6898586531774201", 181); - check_exact_one!(f(5592117679628511, 164; f64) => b"13076622631878654", 66); - check_exact_one!(f(8887055249355788, 665; f64) => b"136052020756121240", 217); - check_exact_one!(f(6994187472632449, 690; f64) => b"3592810217475959676", 224); - check_exact_one!(f(8797576579012143, 588; f64) => b"89125197712484551899", 193); - check_exact_one!(f(7363326733505337, 272; f64) => b"558769757362301140950", 98); - check_exact_one!(f(8549497411294502, -448; f64) => b"1176257830728540379990", -118); -} - -pub fn more_shortest_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - check_shortest!(f{mant: 99_999_999_999_999_999, minus: 1, plus: 1, - exp: 0, inclusive: true} => b"1", 18); - check_shortest!(f{mant: 99_999_999_999_999_999, minus: 1, plus: 1, - exp: 0, inclusive: false} => b"99999999999999999", 17); + check_fixed_one_f64!(6567258882077402.0, 952 => "3", 303); + check_fixed_one_f64!(6712731423444934.0, 535 => "76", 177); + check_fixed_one_f64!(6712731423444934.0, 534 => "378", 177); + check_fixed_one_f64!(5298405411573037.0, -957 => "4350", -272); + check_fixed_one_f64!(5137311167659507.0, -144 => "23037", -27); + check_fixed_one_f64!(6722280709661868.0, 363 => "126301", 126); + check_fixed_one_f64!(5344436398034927.0, -169 => "7142211", -35); + check_fixed_one_f64!(8369123604277281.0, -853 => "13934574", -240); + check_fixed_one_f64!(8995822108487663.0, -780 => "141463449", -218); + check_fixed_one_f64!(8942832835564782.0, -383 => "4539277920", -99); + check_fixed_one_f64!(8942832835564782.0, -384 => "22696389598", -99); + check_fixed_one_f64!(8942832835564782.0, -385 => "113481947988", -99); + check_fixed_one_f64!(6965949469487146.0, -249 => "7700366561890", -59); + check_fixed_one_f64!(6965949469487146.0, -250 => "38501832809448", -59); + check_fixed_one_f64!(6965949469487146.0, -251 => "192509164047238", -59); + check_fixed_one_f64!(7487252720986826.0, 548 => "6898586531774201", 181); + check_fixed_one_f64!(5592117679628511.0, 164 => "13076622631878654", 66); + check_fixed_one_f64!(8887055249355788.0, 665 => "136052020756121240", 217); + check_fixed_one_f64!(6994187472632449.0, 690 => "3592810217475959676", 224); + check_fixed_one_f64!(8797576579012143.0, 588 => "89125197712484551899", 193); + check_fixed_one_f64!(7363326733505337.0, 272 => "558769757362301140950", 98); + check_fixed_one_f64!(8549497411294502.0, -448 => "1176257830728540379990", -118); } -fn to_string_with_parts(mut f: F) -> String -where - F: for<'a> FnMut(&'a mut [MaybeUninit], &'a mut [MaybeUninit>]) -> Formatted<'a>, -{ - let mut buf = [MaybeUninit::new(0); 1024]; - let mut parts = [MaybeUninit::new(Part::Zero(0)); 16]; - let formatted = f(&mut buf, &mut parts); - let mut ret = vec![0; formatted.len()]; - assert_eq!(formatted.write(&mut ret), Some(ret.len())); - String::from_utf8(ret).unwrap() +#[test] +pub fn more_short_sanity_test() { + try_short( + "inclusive", + &Decoded64 { mant: 99_999_999_999_999_999, minus: 1, plus: 1, exp: 0, inclusive: true }, + "1", + 18, + ); + try_short( + "exclusive", + &Decoded64 { mant: 99_999_999_999_999_999, minus: 1, plus: 1, exp: 0, inclusive: false }, + "99999999999999999", + 17, + ); } -pub fn to_shortest_str_test(mut f_: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - use core::num::flt2dec::Sign::*; - - fn to_string(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String - where - T: DecodableFloat, - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), - { - to_string_with_parts(|buf, parts| { - to_shortest_str(|d, b| f(d, b), v, sign, frac_digits, buf, parts) - }) - } - - let f = &mut f_; - - assert_eq!(to_string(f, 0.0, Minus, 0), "0"); - assert_eq!(to_string(f, 0.0, Minus, 0), "0"); - assert_eq!(to_string(f, 0.0, MinusPlus, 0), "+0"); - assert_eq!(to_string(f, -0.0, Minus, 0), "-0"); - assert_eq!(to_string(f, -0.0, MinusPlus, 0), "-0"); - assert_eq!(to_string(f, 0.0, Minus, 1), "0.0"); - assert_eq!(to_string(f, 0.0, Minus, 1), "0.0"); - assert_eq!(to_string(f, 0.0, MinusPlus, 1), "+0.0"); - assert_eq!(to_string(f, -0.0, Minus, 8), "-0.00000000"); - assert_eq!(to_string(f, -0.0, MinusPlus, 8), "-0.00000000"); - - assert_eq!(to_string(f, 1.0 / 0.0, Minus, 0), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, Minus, 0), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 0), "+inf"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, 0), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, 1), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 64), "NaN"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, 0), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, 1), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64), "-inf"); - - assert_eq!(to_string(f, 3.14, Minus, 0), "3.14"); - assert_eq!(to_string(f, 3.14, Minus, 0), "3.14"); - assert_eq!(to_string(f, 3.14, MinusPlus, 0), "+3.14"); - assert_eq!(to_string(f, -3.14, Minus, 0), "-3.14"); - assert_eq!(to_string(f, -3.14, Minus, 0), "-3.14"); - assert_eq!(to_string(f, -3.14, MinusPlus, 0), "-3.14"); - assert_eq!(to_string(f, 3.14, Minus, 1), "3.14"); - assert_eq!(to_string(f, 3.14, Minus, 2), "3.14"); - assert_eq!(to_string(f, 3.14, MinusPlus, 4), "+3.1400"); - assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000"); - assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000"); - assert_eq!(to_string(f, -3.14, MinusPlus, 8), "-3.14000000"); - - assert_eq!(to_string(f, 7.5e-11, Minus, 0), "0.000000000075"); - assert_eq!(to_string(f, 7.5e-11, Minus, 3), "0.000000000075"); - assert_eq!(to_string(f, 7.5e-11, Minus, 12), "0.000000000075"); - assert_eq!(to_string(f, 7.5e-11, Minus, 13), "0.0000000000750"); - - assert_eq!(to_string(f, 1.9971e20, Minus, 0), "199710000000000000000"); - assert_eq!(to_string(f, 1.9971e20, Minus, 1), "199710000000000000000.0"); - assert_eq!(to_string(f, 1.9971e20, Minus, 8), "199710000000000000000.00000000"); - +#[test] +pub fn to_short_str_test() { + assert_eq!(format!("{:}", 0.0), "0"); + assert_eq!(format!("{:+}", 0.0), "+0"); + assert_eq!(format!("{:?}", 0.0), "0.0"); + assert_eq!(format!("{:+?}", 0.0), "+0.0"); + + assert_eq!(format!("{:}", -0.0), "-0"); + assert_eq!(format!("{:+}", -0.0), "-0"); + assert_eq!(format!("{:?}", -0.0), "-0.0"); + assert_eq!(format!("{:+?}", -0.0), "-0.0"); + + assert_eq!(format!("{:}", 1.0 / 0.0), "inf"); + assert_eq!(format!("{:+}", 1.0 / 0.0), "+inf"); + assert_eq!(format!("{:?}", 1.0 / 0.0), "inf"); + assert_eq!(format!("{:+?}", 1.0 / 0.0), "+inf"); + + assert_eq!(format!("{:}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:+}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:?}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:+?}", -1.0 / 0.0), "-inf"); + + assert_eq!(format!("{:}", 0.0 / 0.0), "NaN"); + assert_eq!(format!("{:+}", 0.0 / 0.0), "NaN"); + assert_eq!(format!("{:?}", 0.0 / 0.0), "NaN"); + assert_eq!(format!("{:+?}", 0.0 / 0.0), "NaN"); + + assert_eq!(format!("{:}", 3.14), "3.14"); + assert_eq!(format!("{:+}", 3.14), "+3.14"); + assert_eq!(format!("{:?}", 3.14), "3.14"); + assert_eq!(format!("{:+?}", 3.14), "+3.14"); + + assert_eq!(format!("{:}", -3.14), "-3.14"); + assert_eq!(format!("{:+}", -3.14), "-3.14"); + assert_eq!(format!("{:?}", -3.14), "-3.14"); + assert_eq!(format!("{:+?}", -3.14), "-3.14"); + + assert_eq!(format!("{:}", 7.5e-4), "0.00075"); + assert_eq!(format!("{:+}", 7.5e-4), "+0.00075"); + assert_eq!(format!("{:?}", 7.5e-4), "0.00075"); + assert_eq!(format!("{:+?}", 7.5e-4), "+0.00075"); + + assert_eq!(format!("{:}", 1.9971e15), "1997100000000000"); + assert_eq!(format!("{:+}", 1.9971e15), "+1997100000000000"); + assert_eq!(format!("{:?}", -1.9971e15), "-1997100000000000.0"); + assert_eq!(format!("{:+?}", -1.9971e15), "-1997100000000000.0"); + + // f16 #[cfg(target_has_reliable_f16)] { - // f16 - assert_eq!(to_string(f, f16::MAX, Minus, 0), "65500"); - assert_eq!(to_string(f, f16::MAX, Minus, 1), "65500.0"); - assert_eq!(to_string(f, f16::MAX, Minus, 8), "65500.00000000"); - + assert_eq!(format!("{:}", f16::MAX), "65500"); let minf16 = crate::num::ldexp_f16(1.0, -24); - assert_eq!(to_string(f, minf16, Minus, 0), "0.00000006"); - assert_eq!(to_string(f, minf16, Minus, 8), "0.00000006"); - assert_eq!(to_string(f, minf16, Minus, 9), "0.000000060"); - } - - { - // f32 - assert_eq!(to_string(f, f32::MAX, Minus, 0), format!("34028235{:0>31}", "")); - assert_eq!(to_string(f, f32::MAX, Minus, 1), format!("34028235{:0>31}.0", "")); - assert_eq!(to_string(f, f32::MAX, Minus, 8), format!("34028235{:0>31}.00000000", "")); - - let minf32 = ldexp_f32(1.0, -149); - assert_eq!(to_string(f, minf32, Minus, 0), format!("0.{:0>44}1", "")); - assert_eq!(to_string(f, minf32, Minus, 45), format!("0.{:0>44}1", "")); - assert_eq!(to_string(f, minf32, Minus, 46), format!("0.{:0>44}10", "")); + assert_eq!(format!("{}", minf16), "0.00000006"); } - { - // f64 - assert_eq!(to_string(f, f64::MAX, Minus, 0), format!("17976931348623157{:0>292}", "")); - assert_eq!(to_string(f, f64::MAX, Minus, 1), format!("17976931348623157{:0>292}.0", "")); - assert_eq!( - to_string(f, f64::MAX, Minus, 8), - format!("17976931348623157{:0>292}.00000000", "") - ); - - let minf64 = ldexp_f64(1.0, -1074); - assert_eq!(to_string(f, minf64, Minus, 0), format!("0.{:0>323}5", "")); - assert_eq!(to_string(f, minf64, Minus, 324), format!("0.{:0>323}5", "")); - assert_eq!(to_string(f, minf64, Minus, 325), format!("0.{:0>323}50", "")); - } - - if cfg!(miri) { - // Miri is too slow - return; - } + // f32 + assert_eq!(format!("{}", f32::MAX), format!("34028235{:0>31}", "")); + let minf32 = ldexp_f32(1.0, -149); + assert_eq!(format!("{}", minf32), format!("0.{:0>44}1", "")); - // very large output - assert_eq!(to_string(f, 1.1, Minus, 50000), format!("1.1{:0>49999}", "")); + // f64 + assert_eq!(format!("{}", f64::MAX), format!("17976931348623157{:0>292}", "")); + let minf64 = ldexp_f64(1.0, -1074); + assert_eq!(format!("{}", minf64), format!("0.{:0>323}5", "")); } -pub fn to_shortest_exp_str_test(mut f_: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - use core::num::flt2dec::Sign::*; - - fn to_string(f: &mut F, v: T, sign: Sign, exp_bounds: (i16, i16), upper: bool) -> String - where - T: DecodableFloat, - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), - { - to_string_with_parts(|buf, parts| { - to_shortest_exp_str(|d, b| f(d, b), v, sign, exp_bounds, upper, buf, parts) - }) - } - - let f = &mut f_; - - assert_eq!(to_string(f, 0.0, Minus, (-4, 16), false), "0"); - assert_eq!(to_string(f, 0.0, Minus, (-4, 16), false), "0"); - assert_eq!(to_string(f, 0.0, MinusPlus, (-4, 16), false), "+0"); - assert_eq!(to_string(f, -0.0, Minus, (-4, 16), false), "-0"); - assert_eq!(to_string(f, -0.0, MinusPlus, (-4, 16), false), "-0"); - assert_eq!(to_string(f, 0.0, Minus, (0, 0), true), "0E0"); - assert_eq!(to_string(f, 0.0, Minus, (0, 0), false), "0e0"); - assert_eq!(to_string(f, 0.0, MinusPlus, (5, 9), false), "+0e0"); - assert_eq!(to_string(f, -0.0, Minus, (0, 0), true), "-0E0"); - assert_eq!(to_string(f, -0.0, MinusPlus, (5, 9), false), "-0e0"); - - assert_eq!(to_string(f, 1.0 / 0.0, Minus, (-4, 16), false), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, Minus, (-4, 16), true), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, (-4, 16), true), "+inf"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, (0, 0), false), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, (0, 0), true), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, (5, 9), true), "NaN"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, (0, 0), false), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, (0, 0), true), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, (5, 9), true), "-inf"); - - assert_eq!(to_string(f, 3.14, Minus, (-4, 16), false), "3.14"); - assert_eq!(to_string(f, 3.14, MinusPlus, (-4, 16), false), "+3.14"); - assert_eq!(to_string(f, -3.14, Minus, (-4, 16), false), "-3.14"); - assert_eq!(to_string(f, -3.14, MinusPlus, (-4, 16), false), "-3.14"); - assert_eq!(to_string(f, 3.14, Minus, (0, 0), true), "3.14E0"); - assert_eq!(to_string(f, 3.14, Minus, (0, 0), false), "3.14e0"); - assert_eq!(to_string(f, 3.14, MinusPlus, (5, 9), false), "+3.14e0"); - assert_eq!(to_string(f, -3.14, Minus, (0, 0), true), "-3.14E0"); - assert_eq!(to_string(f, -3.14, Minus, (0, 0), false), "-3.14e0"); - assert_eq!(to_string(f, -3.14, MinusPlus, (5, 9), false), "-3.14e0"); - - assert_eq!(to_string(f, 0.1, Minus, (-4, 16), false), "0.1"); - assert_eq!(to_string(f, 0.1, Minus, (-4, 16), false), "0.1"); - assert_eq!(to_string(f, 0.1, MinusPlus, (-4, 16), false), "+0.1"); - assert_eq!(to_string(f, -0.1, Minus, (-4, 16), false), "-0.1"); - assert_eq!(to_string(f, -0.1, MinusPlus, (-4, 16), false), "-0.1"); - assert_eq!(to_string(f, 0.1, Minus, (0, 0), true), "1E-1"); - assert_eq!(to_string(f, 0.1, Minus, (0, 0), false), "1e-1"); - assert_eq!(to_string(f, 0.1, MinusPlus, (5, 9), false), "+1e-1"); - assert_eq!(to_string(f, -0.1, Minus, (0, 0), true), "-1E-1"); - assert_eq!(to_string(f, -0.1, Minus, (0, 0), false), "-1e-1"); - assert_eq!(to_string(f, -0.1, MinusPlus, (5, 9), false), "-1e-1"); - - assert_eq!(to_string(f, 7.5e-11, Minus, (-4, 16), false), "7.5e-11"); - assert_eq!(to_string(f, 7.5e-11, Minus, (-11, 10), false), "0.000000000075"); - assert_eq!(to_string(f, 7.5e-11, Minus, (-10, 11), false), "7.5e-11"); - - assert_eq!(to_string(f, 1.9971e20, Minus, (-4, 16), false), "1.9971e20"); - assert_eq!(to_string(f, 1.9971e20, Minus, (-20, 21), false), "199710000000000000000"); - assert_eq!(to_string(f, 1.9971e20, Minus, (-21, 20), false), "1.9971e20"); +#[test] +pub fn to_short_exp_str_test() { + assert_eq!(format!("{:E}", 0.0), "0E0"); + assert_eq!(format!("{:+E}", 0.0), "+0E0"); + assert_eq!(format!("{:E}", -0.0), "-0E0"); + assert_eq!(format!("{:+E}", -0.0), "-0E0"); + + assert_eq!(format!("{:E}", 1.0 / 0.0), "inf"); + assert_eq!(format!("{:+E}", 1.0 / 0.0), "+inf"); + assert_eq!(format!("{:E}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:+E}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:E}", 0.0 / 0.0), "NaN"); + assert_eq!(format!("{:+E}", 0.0 / 0.0), "NaN"); + + assert_eq!(format!("{:E}", 3.14), "3.14E0"); + assert_eq!(format!("{:+E}", 3.14), "+3.14E0"); + assert_eq!(format!("{:E}", -3.14), "-3.14E0"); + assert_eq!(format!("{:+E}", -3.14), "-3.14E0"); + + assert_eq!(format!("{:E}", 0.1), "1E-1"); + assert_eq!(format!("{:+E}", 0.1), "+1E-1"); + assert_eq!(format!("{:E}", -0.1), "-1E-1"); + assert_eq!(format!("{:+E}", -0.1), "-1E-1"); + + assert_eq!(format!("{:E}", 7.5e-11), "7.5E-11"); + assert_eq!(format!("{:+E}", 7.5e-11), "+7.5E-11"); + assert_eq!(format!("{:E}", -7.5e-11), "-7.5E-11"); + assert_eq!(format!("{:+E}", -7.5e-11), "-7.5E-11"); + + assert_eq!(format!("{:E}", 1.9971e20), "1.9971E20"); + assert_eq!(format!("{:+E}", 1.9971e20), "+1.9971E20"); + assert_eq!(format!("{:E}", -1.9971e20), "-1.9971E20"); + assert_eq!(format!("{:+E}", -1.9971e20), "-1.9971E20"); // the true value of 1.0e23f64 is less than 10^23, but that shouldn't matter here - assert_eq!(to_string(f, 1.0e23, Minus, (22, 23), false), "1e23"); - assert_eq!(to_string(f, 1.0e23, Minus, (23, 24), false), "100000000000000000000000"); - assert_eq!(to_string(f, 1.0e23, Minus, (24, 25), false), "1e23"); + assert_eq!(format!("{:E}", 1.0e23), "1E23"); + assert_eq!(format!("{:+E}", 1.0e23), "+1E23"); + assert_eq!(format!("{:E}", -1.0e23), "-1E23"); + assert_eq!(format!("{:+E}", -1.0e23), "-1E23"); + // f16 #[cfg(target_has_reliable_f16)] { - // f16 - assert_eq!(to_string(f, f16::MAX, Minus, (-2, 2), false), "6.55e4"); - assert_eq!(to_string(f, f16::MAX, Minus, (-4, 4), false), "6.55e4"); - assert_eq!(to_string(f, f16::MAX, Minus, (-5, 5), false), "65500"); - + assert_eq!(format!("{:e}", f16::MAX), "6.55e4"); let minf16 = crate::num::ldexp_f16(1.0, -24); - assert_eq!(to_string(f, minf16, Minus, (-2, 2), false), "6e-8"); - assert_eq!(to_string(f, minf16, Minus, (-7, 7), false), "6e-8"); - assert_eq!(to_string(f, minf16, Minus, (-8, 8), false), "0.00000006"); + assert_eq!(format!("{:e}", minf16), "6e-8"); } - { - // f32 - assert_eq!(to_string(f, f32::MAX, Minus, (-4, 16), false), "3.4028235e38"); - assert_eq!(to_string(f, f32::MAX, Minus, (-39, 38), false), "3.4028235e38"); - assert_eq!(to_string(f, f32::MAX, Minus, (-38, 39), false), format!("34028235{:0>31}", "")); - - let minf32 = ldexp_f32(1.0, -149); - assert_eq!(to_string(f, minf32, Minus, (-4, 16), false), "1e-45"); - assert_eq!(to_string(f, minf32, Minus, (-44, 45), false), "1e-45"); - assert_eq!(to_string(f, minf32, Minus, (-45, 44), false), format!("0.{:0>44}1", "")); - } + // f32 + assert_eq!(format!("{:e}", f32::MAX), "3.4028235e38"); + let minf32 = ldexp_f32(1.0, -149); + assert_eq!(format!("{:e}", minf32), "1e-45"); - { - // f64 - assert_eq!(to_string(f, f64::MAX, Minus, (-4, 16), false), "1.7976931348623157e308"); - assert_eq!( - to_string(f, f64::MAX, Minus, (-308, 309), false), - format!("17976931348623157{:0>292}", "") - ); - assert_eq!(to_string(f, f64::MAX, Minus, (-309, 308), false), "1.7976931348623157e308"); - - let minf64 = ldexp_f64(1.0, -1074); - assert_eq!(to_string(f, minf64, Minus, (-4, 16), false), "5e-324"); - assert_eq!(to_string(f, minf64, Minus, (-324, 323), false), format!("0.{:0>323}5", "")); - assert_eq!(to_string(f, minf64, Minus, (-323, 324), false), "5e-324"); - } - assert_eq!(to_string(f, 1.1, Minus, (i16::MIN, i16::MAX), false), "1.1"); + // f64 + assert_eq!(format!("{:e}", f64::MAX), "1.7976931348623157e308"); + let minf64 = ldexp_f64(1.0, -1074); + assert_eq!(format!("{:e}", minf64), "5e-324"); } -pub fn to_exact_exp_str_test(mut f_: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - use core::num::flt2dec::Sign::*; - - fn to_string(f: &mut F, v: T, sign: Sign, ndigits: usize, upper: bool) -> String - where - T: DecodableFloat, - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], 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) - }) - } - - let f = &mut f_; - - assert_eq!(to_string(f, 0.0, Minus, 1, true), "0E0"); - assert_eq!(to_string(f, 0.0, Minus, 1, false), "0e0"); - assert_eq!(to_string(f, 0.0, MinusPlus, 1, false), "+0e0"); - assert_eq!(to_string(f, -0.0, Minus, 1, true), "-0E0"); - assert_eq!(to_string(f, -0.0, MinusPlus, 1, false), "-0e0"); - assert_eq!(to_string(f, 0.0, Minus, 2, true), "0.0E0"); - assert_eq!(to_string(f, 0.0, Minus, 2, false), "0.0e0"); - assert_eq!(to_string(f, 0.0, MinusPlus, 2, false), "+0.0e0"); - assert_eq!(to_string(f, -0.0, Minus, 8, false), "-0.0000000e0"); - assert_eq!(to_string(f, -0.0, MinusPlus, 8, false), "-0.0000000e0"); - - assert_eq!(to_string(f, 1.0 / 0.0, Minus, 1, false), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, Minus, 1, true), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 1, true), "+inf"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, 8, false), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, 8, true), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 8, true), "NaN"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, 64, false), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, 64, true), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64, true), "-inf"); - - assert_eq!(to_string(f, 3.14, Minus, 1, true), "3E0"); - assert_eq!(to_string(f, 3.14, Minus, 1, false), "3e0"); - assert_eq!(to_string(f, 3.14, MinusPlus, 1, false), "+3e0"); - assert_eq!(to_string(f, -3.14, Minus, 2, true), "-3.1E0"); - assert_eq!(to_string(f, -3.14, Minus, 2, false), "-3.1e0"); - assert_eq!(to_string(f, -3.14, MinusPlus, 2, false), "-3.1e0"); - assert_eq!(to_string(f, 3.14, Minus, 3, true), "3.14E0"); - assert_eq!(to_string(f, 3.14, Minus, 3, false), "3.14e0"); - assert_eq!(to_string(f, 3.14, MinusPlus, 3, false), "+3.14e0"); - assert_eq!(to_string(f, -3.14, Minus, 4, true), "-3.140E0"); - assert_eq!(to_string(f, -3.14, Minus, 4, false), "-3.140e0"); - assert_eq!(to_string(f, -3.14, MinusPlus, 4, false), "-3.140e0"); - - assert_eq!(to_string(f, 0.195, Minus, 1, false), "2e-1"); - assert_eq!(to_string(f, 0.195, Minus, 1, true), "2E-1"); - assert_eq!(to_string(f, 0.195, MinusPlus, 1, true), "+2E-1"); - assert_eq!(to_string(f, -0.195, Minus, 2, false), "-2.0e-1"); - assert_eq!(to_string(f, -0.195, Minus, 2, true), "-2.0E-1"); - assert_eq!(to_string(f, -0.195, MinusPlus, 2, true), "-2.0E-1"); - assert_eq!(to_string(f, 0.195, Minus, 3, false), "1.95e-1"); - assert_eq!(to_string(f, 0.195, Minus, 3, true), "1.95E-1"); - assert_eq!(to_string(f, 0.195, MinusPlus, 3, true), "+1.95E-1"); - assert_eq!(to_string(f, -0.195, Minus, 4, false), "-1.950e-1"); - assert_eq!(to_string(f, -0.195, Minus, 4, true), "-1.950E-1"); - assert_eq!(to_string(f, -0.195, MinusPlus, 4, true), "-1.950E-1"); - - assert_eq!(to_string(f, 9.5, Minus, 1, false), "1e1"); - assert_eq!(to_string(f, 9.5, Minus, 2, false), "9.5e0"); - assert_eq!(to_string(f, 9.5, Minus, 3, false), "9.50e0"); - assert_eq!(to_string(f, 9.5, Minus, 30, false), "9.50000000000000000000000000000e0"); - - assert_eq!(to_string(f, 1.0e25, Minus, 1, false), "1e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 2, false), "1.0e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 15, false), "1.00000000000000e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 16, false), "1.000000000000000e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 17, false), "1.0000000000000001e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 18, false), "1.00000000000000009e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 19, false), "1.000000000000000091e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 20, false), "1.0000000000000000906e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 21, false), "1.00000000000000009060e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 22, false), "1.000000000000000090597e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 23, false), "1.0000000000000000905970e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 24, false), "1.00000000000000009059697e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 25, false), "1.000000000000000090596966e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 26, false), "1.0000000000000000905969664e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 27, false), "1.00000000000000009059696640e25"); - assert_eq!(to_string(f, 1.0e25, Minus, 30, false), "1.00000000000000009059696640000e25"); - - assert_eq!(to_string(f, 1.0e-6, Minus, 1, false), "1e-6"); - assert_eq!(to_string(f, 1.0e-6, Minus, 2, false), "1.0e-6"); - assert_eq!(to_string(f, 1.0e-6, Minus, 16, false), "1.000000000000000e-6"); - assert_eq!(to_string(f, 1.0e-6, Minus, 17, false), "9.9999999999999995e-7"); - assert_eq!(to_string(f, 1.0e-6, Minus, 18, false), "9.99999999999999955e-7"); - assert_eq!(to_string(f, 1.0e-6, Minus, 19, false), "9.999999999999999547e-7"); - assert_eq!(to_string(f, 1.0e-6, Minus, 20, false), "9.9999999999999995475e-7"); - assert_eq!(to_string(f, 1.0e-6, Minus, 30, false), "9.99999999999999954748111825886e-7"); - assert_eq!( - to_string(f, 1.0e-6, Minus, 40, false), - "9.999999999999999547481118258862586856139e-7" - ); +#[test] +pub fn to_exact_exp_str_test() { + assert_eq!(format!("{:.0E}", 0.0), "0E0"); + assert_eq!(format!("{:+.0E}", 0.0), "+0E0"); + assert_eq!(format!("{:.0E}", -0.0), "-0E0"); + assert_eq!(format!("{:+.0E}", -0.0), "-0E0"); + + assert_eq!(format!("{:.1E}", 0.0), "0.0E0"); + assert_eq!(format!("{:+.1E}", 0.0), "+0.0E0"); + assert_eq!(format!("{:.1E}", -0.0), "-0.0E0"); + assert_eq!(format!("{:+.1E}", -0.0), "-0.0E0"); + + assert_eq!(format!("{:.8E}", 0.0), "0.00000000E0"); + assert_eq!(format!("{:+.8E}", 0.0), "+0.00000000E0"); + assert_eq!(format!("{:.8E}", -0.0), "-0.00000000E0"); + assert_eq!(format!("{:+.8E}", -0.0), "-0.00000000E0"); + + assert_eq!(format!("{:.0E}", 1.0 / 0.0), "inf"); + assert_eq!(format!("{:+.1E}", 1.0 / 0.0), "+inf"); + assert_eq!(format!("{:.2E}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:+.3E}", -1.0 / 0.0), "-inf"); + assert_eq!(format!("{:.4E}", 0.0 / 0.0), "NaN"); + assert_eq!(format!("{:+.5E}", 0.0 / 0.0), "NaN"); + + assert_eq!(format!("{:.0E}", 3.14), "3E0"); + assert_eq!(format!("{:+.0E}", 3.14), "+3E0"); + assert_eq!(format!("{:.0E}", -3.14), "-3E0"); + assert_eq!(format!("{:+.0E}", -3.14), "-3E0"); + + assert_eq!(format!("{:.1E}", 3.14), "3.1E0"); + assert_eq!(format!("{:+.1E}", 3.14), "+3.1E0"); + assert_eq!(format!("{:.1E}", -3.14), "-3.1E0"); + assert_eq!(format!("{:+.1E}", -3.14), "-3.1E0"); + + assert_eq!(format!("{:.2E}", 3.14), "3.14E0"); + assert_eq!(format!("{:+.2E}", 3.14), "+3.14E0"); + assert_eq!(format!("{:.2E}", -3.14), "-3.14E0"); + assert_eq!(format!("{:+.2E}", -3.14), "-3.14E0"); + + assert_eq!(format!("{:.3E}", 3.14), "3.140E0"); + assert_eq!(format!("{:+.3E}", 3.14), "+3.140E0"); + assert_eq!(format!("{:.3E}", -3.14), "-3.140E0"); + assert_eq!(format!("{:+.3E}", -3.14), "-3.140E0"); + + assert_eq!(format!("{:.0E}", 0.195), "2E-1"); + assert_eq!(format!("{:.1E}", 0.195), "2.0E-1"); + assert_eq!(format!("{:.2E}", 0.195), "1.95E-1"); + assert_eq!(format!("{:.3E}", 0.195), "1.950E-1"); + + assert_eq!(format!("{:.0E}", -0.195), "-2E-1"); + assert_eq!(format!("{:.1E}", -0.195), "-2.0E-1"); + assert_eq!(format!("{:.2E}", -0.195), "-1.95E-1"); + assert_eq!(format!("{:.3E}", -0.195), "-1.950E-1"); + + assert_eq!(format!("{:.0E}", 9.5), "1E1"); + assert_eq!(format!("{:.1E}", 9.5), "9.5E0"); + assert_eq!(format!("{:.2E}", 9.5), "9.50E0"); + + assert_eq!(format!("{:.0E}", -9.5), "-1E1"); + assert_eq!(format!("{:.1E}", -9.5), "-9.5E0"); + assert_eq!(format!("{:.2E}", -9.5), "-9.50E0"); + + assert_eq!(format!("{:.0e}", 1.0e25), "1e25"); + assert_eq!(format!("{:.1e}", 1.0e25), "1.0e25"); + assert_eq!(format!("{:.14e}", 1.0e25), "1.00000000000000e25"); + assert_eq!(format!("{:.15e}", 1.0e25), "1.000000000000000e25"); + assert_eq!(format!("{:.16e}", 1.0e25), "1.0000000000000001e25"); + assert_eq!(format!("{:.17e}", 1.0e25), "1.00000000000000009e25"); + assert_eq!(format!("{:.18e}", 1.0e25), "1.000000000000000091e25"); + assert_eq!(format!("{:.19e}", 1.0e25), "1.0000000000000000906e25"); + assert_eq!(format!("{:.20e}", 1.0e25), "1.00000000000000009060e25"); + assert_eq!(format!("{:.21e}", 1.0e25), "1.000000000000000090597e25"); + assert_eq!(format!("{:.22e}", 1.0e25), "1.0000000000000000905970e25"); + assert_eq!(format!("{:.23e}", 1.0e25), "1.00000000000000009059697e25"); + assert_eq!(format!("{:.24e}", 1.0e25), "1.000000000000000090596966e25"); + assert_eq!(format!("{:.25e}", 1.0e25), "1.0000000000000000905969664e25"); + assert_eq!(format!("{:.26e}", 1.0e25), "1.00000000000000009059696640e25"); + assert_eq!(format!("{:.29e}", 1.0e25), "1.00000000000000009059696640000e25"); + + assert_eq!(format!("{:.0e}", 1.0e-6), "1e-6"); + assert_eq!(format!("{:.1e}", 1.0e-6), "1.0e-6"); + assert_eq!(format!("{:.15e}", 1.0e-6), "1.000000000000000e-6"); + assert_eq!(format!("{:.16e}", 1.0e-6), "9.9999999999999995e-7"); + assert_eq!(format!("{:.17e}", 1.0e-6), "9.99999999999999955e-7"); + assert_eq!(format!("{:.18e}", 1.0e-6), "9.999999999999999547e-7"); + assert_eq!(format!("{:.19e}", 1.0e-6), "9.9999999999999995475e-7"); + assert_eq!(format!("{:.29e}", 1.0e-6), "9.99999999999999954748111825886e-7"); + assert_eq!(format!("{:.39e}", 1.0e-6), "9.999999999999999547481118258862586856139e-7"); assert_eq!( - to_string(f, 1.0e-6, Minus, 50, false), + format!("{:.49e}", 1.0e-6), "9.9999999999999995474811182588625868561393872369081e-7" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 60, false), + format!("{:.59e}", 1.0e-6), "9.99999999999999954748111825886258685613938723690807819366455e-7" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 70, false), + format!("{:.69e}", 1.0e-6), "9.999999999999999547481118258862586856139387236908078193664550781250000e-7" ); + // f16 #[cfg(target_has_reliable_f16)] { - assert_eq!(to_string(f, f16::MAX, Minus, 1, false), "7e4"); - assert_eq!(to_string(f, f16::MAX, Minus, 2, false), "6.6e4"); - assert_eq!(to_string(f, f16::MAX, Minus, 4, false), "6.550e4"); - assert_eq!(to_string(f, f16::MAX, Minus, 5, false), "6.5504e4"); - assert_eq!(to_string(f, f16::MAX, Minus, 6, false), "6.55040e4"); - assert_eq!(to_string(f, f16::MAX, Minus, 16, false), "6.550400000000000e4"); + assert_eq!(format!("{:.0e}", f16::MAX), "7e4"); + assert_eq!(format!("{:.1e}", f16::MAX), "6.6e4"); + assert_eq!(format!("{:.3e}", f16::MAX), "6.550e4"); + assert_eq!(format!("{:.4e}", f16::MAX), "6.5504e4"); + assert_eq!(format!("{:.5e}", f16::MAX), "6.55040e4"); + assert_eq!(format!("{:.15e}", f16::MAX), "6.550400000000000e4"); let minf16 = crate::num::ldexp_f16(1.0, -24); - assert_eq!(to_string(f, minf16, Minus, 1, false), "6e-8"); - assert_eq!(to_string(f, minf16, Minus, 2, false), "6.0e-8"); - assert_eq!(to_string(f, minf16, Minus, 4, false), "5.960e-8"); - assert_eq!(to_string(f, minf16, Minus, 8, false), "5.9604645e-8"); - assert_eq!(to_string(f, minf16, Minus, 16, false), "5.960464477539062e-8"); - assert_eq!(to_string(f, minf16, Minus, 17, false), "5.9604644775390625e-8"); - assert_eq!(to_string(f, minf16, Minus, 18, false), "5.96046447753906250e-8"); - assert_eq!(to_string(f, minf16, Minus, 24, false), "5.96046447753906250000000e-8"); + assert_eq!(format!("{:.0e}", minf16), "6e-8"); + assert_eq!(format!("{:.1e}", minf16), "6.0e-8"); + assert_eq!(format!("{:.3e}", minf16), "5.960e-8"); + assert_eq!(format!("{:.7e}", minf16), "5.9604645e-8"); + assert_eq!(format!("{:.15e}", minf16), "5.960464477539062e-8"); + assert_eq!(format!("{:.16e}", minf16), "5.9604644775390625e-8"); + assert_eq!(format!("{:.17e}", minf16), "5.96046447753906250e-8"); + assert_eq!(format!("{:.23e}", minf16), "5.96046447753906250000000e-8"); } - assert_eq!(to_string(f, f32::MAX, Minus, 1, false), "3e38"); - assert_eq!(to_string(f, f32::MAX, Minus, 2, false), "3.4e38"); - assert_eq!(to_string(f, f32::MAX, Minus, 4, false), "3.403e38"); - assert_eq!(to_string(f, f32::MAX, Minus, 8, false), "3.4028235e38"); - assert_eq!(to_string(f, f32::MAX, Minus, 16, false), "3.402823466385289e38"); - assert_eq!(to_string(f, f32::MAX, Minus, 32, false), "3.4028234663852885981170418348452e38"); + // f32 + assert_eq!(format!("{:.0e}", f32::MAX), "3e38"); + assert_eq!(format!("{:.1e}", f32::MAX), "3.4e38"); + assert_eq!(format!("{:.3e}", f32::MAX), "3.403e38"); + assert_eq!(format!("{:.7e}", f32::MAX), "3.4028235e38"); + assert_eq!(format!("{:.15e}", f32::MAX), "3.402823466385289e38"); + assert_eq!(format!("{:.31e}", f32::MAX), "3.4028234663852885981170418348452e38"); assert_eq!( - to_string(f, f32::MAX, Minus, 64, false), + format!("{:.63e}", f32::MAX), "3.402823466385288598117041834845169254400000000000000000000000000e38" ); let minf32 = ldexp_f32(1.0, -149); - assert_eq!(to_string(f, minf32, Minus, 1, false), "1e-45"); - assert_eq!(to_string(f, minf32, Minus, 2, false), "1.4e-45"); - assert_eq!(to_string(f, minf32, Minus, 4, false), "1.401e-45"); - assert_eq!(to_string(f, minf32, Minus, 8, false), "1.4012985e-45"); - assert_eq!(to_string(f, minf32, Minus, 16, false), "1.401298464324817e-45"); - assert_eq!(to_string(f, minf32, Minus, 32, false), "1.4012984643248170709237295832899e-45"); + assert_eq!(format!("{:.0e}", minf32), "1e-45"); + assert_eq!(format!("{:.1e}", minf32), "1.4e-45"); + assert_eq!(format!("{:.3e}", minf32), "1.401e-45"); + assert_eq!(format!("{:.7e}", minf32), "1.4012985e-45"); + assert_eq!(format!("{:.15e}", minf32), "1.401298464324817e-45"); + assert_eq!(format!("{:.31e}", minf32), "1.4012984643248170709237295832899e-45"); assert_eq!( - to_string(f, minf32, Minus, 64, false), + format!("{:.63e}", minf32), "1.401298464324817070923729583289916131280261941876515771757068284e-45" ); assert_eq!( - to_string(f, minf32, Minus, 128, false), + format!("{:.127e}", minf32), "1.401298464324817070923729583289916131280261941876515771757068283\ - 8897910826858606014866381883621215820312500000000000000000000000e-45" + 8897910826858606014866381883621215820312500000000000000000000000e-45" ); if cfg!(miri) { @@ -950,261 +753,219 @@ where return; } - assert_eq!(to_string(f, f64::MAX, Minus, 1, false), "2e308"); - assert_eq!(to_string(f, f64::MAX, Minus, 2, false), "1.8e308"); - assert_eq!(to_string(f, f64::MAX, Minus, 4, false), "1.798e308"); - assert_eq!(to_string(f, f64::MAX, Minus, 8, false), "1.7976931e308"); - assert_eq!(to_string(f, f64::MAX, Minus, 16, false), "1.797693134862316e308"); - assert_eq!(to_string(f, f64::MAX, Minus, 32, false), "1.7976931348623157081452742373170e308"); + // f64 + assert_eq!(format!("{:.0e}", f64::MAX), "2e308"); + assert_eq!(format!("{:.1e}", f64::MAX), "1.8e308"); + assert_eq!(format!("{:.3e}", f64::MAX), "1.798e308"); + assert_eq!(format!("{:.7e}", f64::MAX), "1.7976931e308"); + assert_eq!(format!("{:.15e}", f64::MAX), "1.797693134862316e308"); + assert_eq!(format!("{:.31e}", f64::MAX), "1.7976931348623157081452742373170e308"); assert_eq!( - to_string(f, f64::MAX, Minus, 64, false), + format!("{:.63e}", f64::MAX), "1.797693134862315708145274237317043567980705675258449965989174768e308" ); assert_eq!( - to_string(f, f64::MAX, Minus, 128, false), + format!("{:.127e}", f64::MAX), "1.797693134862315708145274237317043567980705675258449965989174768\ - 0315726078002853876058955863276687817154045895351438246423432133e308" + 0315726078002853876058955863276687817154045895351438246423432133e308" ); assert_eq!( - to_string(f, f64::MAX, Minus, 256, false), + format!("{:.255e}", f64::MAX), "1.797693134862315708145274237317043567980705675258449965989174768\ - 0315726078002853876058955863276687817154045895351438246423432132\ - 6889464182768467546703537516986049910576551282076245490090389328\ - 9440758685084551339423045832369032229481658085593321233482747978e308" + 0315726078002853876058955863276687817154045895351438246423432132\ + 6889464182768467546703537516986049910576551282076245490090389328\ + 9440758685084551339423045832369032229481658085593321233482747978e308" ); assert_eq!( - to_string(f, f64::MAX, Minus, 512, false), + format!("{:.511e}", f64::MAX), "1.797693134862315708145274237317043567980705675258449965989174768\ - 0315726078002853876058955863276687817154045895351438246423432132\ - 6889464182768467546703537516986049910576551282076245490090389328\ - 9440758685084551339423045832369032229481658085593321233482747978\ - 2620414472316873817718091929988125040402618412485836800000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000e308" + 0315726078002853876058955863276687817154045895351438246423432132\ + 6889464182768467546703537516986049910576551282076245490090389328\ + 9440758685084551339423045832369032229481658085593321233482747978\ + 2620414472316873817718091929988125040402618412485836800000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000e308" ); - // okay, this is becoming tough. fortunately for us, this is almost the worst case. let minf64 = ldexp_f64(1.0, -1074); - assert_eq!(to_string(f, minf64, Minus, 1, false), "5e-324"); - assert_eq!(to_string(f, minf64, Minus, 2, false), "4.9e-324"); - assert_eq!(to_string(f, minf64, Minus, 4, false), "4.941e-324"); - assert_eq!(to_string(f, minf64, Minus, 8, false), "4.9406565e-324"); - assert_eq!(to_string(f, minf64, Minus, 16, false), "4.940656458412465e-324"); - assert_eq!(to_string(f, minf64, Minus, 32, false), "4.9406564584124654417656879286822e-324"); + assert_eq!(format!("{:.0e}", minf64), "5e-324"); + assert_eq!(format!("{:.1e}", minf64), "4.9e-324"); + assert_eq!(format!("{:.3e}", minf64), "4.941e-324"); + assert_eq!(format!("{:.7e}", minf64), "4.9406565e-324"); + assert_eq!(format!("{:.15e}", minf64), "4.940656458412465e-324"); + assert_eq!(format!("{:.31e}", minf64), "4.9406564584124654417656879286822e-324"); assert_eq!( - to_string(f, minf64, Minus, 64, false), + format!("{:.63e}", minf64), "4.940656458412465441765687928682213723650598026143247644255856825e-324" ); assert_eq!( - to_string(f, minf64, Minus, 128, false), + format!("{:.127e}", minf64), "4.940656458412465441765687928682213723650598026143247644255856825\ - 0067550727020875186529983636163599237979656469544571773092665671e-324" + 0067550727020875186529983636163599237979656469544571773092665671e-324" ); assert_eq!( - to_string(f, minf64, Minus, 256, false), + format!("{:.255e}", minf64), "4.940656458412465441765687928682213723650598026143247644255856825\ - 0067550727020875186529983636163599237979656469544571773092665671\ - 0355939796398774796010781878126300713190311404527845817167848982\ - 1036887186360569987307230500063874091535649843873124733972731696e-324" + 0067550727020875186529983636163599237979656469544571773092665671\ + 0355939796398774796010781878126300713190311404527845817167848982\ + 1036887186360569987307230500063874091535649843873124733972731696e-324" ); assert_eq!( - to_string(f, minf64, Minus, 512, false), + format!("{:.511e}", minf64), "4.940656458412465441765687928682213723650598026143247644255856825\ - 0067550727020875186529983636163599237979656469544571773092665671\ - 0355939796398774796010781878126300713190311404527845817167848982\ - 1036887186360569987307230500063874091535649843873124733972731696\ - 1514003171538539807412623856559117102665855668676818703956031062\ - 4931945271591492455329305456544401127480129709999541931989409080\ - 4165633245247571478690147267801593552386115501348035264934720193\ - 7902681071074917033322268447533357208324319360923828934583680601e-324" + 0067550727020875186529983636163599237979656469544571773092665671\ + 0355939796398774796010781878126300713190311404527845817167848982\ + 1036887186360569987307230500063874091535649843873124733972731696\ + 1514003171538539807412623856559117102665855668676818703956031062\ + 4931945271591492455329305456544401127480129709999541931989409080\ + 4165633245247571478690147267801593552386115501348035264934720193\ + 7902681071074917033322268447533357208324319360923828934583680601e-324" ); assert_eq!( - to_string(f, minf64, Minus, 1024, false), + format!("{:.1023e}", minf64), "4.940656458412465441765687928682213723650598026143247644255856825\ - 0067550727020875186529983636163599237979656469544571773092665671\ - 0355939796398774796010781878126300713190311404527845817167848982\ - 1036887186360569987307230500063874091535649843873124733972731696\ - 1514003171538539807412623856559117102665855668676818703956031062\ - 4931945271591492455329305456544401127480129709999541931989409080\ - 4165633245247571478690147267801593552386115501348035264934720193\ - 7902681071074917033322268447533357208324319360923828934583680601\ - 0601150616980975307834227731832924790498252473077637592724787465\ - 6084778203734469699533647017972677717585125660551199131504891101\ - 4510378627381672509558373897335989936648099411642057026370902792\ - 4276754456522908753868250641971826553344726562500000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000e-324" + 0067550727020875186529983636163599237979656469544571773092665671\ + 0355939796398774796010781878126300713190311404527845817167848982\ + 1036887186360569987307230500063874091535649843873124733972731696\ + 1514003171538539807412623856559117102665855668676818703956031062\ + 4931945271591492455329305456544401127480129709999541931989409080\ + 4165633245247571478690147267801593552386115501348035264934720193\ + 7902681071074917033322268447533357208324319360923828934583680601\ + 0601150616980975307834227731832924790498252473077637592724787465\ + 6084778203734469699533647017972677717585125660551199131504891101\ + 4510378627381672509558373897335989936648099411642057026370902792\ + 4276754456522908753868250641971826553344726562500000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000e-324" ); // very large output - assert_eq!(to_string(f, 0.0, Minus, 50000, false), format!("0.{:0>49999}e0", "")); - assert_eq!(to_string(f, 1.0e1, Minus, 50000, false), format!("1.{:0>49999}e1", "")); - assert_eq!(to_string(f, 1.0e0, Minus, 50000, false), format!("1.{:0>49999}e0", "")); + assert_eq!(format!("{:.49999e}", 0.0), format!("0.{:0>49999}e0", "")); + assert_eq!(format!("{:.49999e}", 10.0), format!("1.{:0>49999}e1", "")); + assert_eq!(format!("{:.49999e}", 1.0), format!("1.{:0>49999}e0", "")); assert_eq!( - to_string(f, 1.0e-1, Minus, 50000, false), - format!( - "1.000000000000000055511151231257827021181583404541015625{:0>49945}\ - e-1", - "" - ) + format!("{:.49999e}", 0.1), + format!("1.000000000000000055511151231257827021181583404541015625{:0>49945}e-1", "",) ); assert_eq!( - to_string(f, 1.0e-20, Minus, 50000, false), + format!("{:.49999e}", 1.0e-20), format!( "9.999999999999999451532714542095716517295037027873924471077157760\ - 66783064379706047475337982177734375{:0>49901}e-21", - "" + 66783064379706047475337982177734375{:0>49901}e-21", + "", ) ); } -pub fn to_exact_fixed_str_test(mut f_: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), -{ - use core::num::flt2dec::Sign::*; - - fn to_string(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String - where - T: DecodableFloat, - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), - { - to_string_with_parts(|buf, parts| { - to_exact_fixed_str(|d, b, l| f(d, b, l), v, sign, frac_digits, buf, parts) - }) - } - - let f = &mut f_; - - assert_eq!(to_string(f, 0.0, Minus, 0), "0"); - assert_eq!(to_string(f, 0.0, MinusPlus, 0), "+0"); - assert_eq!(to_string(f, -0.0, Minus, 0), "-0"); - assert_eq!(to_string(f, -0.0, MinusPlus, 0), "-0"); - assert_eq!(to_string(f, 0.0, Minus, 1), "0.0"); - assert_eq!(to_string(f, 0.0, MinusPlus, 1), "+0.0"); - assert_eq!(to_string(f, -0.0, Minus, 8), "-0.00000000"); - assert_eq!(to_string(f, -0.0, MinusPlus, 8), "-0.00000000"); - - assert_eq!(to_string(f, 1.0 / 0.0, Minus, 0), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, Minus, 1), "inf"); - assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 64), "+inf"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, 0), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, Minus, 1), "NaN"); - assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 64), "NaN"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, 0), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, Minus, 1), "-inf"); - assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64), "-inf"); - - assert_eq!(to_string(f, 3.14, Minus, 0), "3"); - assert_eq!(to_string(f, 3.14, Minus, 0), "3"); - assert_eq!(to_string(f, 3.14, MinusPlus, 0), "+3"); - assert_eq!(to_string(f, -3.14, Minus, 0), "-3"); - assert_eq!(to_string(f, -3.14, Minus, 0), "-3"); - assert_eq!(to_string(f, -3.14, MinusPlus, 0), "-3"); - assert_eq!(to_string(f, 3.14, Minus, 1), "3.1"); - assert_eq!(to_string(f, 3.14, Minus, 2), "3.14"); - assert_eq!(to_string(f, 3.14, MinusPlus, 4), "+3.1400"); - assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000"); - assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000"); - assert_eq!(to_string(f, -3.14, MinusPlus, 8), "-3.14000000"); - - assert_eq!(to_string(f, 0.195, Minus, 0), "0"); - assert_eq!(to_string(f, 0.195, MinusPlus, 0), "+0"); - assert_eq!(to_string(f, -0.195, Minus, 0), "-0"); - assert_eq!(to_string(f, -0.195, Minus, 0), "-0"); - assert_eq!(to_string(f, -0.195, MinusPlus, 0), "-0"); - assert_eq!(to_string(f, 0.195, Minus, 1), "0.2"); - assert_eq!(to_string(f, 0.195, Minus, 2), "0.20"); - assert_eq!(to_string(f, 0.195, MinusPlus, 4), "+0.1950"); - assert_eq!(to_string(f, -0.195, Minus, 5), "-0.19500"); - assert_eq!(to_string(f, -0.195, Minus, 6), "-0.195000"); - assert_eq!(to_string(f, -0.195, MinusPlus, 8), "-0.19500000"); - - assert_eq!(to_string(f, 999.5, Minus, 0), "1000"); - assert_eq!(to_string(f, 999.5, Minus, 1), "999.5"); - assert_eq!(to_string(f, 999.5, Minus, 2), "999.50"); - assert_eq!(to_string(f, 999.5, Minus, 3), "999.500"); - assert_eq!(to_string(f, 999.5, Minus, 30), "999.500000000000000000000000000000"); - - assert_eq!(to_string(f, 0.5, Minus, 0), "0"); - assert_eq!(to_string(f, 0.5, Minus, 1), "0.5"); - assert_eq!(to_string(f, 0.5, Minus, 2), "0.50"); - assert_eq!(to_string(f, 0.5, Minus, 3), "0.500"); - - assert_eq!(to_string(f, 0.95, Minus, 0), "1"); - assert_eq!(to_string(f, 0.95, Minus, 1), "0.9"); // because it really is less than 0.95 - assert_eq!(to_string(f, 0.95, Minus, 2), "0.95"); - assert_eq!(to_string(f, 0.95, Minus, 3), "0.950"); - assert_eq!(to_string(f, 0.95, Minus, 10), "0.9500000000"); - assert_eq!(to_string(f, 0.95, Minus, 30), "0.949999999999999955591079014994"); - - assert_eq!(to_string(f, 0.095, Minus, 0), "0"); - assert_eq!(to_string(f, 0.095, Minus, 1), "0.1"); - assert_eq!(to_string(f, 0.095, Minus, 2), "0.10"); - assert_eq!(to_string(f, 0.095, Minus, 3), "0.095"); - assert_eq!(to_string(f, 0.095, Minus, 4), "0.0950"); - assert_eq!(to_string(f, 0.095, Minus, 10), "0.0950000000"); - assert_eq!(to_string(f, 0.095, Minus, 30), "0.095000000000000001110223024625"); - - assert_eq!(to_string(f, 0.0095, Minus, 0), "0"); - assert_eq!(to_string(f, 0.0095, Minus, 1), "0.0"); - assert_eq!(to_string(f, 0.0095, Minus, 2), "0.01"); - assert_eq!(to_string(f, 0.0095, Minus, 3), "0.009"); // really is less than 0.0095 - assert_eq!(to_string(f, 0.0095, Minus, 4), "0.0095"); - assert_eq!(to_string(f, 0.0095, Minus, 5), "0.00950"); - assert_eq!(to_string(f, 0.0095, Minus, 10), "0.0095000000"); - assert_eq!(to_string(f, 0.0095, Minus, 30), "0.009499999999999999764077607267"); - - assert_eq!(to_string(f, 7.5e-11, Minus, 0), "0"); - assert_eq!(to_string(f, 7.5e-11, Minus, 3), "0.000"); - assert_eq!(to_string(f, 7.5e-11, Minus, 10), "0.0000000001"); - assert_eq!(to_string(f, 7.5e-11, Minus, 11), "0.00000000007"); // ditto - assert_eq!(to_string(f, 7.5e-11, Minus, 12), "0.000000000075"); - assert_eq!(to_string(f, 7.5e-11, Minus, 13), "0.0000000000750"); - assert_eq!(to_string(f, 7.5e-11, Minus, 20), "0.00000000007500000000"); - assert_eq!(to_string(f, 7.5e-11, Minus, 30), "0.000000000074999999999999999501"); - - assert_eq!(to_string(f, 1.0e25, Minus, 0), "10000000000000000905969664"); - assert_eq!(to_string(f, 1.0e25, Minus, 1), "10000000000000000905969664.0"); - assert_eq!(to_string(f, 1.0e25, Minus, 3), "10000000000000000905969664.000"); - - assert_eq!(to_string(f, 1.0e-6, Minus, 0), "0"); - assert_eq!(to_string(f, 1.0e-6, Minus, 3), "0.000"); - assert_eq!(to_string(f, 1.0e-6, Minus, 6), "0.000001"); - assert_eq!(to_string(f, 1.0e-6, Minus, 9), "0.000001000"); - assert_eq!(to_string(f, 1.0e-6, Minus, 12), "0.000001000000"); - assert_eq!(to_string(f, 1.0e-6, Minus, 22), "0.0000010000000000000000"); - assert_eq!(to_string(f, 1.0e-6, Minus, 23), "0.00000099999999999999995"); - assert_eq!(to_string(f, 1.0e-6, Minus, 24), "0.000000999999999999999955"); - assert_eq!(to_string(f, 1.0e-6, Minus, 25), "0.0000009999999999999999547"); - assert_eq!(to_string(f, 1.0e-6, Minus, 35), "0.00000099999999999999995474811182589"); - assert_eq!(to_string(f, 1.0e-6, Minus, 45), "0.000000999999999999999954748111825886258685614"); +#[test] +pub fn to_exact_fixed_str_test() { + assert_eq!(format!("{:.0}", 3.14), "3"); + assert_eq!(format!("{:.1}", 3.14), "3.1"); + assert_eq!(format!("{:.2}", 3.14), "3.14"); + assert_eq!(format!("{:.3}", 3.14), "3.140"); + assert_eq!(format!("{:.4}", 3.14), "3.1400"); + + assert_eq!(format!("{:.0}", 0.195), "0"); + assert_eq!(format!("{:.1}", 0.195), "0.2"); + assert_eq!(format!("{:.2}", 0.195), "0.20"); + assert_eq!(format!("{:+.2}", 0.195), "+0.20"); + assert_eq!(format!("{:+.2}", -0.195), "-0.20"); + assert_eq!(format!("{:.2}", -0.195), "-0.20"); + assert_eq!(format!("{:.3}", 0.195), "0.195"); + assert_eq!(format!("{:+.3}", 0.195), "+0.195"); + assert_eq!(format!("{:+.3}", -0.195), "-0.195"); + assert_eq!(format!("{:.3}", -0.195), "-0.195"); + assert_eq!(format!("{:.4}", 0.195), "0.1950"); + assert_eq!(format!("{:+.4}", 0.195), "+0.1950"); + assert_eq!(format!("{:+.4}", -0.195), "-0.1950"); + assert_eq!(format!("{:.4}", -0.195), "-0.1950"); + + assert_eq!(format!("{:.0}", 999.5), "1000"); + assert_eq!(format!("{:.1}", 999.5), "999.5"); + assert_eq!(format!("{:.2}", 999.5), "999.50"); + assert_eq!(format!("{:.3}", 999.5), "999.500"); + assert_eq!(format!("{:.30}", 999.5), "999.500000000000000000000000000000"); + + assert_eq!(format!("{:.0}", 0.5), "0"); + assert_eq!(format!("{:.1}", 0.5), "0.5"); + assert_eq!(format!("{:.2}", 0.5), "0.50"); + assert_eq!(format!("{:.3}", 0.5), "0.500"); + + assert_eq!(format!("{:.0}", 0.95), "1"); + assert_eq!(format!("{:.1}", 0.95), "0.9"); // because it really is less than 0.95 + assert_eq!(format!("{:.2}", 0.95), "0.95"); + assert_eq!(format!("{:.3}", 0.95), "0.950"); + assert_eq!(format!("{:.10}", 0.95), "0.9500000000"); + assert_eq!(format!("{:.30}", 0.95), "0.949999999999999955591079014994"); + + assert_eq!(format!("{:.0}", 0.095), "0"); + assert_eq!(format!("{:.1}", 0.095), "0.1"); + assert_eq!(format!("{:.2}", 0.095), "0.10"); + assert_eq!(format!("{:.3}", 0.095), "0.095"); + assert_eq!(format!("{:.4}", 0.095), "0.0950"); + assert_eq!(format!("{:.10}", 0.095), "0.0950000000"); + assert_eq!(format!("{:.30}", 0.095), "0.095000000000000001110223024625"); + + assert_eq!(format!("{:.0}", 0.0095), "0"); + assert_eq!(format!("{:.1}", 0.0095), "0.0"); + assert_eq!(format!("{:.2}", 0.0095), "0.01"); + assert_eq!(format!("{:.3}", 0.0095), "0.009"); // because it really is less than 0.0095 + assert_eq!(format!("{:.4}", 0.0095), "0.0095"); + assert_eq!(format!("{:.5}", 0.0095), "0.00950"); + assert_eq!(format!("{:.10}", 0.0095), "0.0095000000"); + assert_eq!(format!("{:.30}", 0.0095), "0.009499999999999999764077607267"); + + assert_eq!(format!("{:.0}", 7.5e-11), "0"); + assert_eq!(format!("{:.3}", 7.5e-11), "0.000"); + assert_eq!(format!("{:.10}", 7.5e-11), "0.0000000001"); + assert_eq!(format!("{:.11}", 7.5e-11), "0.00000000007"); // ditto + assert_eq!(format!("{:.12}", 7.5e-11), "0.000000000075"); + assert_eq!(format!("{:.13}", 7.5e-11), "0.0000000000750"); + assert_eq!(format!("{:.20}", 7.5e-11), "0.00000000007500000000"); + assert_eq!(format!("{:.30}", 7.5e-11), "0.000000000074999999999999999501"); + + assert_eq!(format!("{:.0}", 1.0e25), "10000000000000000905969664"); + assert_eq!(format!("{:.1}", 1.0e25), "10000000000000000905969664.0"); + assert_eq!(format!("{:.3}", 1.0e25), "10000000000000000905969664.000"); + + assert_eq!(format!("{:.0}", 1.0e-6), "0"); + assert_eq!(format!("{:.3}", 1.0e-6), "0.000"); + assert_eq!(format!("{:.6}", 1.0e-6), "0.000001"); + assert_eq!(format!("{:.9}", 1.0e-6), "0.000001000"); + assert_eq!(format!("{:.12}", 1.0e-6), "0.000001000000"); + assert_eq!(format!("{:.22}", 1.0e-6), "0.0000010000000000000000"); + assert_eq!(format!("{:.23}", 1.0e-6), "0.00000099999999999999995"); + assert_eq!(format!("{:.24}", 1.0e-6), "0.000000999999999999999955"); + assert_eq!(format!("{:.25}", 1.0e-6), "0.0000009999999999999999547"); + assert_eq!(format!("{:.35}", 1.0e-6), "0.00000099999999999999995474811182589"); + assert_eq!(format!("{:.45}", 1.0e-6), "0.000000999999999999999954748111825886258685614"); assert_eq!( - to_string(f, 1.0e-6, Minus, 55), + format!("{:.55}", 1.0e-6), "0.0000009999999999999999547481118258862586856139387236908" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 65), + format!("{:.65}", 1.0e-6), "0.00000099999999999999995474811182588625868561393872369080781936646" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 75), + format!("{:.75}", 1.0e-6), "0.000000999999999999999954748111825886258685613938723690807819366455078125000" ); + // f16 #[cfg(target_has_reliable_f16)] { - assert_eq!(to_string(f, f16::MAX, Minus, 0), "65504"); - assert_eq!(to_string(f, f16::MAX, Minus, 1), "65504.0"); - assert_eq!(to_string(f, f16::MAX, Minus, 2), "65504.00"); + assert_eq!(format!("{:.0}", f16::MAX), "65504"); + assert_eq!(format!("{:.1}", f16::MAX), "65504.0"); + assert_eq!(format!("{:.2}", f16::MAX), "65504.00"); } - - assert_eq!(to_string(f, f32::MAX, Minus, 0), "340282346638528859811704183484516925440"); - assert_eq!(to_string(f, f32::MAX, Minus, 1), "340282346638528859811704183484516925440.0"); - assert_eq!(to_string(f, f32::MAX, Minus, 2), "340282346638528859811704183484516925440.00"); + // f32 + assert_eq!(format!("{:.0}", f32::MAX), "340282346638528859811704183484516925440"); + assert_eq!(format!("{:.1}", f32::MAX), "340282346638528859811704183484516925440.0"); + assert_eq!(format!("{:.2}", f32::MAX), "340282346638528859811704183484516925440.00"); if cfg!(miri) { // Miri is too slow @@ -1214,103 +975,103 @@ where #[cfg(target_has_reliable_f16)] { let minf16 = crate::num::ldexp_f16(1.0, -24); - assert_eq!(to_string(f, minf16, Minus, 0), "0"); - assert_eq!(to_string(f, minf16, Minus, 1), "0.0"); - assert_eq!(to_string(f, minf16, Minus, 2), "0.00"); - assert_eq!(to_string(f, minf16, Minus, 4), "0.0000"); - assert_eq!(to_string(f, minf16, Minus, 8), "0.00000006"); - assert_eq!(to_string(f, minf16, Minus, 10), "0.0000000596"); - assert_eq!(to_string(f, minf16, Minus, 15), "0.000000059604645"); - assert_eq!(to_string(f, minf16, Minus, 20), "0.00000005960464477539"); - assert_eq!(to_string(f, minf16, Minus, 24), "0.000000059604644775390625"); - assert_eq!(to_string(f, minf16, Minus, 32), "0.00000005960464477539062500000000"); + assert_eq!(format!("{:.0}", minf16), "0"); + assert_eq!(format!("{:.1}", minf16), "0.0"); + assert_eq!(format!("{:.2}", minf16), "0.00"); + assert_eq!(format!("{:.4}", minf16), "0.0000"); + assert_eq!(format!("{:.8}", minf16), "0.00000006"); + assert_eq!(format!("{:.10}", minf16), "0.0000000596"); + assert_eq!(format!("{:.15}", minf16), "0.000000059604645"); + assert_eq!(format!("{:.20}", minf16), "0.00000005960464477539"); + assert_eq!(format!("{:.24}", minf16), "0.000000059604644775390625"); + assert_eq!(format!("{:.32}", minf16), "0.00000005960464477539062500000000"); } let minf32 = ldexp_f32(1.0, -149); - assert_eq!(to_string(f, minf32, Minus, 0), "0"); - assert_eq!(to_string(f, minf32, Minus, 1), "0.0"); - assert_eq!(to_string(f, minf32, Minus, 2), "0.00"); - assert_eq!(to_string(f, minf32, Minus, 4), "0.0000"); - assert_eq!(to_string(f, minf32, Minus, 8), "0.00000000"); - assert_eq!(to_string(f, minf32, Minus, 16), "0.0000000000000000"); - assert_eq!(to_string(f, minf32, Minus, 32), "0.00000000000000000000000000000000"); + assert_eq!(format!("{:.0}", minf32), "0"); + assert_eq!(format!("{:.1}", minf32), "0.0"); + assert_eq!(format!("{:.2}", minf32), "0.00"); + assert_eq!(format!("{:.4}", minf32), "0.0000"); + assert_eq!(format!("{:.8}", minf32), "0.00000000"); + assert_eq!(format!("{:.16}", minf32), "0.0000000000000000"); + assert_eq!(format!("{:.32}", minf32), "0.00000000000000000000000000000000"); assert_eq!( - to_string(f, minf32, Minus, 64), + format!("{:.64}", minf32), "0.0000000000000000000000000000000000000000000014012984643248170709" ); assert_eq!( - to_string(f, minf32, Minus, 128), + format!("{:.128}", minf32), "0.0000000000000000000000000000000000000000000014012984643248170709\ - 2372958328991613128026194187651577175706828388979108268586060149" + 2372958328991613128026194187651577175706828388979108268586060149" ); assert_eq!( - to_string(f, minf32, Minus, 256), + format!("{:.256}", minf32), "0.0000000000000000000000000000000000000000000014012984643248170709\ - 2372958328991613128026194187651577175706828388979108268586060148\ - 6638188362121582031250000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000" + 2372958328991613128026194187651577175706828388979108268586060148\ + 6638188362121582031250000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000" ); assert_eq!( - to_string(f, f64::MAX, Minus, 0), + format!("{:.0}", f64::MAX), "1797693134862315708145274237317043567980705675258449965989174768\ - 0315726078002853876058955863276687817154045895351438246423432132\ - 6889464182768467546703537516986049910576551282076245490090389328\ - 9440758685084551339423045832369032229481658085593321233482747978\ - 26204144723168738177180919299881250404026184124858368" + 0315726078002853876058955863276687817154045895351438246423432132\ + 6889464182768467546703537516986049910576551282076245490090389328\ + 9440758685084551339423045832369032229481658085593321233482747978\ + 26204144723168738177180919299881250404026184124858368" ); assert_eq!( - to_string(f, f64::MAX, Minus, 10), + format!("{:.10}", f64::MAX), "1797693134862315708145274237317043567980705675258449965989174768\ - 0315726078002853876058955863276687817154045895351438246423432132\ - 6889464182768467546703537516986049910576551282076245490090389328\ - 9440758685084551339423045832369032229481658085593321233482747978\ - 26204144723168738177180919299881250404026184124858368.0000000000" + 0315726078002853876058955863276687817154045895351438246423432132\ + 6889464182768467546703537516986049910576551282076245490090389328\ + 9440758685084551339423045832369032229481658085593321233482747978\ + 26204144723168738177180919299881250404026184124858368.0000000000" ); let minf64 = ldexp_f64(1.0, -1074); - assert_eq!(to_string(f, minf64, Minus, 0), "0"); - assert_eq!(to_string(f, minf64, Minus, 1), "0.0"); - assert_eq!(to_string(f, minf64, Minus, 10), "0.0000000000"); + assert_eq!(format!("{:.0}", minf64), "0"); + assert_eq!(format!("{:.1}", minf64), "0.0"); + assert_eq!(format!("{:.10}", minf64), "0.0000000000"); assert_eq!( - to_string(f, minf64, Minus, 100), + format!("{:.100}", minf64), "0.0000000000000000000000000000000000000000000000000000000000000000\ - 000000000000000000000000000000000000" + 000000000000000000000000000000000000" ); assert_eq!( - to_string(f, minf64, Minus, 1000), + format!("{:.1000}", minf64), "0.0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000\ - 0004940656458412465441765687928682213723650598026143247644255856\ - 8250067550727020875186529983636163599237979656469544571773092665\ - 6710355939796398774796010781878126300713190311404527845817167848\ - 9821036887186360569987307230500063874091535649843873124733972731\ - 6961514003171538539807412623856559117102665855668676818703956031\ - 0624931945271591492455329305456544401127480129709999541931989409\ - 0804165633245247571478690147267801593552386115501348035264934720\ - 1937902681071074917033322268447533357208324319360923828934583680\ - 6010601150616980975307834227731832924790498252473077637592724787\ - 4656084778203734469699533647017972677717585125660551199131504891\ - 1014510378627381672509558373897335989937" + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0004940656458412465441765687928682213723650598026143247644255856\ + 8250067550727020875186529983636163599237979656469544571773092665\ + 6710355939796398774796010781878126300713190311404527845817167848\ + 9821036887186360569987307230500063874091535649843873124733972731\ + 6961514003171538539807412623856559117102665855668676818703956031\ + 0624931945271591492455329305456544401127480129709999541931989409\ + 0804165633245247571478690147267801593552386115501348035264934720\ + 1937902681071074917033322268447533357208324319360923828934583680\ + 6010601150616980975307834227731832924790498252473077637592724787\ + 4656084778203734469699533647017972677717585125660551199131504891\ + 1014510378627381672509558373897335989937" ); // very large output - assert_eq!(to_string(f, 0.0, Minus, 50000), format!("0.{:0>50000}", "")); - assert_eq!(to_string(f, 1.0e1, Minus, 50000), format!("10.{:0>50000}", "")); - assert_eq!(to_string(f, 1.0e0, Minus, 50000), format!("1.{:0>50000}", "")); + assert_eq!(format!("{:.50000}", 0.0), format!("0.{:0>50000}", "")); + assert_eq!(format!("{:.50000}", 10.0), format!("10.{:0>50000}", "")); + assert_eq!(format!("{:.50000}", 1.0), format!("1.{:0>50000}", "")); assert_eq!( - to_string(f, 1.0e-1, Minus, 50000), - format!("0.1000000000000000055511151231257827021181583404541015625{:0>49945}", "") + format!("{:.50000}", 0.1), + format!("0.1000000000000000055511151231257827021181583404541015625{:0>49945}", "",) ); assert_eq!( - to_string(f, 1.0e-20, Minus, 50000), + format!("{:.50000}", 1.0e-20), format!( "0.0000000000000000000099999999999999994515327145420957165172950370\ - 2787392447107715776066783064379706047475337982177734375{:0>49881}", - "" + 2787392447107715776066783064379706047475337982177734375{:0>49881}", + "", ) ); } diff --git a/library/coretests/tests/num/flt2dec/random.rs b/library/coretests/tests/num/flt2dec/random.rs index 7386139aaced5..ae65e09a96a27 100644 --- a/library/coretests/tests/num/flt2dec/random.rs +++ b/library/coretests/tests/num/flt2dec/random.rs @@ -1,29 +1,20 @@ #![cfg(not(target_arch = "wasm32"))] -use core::num::flt2dec::strategy::grisu::{format_exact_opt, format_shortest_opt}; -use core::num::flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; +use core::num::flt2dec::decoder::{Decoded64, decode_f16, decode_f32, decode_f64}; +use core::num::flt2dec::strategy::{dragon, grisu}; use std::mem::MaybeUninit; use std::str; use rand::distr::{Distribution, Uniform}; -pub fn decode_finite(v: T) -> Decoded { - match decode(v).1 { - FullDecoded::Finite(decoded) => decoded, - full_decoded => panic!("expected finite, got {full_decoded:?} instead"), - } -} - -fn iterate(func: &str, k: usize, n: usize, mut f: F, mut g: G, mut v: V) -> (usize, usize) +/// Compare the short representation of grisu and dragon against each other. +/// A total `n` test cases are generated by invoking `sample` with range `0..n`. +fn test_short_representation(test_name: &str, n: usize, mut sample: F) -> (usize, usize) where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> Option<(&'a [u8], i16)>, - G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), - V: FnMut(usize) -> Decoded, + F: FnMut(usize) -> Decoded64, { - assert!(k <= 1024); - - let mut npassed = 0; // f(x) = Some(g(x)) - let mut nignored = 0; // f(x) = None + let mut npassed = 0; + let mut nignored = 0; for i in 0..n { if (i & 0xfffff) == 0 { @@ -37,23 +28,16 @@ where ); } - let decoded = v(i); - let mut buf1 = [MaybeUninit::new(0); 1024]; - if let Some((buf1, e1)) = f(&decoded, &mut buf1[..k]) { - let mut buf2 = [MaybeUninit::new(0); 1024]; - let (buf2, e2) = g(&decoded, &mut buf2[..k]); - if e1 == e2 && buf1 == buf2 { + let decoded = sample(i); + let mut buf1 = [MaybeUninit::new(0); 17]; + if let Some((s1, e1)) = grisu::format_short(&decoded, &mut buf1) { + let mut buf2 = [MaybeUninit::new(0); 17]; + let (s2, e2) = dragon::format_short(&decoded, &mut buf2); + if e1 == e2 && s1 == s2 { npassed += 1; } else { println!( - "equivalence test failed, {:x}/{:x}: {:?} f(i)={}e{} g(i)={}e{}", - i, - n, - decoded, - str::from_utf8(buf1).unwrap(), - e1, - str::from_utf8(buf2).unwrap(), - e2 + "equivalence test failed, {i:x}/{n:x}: {decoded:?} f(i)={s1}e{e1} g(i)={s2}e{e2}" ); } } else { @@ -61,191 +45,219 @@ where } } println!( - "{}({}): done, ignored={} passed={} failed={}", - func, - k, + "{}(): done, ignored={} passed={} failed={}", + test_name, nignored, npassed, n - nignored - npassed ); assert!( nignored + npassed == n, - "{}({}): {} out of {} values returns an incorrect value!", - func, - k, + "{}(): {} out of {} values returns an incorrect value!", + test_name, n - nignored - npassed, n ); (npassed, nignored) } +// Bits 0u16..0x7C00 cover the positive finite-range, +// with 1u16..0x7C00 for non-zero. +const F16_POS_FIN_RANGE: std::ops::Range = 1..0x7C00; + +// Bits 0u32..0x7F80_0000 cover the positive finite-range, +// with 1u32..0x7F80_0000 for non-zero. +const F32_POS_FIN_RANGE: std::ops::Range = 1..0x7F80_000; + +// Bits 0u64..0x7FF0_0000_0000_0000 cover the positive finite-range, +// With 1u64..0x7FF0_0000_0000_0000 for non-zero. +const F64_POS_FIN_RANGE: std::ops::Range = 1..0x7FF0_0000_0000_0000; + #[cfg(target_has_reliable_f16)] -pub fn f16_random_equivalence_test(f: F, g: G, k: usize, n: usize) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> Option<(&'a [u8], i16)>, - G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +pub fn f16_random_equivalence_test(n: usize) { let mut rng = crate::test_rng(); - let f16_range = Uniform::new(0x0001u16, 0x7c00).unwrap(); - iterate("f16_random_equivalence_test", k, n, f, g, |_| { + let f16_range = Uniform::new(F16_POS_FIN_RANGE.start, F16_POS_FIN_RANGE.end).unwrap(); + test_short_representation("short_f16_random_equivalence", n, |_| { let x = f16::from_bits(f16_range.sample(&mut rng)); - decode_finite(x) + assert!(x.is_finite()); + decode_f16(x) }); } -pub fn f32_random_equivalence_test(f: F, g: G, k: usize, n: usize) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> Option<(&'a [u8], i16)>, - G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +pub fn f32_random_equivalence_test(n: usize) { let mut rng = crate::test_rng(); - let f32_range = Uniform::new(0x0000_0001u32, 0x7f80_0000).unwrap(); - iterate("f32_random_equivalence_test", k, n, f, g, |_| { + let f32_range = Uniform::new(F32_POS_FIN_RANGE.start, F32_POS_FIN_RANGE.end).unwrap(); + test_short_representation("short_f32_random_equivalence", n, |_| { let x = f32::from_bits(f32_range.sample(&mut rng)); - decode_finite(x) + assert!(x.is_finite()); + decode_f32(x) }); } -pub fn f64_random_equivalence_test(f: F, g: G, k: usize, n: usize) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> Option<(&'a [u8], i16)>, - G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +pub fn f64_random_equivalence_test(n: usize) { let mut rng = crate::test_rng(); - let f64_range = Uniform::new(0x0000_0000_0000_0001u64, 0x7ff0_0000_0000_0000).unwrap(); - iterate("f64_random_equivalence_test", k, n, f, g, |_| { + let f64_range = Uniform::new(F64_POS_FIN_RANGE.start, F64_POS_FIN_RANGE.end).unwrap(); + test_short_representation("short_f64_random_equivalence", n, |_| { let x = f64::from_bits(f64_range.sample(&mut rng)); - decode_finite(x) + assert!(x.is_finite()); + decode_f64(x) }); } +/// Half precision is narrow enough to cover all of its finite numbers. +#[test] +#[cfg_attr(miri, ignore)] // Miri is too slow #[cfg(target_has_reliable_f16)] -pub fn f16_exhaustive_equivalence_test(f: F, g: G, k: usize) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> Option<(&'a [u8], i16)>, - G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - // Unlike the other float types, `f16` is small enough that these exhaustive tests - // can run in less than a second so we don't need to ignore it. - - // iterate from 0x0001 to 0x7bff, i.e., all finite ranges - let (npassed, nignored) = - iterate("f16_exhaustive_equivalence_test", k, 0x7bff, f, g, |i: usize| { - let x = f16::from_bits(i as u16 + 1); - decode_finite(x) - }); +fn short_f16_exhaustive_equivalence_test() { + let (npassed, nignored) = test_short_representation( + "short_f16_exhaustive_equivalence", + (F16_POS_FIN_RANGE.end - F16_POS_FIN_RANGE.start) as usize, + |i: usize| { + let x = f16::from_bits(i as u16 + F16_POS_FIN_RANGE.start); + assert!(x.is_finite()); + decode_f16(x) + }, + ); assert_eq!((npassed, nignored), (29735, 2008)); } -pub fn f32_exhaustive_equivalence_test(f: F, g: G, k: usize) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> Option<(&'a [u8], i16)>, - G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ - // we have only 2^23 * (2^8 - 1) - 1 = 2,139,095,039 positive finite f32 values, - // so why not simply testing all of them? - // - // this is of course very stressful (and thus should be behind an `#[ignore]` attribute), - // but with `-C opt-level=3 -C lto` this only takes about an hour or so. - - // iterate from 0x0000_0001 to 0x7f7f_ffff, i.e., all finite ranges - let (npassed, nignored) = - iterate("f32_exhaustive_equivalence_test", k, 0x7f7f_ffff, f, g, |i: usize| { - let x = f32::from_bits(i as u32 + 1); - decode_finite(x) - }); +/// Single precision is too wide to cover all of its finite numbers. +/// There are 2^23 * (2^8 - 1) - 1 = 2,139,095,039 positive finite f32 values. +/// With `-C opt-level=3 -C lto` this takes about an hour, give or take. Use +/// `--nocapture` for progress reporting. +#[test] +#[ignore] // it is too expensive +fn short_f32_exhaustive_equivalence_test() { + let (npassed, nignored) = test_short_representation( + "short_f32_exhaustive_equivalence", + (F32_POS_FIN_RANGE.end - F32_POS_FIN_RANGE.start) as usize, + |i: usize| { + let x = f32::from_bits(i as u32 + F32_POS_FIN_RANGE.start); + assert!(x.is_finite()); + decode_f32(x) + }, + ); assert_eq!((npassed, nignored), (2121451881, 17643158)); } #[test] -fn shortest_random_equivalence_test() { - use core::num::flt2dec::strategy::dragon::format_shortest as fallback; +fn short_random_equivalence_test() { // Miri is too slow let n = if cfg!(miri) { 10 } else { 10_000 }; - f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n); - f32_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n); + f64_random_equivalence_test(n); + f32_random_equivalence_test(n); #[cfg(target_has_reliable_f16)] - f16_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n); -} - -#[test] -#[cfg_attr(miri, ignore)] // Miri is to slow -#[cfg(target_has_reliable_f16)] -fn shortest_f16_exhaustive_equivalence_test() { - // see the f32 version - use core::num::flt2dec::strategy::dragon::format_shortest as fallback; - f16_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS); + f16_random_equivalence_test(n); } #[test] #[ignore] // it is too expensive -fn shortest_f32_exhaustive_equivalence_test() { - // it is hard to directly test the optimality of the output, but we can at least test if - // two different algorithms agree to each other. - // - // this reports the progress and the number of f32 values returned `None`. - // with `--nocapture` (and plenty of time and appropriate rustc flags), this should print: - // `done, ignored=17643158 passed=2121451881 failed=0`. - - use core::num::flt2dec::strategy::dragon::format_shortest as fallback; - f32_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS); +fn short_f64_hard_random_equivalence_test() { + // this again probably has to use appropriate rustc flags. + f64_random_equivalence_test(100_000_000); } -#[test] -#[ignore] // it is too expensive -fn shortest_f64_hard_random_equivalence_test() { - // this again probably has to use appropriate rustc flags. +/// Compare the fixed representation of grisu and dragon against each other. +/// A total `n` test cases are generated by invoking `sample` with range `0..n`. +fn test_fixed_representation(test_name: &str, bufn: usize, n: usize, mut sample: F) +where + F: FnMut(usize) -> Decoded64, +{ + const BUF_CAP: usize = 128; + assert!(bufn <= BUF_CAP); + let mut npassed = 0; + let mut nignored = 0; - use core::num::flt2dec::strategy::dragon::format_shortest as fallback; - f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, 100_000_000); + for i in 0..n { + if (i & 0xfffff) == 0 { + println!( + "in progress, {:x}/{:x} (ignored={} passed={} failed={})", + i, + n, + nignored, + npassed, + i - nignored - npassed + ); + } + + let decoded = sample(i); + let mut buf1 = [MaybeUninit::new(0); BUF_CAP]; + if let Some((s1, e1)) = grisu::format_fixed(&decoded, &mut buf1[..bufn], isize::MIN) { + let mut buf2 = [MaybeUninit::new(0); BUF_CAP]; + let (s2, e2) = dragon::format_fixed(&decoded, &mut buf2[..bufn], isize::MIN); + if e1 == e2 && s1 == s2 { + npassed += 1; + } else { + println!( + "equivalence test failed, {i:x}/{n:x}: {decoded:?} f(i)={s1}e{e1} g(i)={s2}e{e2}" + ); + } + } else { + nignored += 1; + } + } + println!( + "fixed_{}(): done, ignored={} passed={} failed={}", + test_name, + nignored, + npassed, + n - nignored - npassed + ); + assert!( + nignored + npassed == n, + "{}(): {} out of {} values returns an incorrect value!", + test_name, + n - nignored - npassed, + n + ); } #[test] #[cfg(target_has_reliable_f16)] -fn exact_f16_random_equivalence_test() { - use core::num::flt2dec::strategy::dragon::format_exact as fallback; +fn fixed_f16_random_equivalence_test() { // Miri is too slow let n = if cfg!(miri) { 3 } else { 1_000 }; - - for k in 1..21 { - f16_random_equivalence_test( - |d, buf| format_exact_opt(d, buf, i16::MIN), - |d, buf| fallback(d, buf, i16::MIN), - k, - n, - ); + // Run bufn times n test cases. + for bufn in 1..21 { + let mut rng = crate::test_rng(); + let f16_range = Uniform::new(F16_POS_FIN_RANGE.start, F16_POS_FIN_RANGE.end).unwrap(); + test_fixed_representation("fixed_f16_random_equivalence", bufn, n, |_| { + let x = f16::from_bits(f16_range.sample(&mut rng)); + assert!(x.is_finite()); + decode_f16(x) + }); } } #[test] -fn exact_f32_random_equivalence_test() { - use core::num::flt2dec::strategy::dragon::format_exact as fallback; +fn fixed_f32_random_equivalence_test() { // Miri is too slow let n = if cfg!(miri) { 3 } else { 1_000 }; - - for k in 1..21 { - f32_random_equivalence_test( - |d, buf| format_exact_opt(d, buf, i16::MIN), - |d, buf| fallback(d, buf, i16::MIN), - k, - n, - ); + // Run bufn times n test cases. + for bufn in 1..21 { + let mut rng = crate::test_rng(); + let f32_range = Uniform::new(F32_POS_FIN_RANGE.start, F32_POS_FIN_RANGE.end).unwrap(); + test_fixed_representation("fixed_f32_random_equivalence", bufn, n, |_| { + let x = f32::from_bits(f32_range.sample(&mut rng)); + assert!(x.is_finite()); + decode_f32(x) + }); } } #[test] -fn exact_f64_random_equivalence_test() { - use core::num::flt2dec::strategy::dragon::format_exact as fallback; +fn fixed_f64_random_equivalence_test() { // Miri is too slow let n = if cfg!(miri) { 2 } else { 1_000 }; - - for k in 1..21 { - f64_random_equivalence_test( - |d, buf| format_exact_opt(d, buf, i16::MIN), - |d, buf| fallback(d, buf, i16::MIN), - k, - n, - ); + // Run bufn times n test cases. + for bufn in 1..21 { + let mut rng = crate::test_rng(); + let f64_range = Uniform::new(F64_POS_FIN_RANGE.start, F64_POS_FIN_RANGE.end).unwrap(); + test_fixed_representation("fixed_f64_random_equivalence", bufn, n, |_| { + let x = f64::from_bits(f64_range.sample(&mut rng)); + assert!(x.is_finite()); + decode_f64(x) + }); } } diff --git a/library/coretests/tests/num/flt2dec/strategy/dragon.rs b/library/coretests/tests/num/flt2dec/strategy/dragon.rs index 43bb6024f9cee..105da5d0b9352 100644 --- a/library/coretests/tests/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/tests/num/flt2dec/strategy/dragon.rs @@ -1,8 +1,6 @@ use core::num::bignum::Big32x40 as Big; use core::num::flt2dec::strategy::dragon::*; -use super::super::*; - #[test] fn test_mul_pow10() { let mut prevpow10 = Big::from_small(1); @@ -13,57 +11,3 @@ fn test_mul_pow10() { prevpow10 = curpow10; } } - -#[test] -fn shortest_sanity_test() { - f64_shortest_sanity_test(format_shortest); - f32_shortest_sanity_test(format_shortest); - #[cfg(target_has_reliable_f16)] - f16_shortest_sanity_test(format_shortest); - more_shortest_sanity_test(format_shortest); -} - -#[test] -#[cfg_attr(miri, ignore)] // Miri is too slow -fn exact_sanity_test() { - // This test ends up running what I can only assume is some corner-ish case - // of the `exp2` library function, defined in whatever C runtime we're - // using. In VS 2013 this function apparently had a bug as this test fails - // when linked, but with VS 2015 the bug appears fixed as the test runs just - // fine. - // - // The bug seems to be a difference in return value of `exp2(-1057)`, where - // in VS 2013 it returns a double with the bit pattern 0x2 and in VS 2015 it - // returns 0x20000. - // - // For now just ignore this test entirely on MSVC as it's tested elsewhere - // anyway and we're not super interested in testing each platform's exp2 - // implementation. - if !cfg!(target_env = "msvc") { - f64_exact_sanity_test(format_exact); - } - f32_exact_sanity_test(format_exact); - - #[cfg(target_has_reliable_f16)] - f16_exact_sanity_test(format_exact); -} - -#[test] -fn test_to_shortest_str() { - to_shortest_str_test(format_shortest); -} - -#[test] -fn test_to_shortest_exp_str() { - to_shortest_exp_str_test(format_shortest); -} - -#[test] -fn test_to_exact_exp_str() { - to_exact_exp_str_test(format_exact); -} - -#[test] -fn test_to_exact_fixed_str() { - to_exact_fixed_str_test(format_exact); -} diff --git a/library/coretests/tests/num/flt2dec/strategy/grisu.rs b/library/coretests/tests/num/flt2dec/strategy/grisu.rs index 117191e0c8fdb..de62910cc1a9e 100644 --- a/library/coretests/tests/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/tests/num/flt2dec/strategy/grisu.rs @@ -1,7 +1,5 @@ use core::num::flt2dec::strategy::grisu::*; -use super::super::*; - #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_cached_power() { @@ -33,45 +31,3 @@ fn test_max_pow10_no_more_than() { prevtenk = tenk; } } - -#[test] -fn shortest_sanity_test() { - f64_shortest_sanity_test(format_shortest); - f32_shortest_sanity_test(format_shortest); - #[cfg(target_has_reliable_f16)] - f16_shortest_sanity_test(format_shortest); - more_shortest_sanity_test(format_shortest); -} - -#[test] -#[cfg_attr(miri, ignore)] // Miri is too slow -fn exact_sanity_test() { - // See comments in dragon.rs's exact_sanity_test for why this test is - // ignored on MSVC - if !cfg!(target_env = "msvc") { - f64_exact_sanity_test(format_exact); - } - f32_exact_sanity_test(format_exact); - #[cfg(target_has_reliable_f16)] - f16_exact_sanity_test(format_exact); -} - -#[test] -fn test_to_shortest_str() { - to_shortest_str_test(format_shortest); -} - -#[test] -fn test_to_shortest_exp_str() { - to_shortest_exp_str_test(format_shortest); -} - -#[test] -fn test_to_exact_exp_str() { - to_exact_exp_str_test(format_exact); -} - -#[test] -fn test_to_exact_fixed_str() { - to_exact_fixed_str_test(format_exact); -} diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff deleted file mode 100644 index 6baa902b6f4bd..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff +++ /dev/null @@ -1,175 +0,0 @@ -- // MIR for `float_to_exponential_common` before GVN -+ // MIR for `float_to_exponential_common` after GVN - - fn float_to_exponential_common(_1: &mut Formatter<'_>, _2: &T, _3: bool) -> Result<(), std::fmt::Error> { - debug fmt => _1; - debug num => _2; - debug upper => _3; - let mut _0: std::result::Result<(), std::fmt::Error>; - let _4: bool; - let mut _6: std::option::Option; - let mut _7: isize; - let mut _9: &mut std::fmt::Formatter<'_>; - let mut _10: &T; - let mut _11: core::num::flt2dec::Sign; - let mut _12: u32; - let mut _13: u32; - let mut _14: usize; - let mut _15: bool; - let mut _16: &mut std::fmt::Formatter<'_>; - let mut _17: &T; - let mut _18: core::num::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::flt2dec::Sign; - scope 2 { - debug sign => _5; - scope 3 { - debug precision => _8; - let _8: usize; - scope 5 (inlined Formatter::<'_>::precision) { - let mut _22: u32; - let mut _23: u32; - let mut _24: usize; - let mut _25: u16; - } - } - } - } - scope 4 (inlined Formatter::<'_>::sign_plus) { - let mut _20: u32; - let mut _21: u32; - } - - bb0: { - StorageLive(_4); - StorageLive(_20); - StorageLive(_21); - _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = MinusPlus; -+ _5 = const MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::flt2dec::Sign::Minus; -+ _5 = const core::num::flt2dec::Sign::Minus; - goto -> bb3; - } - - bb3: { - StorageLive(_6); - StorageLive(_22); - StorageLive(_23); - _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); - StorageDead(_23); - switchInt(move _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { -- StorageLive(_8); -+ nop; - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = copy _1; - StorageLive(_10); - _10 = copy _2; - StorageLive(_11); - _11 = copy _5; - StorageLive(_12); - StorageLive(_13); - StorageLive(_14); - _14 = copy _8; -- _13 = move _14 as u32 (IntToInt); -+ _13 = copy _8 as u32 (IntToInt); - StorageDead(_14); - _12 = Add(move _13, const 1_u32); - StorageDead(_13); - StorageLive(_15); - _15 = copy _3; -- _0 = float_to_exponential_common_exact::(move _9, move _10, move _11, move _12, move _15) -> [return: bb5, unwind unreachable]; -+ _0 = float_to_exponential_common_exact::(copy _1, copy _2, move _11, move _12, copy _3) -> [return: bb5, unwind unreachable]; - } - - bb5: { - StorageDead(_15); - StorageDead(_12); - StorageDead(_11); - StorageDead(_10); - StorageDead(_9); -- StorageDead(_8); -+ nop; - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = copy _1; - StorageLive(_17); - _17 = copy _2; - StorageLive(_18); - _18 = copy _5; - StorageLive(_19); - _19 = copy _3; -- _0 = float_to_exponential_common_shortest::(move _16, move _17, move _18, move _19) -> [return: bb7, unwind unreachable]; -+ _0 = float_to_exponential_common_shortest::(copy _1, copy _2, move _18, copy _3) -> [return: bb7, unwind unreachable]; - } - - bb7: { - StorageDead(_19); - StorageDead(_18); - StorageDead(_17); - StorageDead(_16); - goto -> bb8; - } - - bb8: { - StorageDead(_5); - StorageDead(_4); - StorageDead(_6); - return; - } - - bb9: { - _7 = discriminant(_6); - switchInt(move _7) -> [1: bb4, 0: bb6, otherwise: bb12]; - } - - bb10: { - StorageDead(_22); - _6 = const Option::::None; - goto -> bb9; - } - - bb11: { - StorageDead(_22); - StorageLive(_24); - StorageLive(_25); - _25 = copy (((*_1).0: std::fmt::FormattingOptions).2: u16); - _24 = move _25 as usize (IntToInt); - StorageDead(_25); - _6 = Option::::Some(move _24); - StorageDead(_24); - goto -> bb9; - } - - bb12: { - unreachable; - } - } - - ALLOC0 (size: 8, align: 4) { - 00 00 00 00 __ __ __ __ │ ....░░░░ - } - diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff deleted file mode 100644 index 36540e038654f..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff +++ /dev/null @@ -1,175 +0,0 @@ -- // MIR for `float_to_exponential_common` before GVN -+ // MIR for `float_to_exponential_common` after GVN - - fn float_to_exponential_common(_1: &mut Formatter<'_>, _2: &T, _3: bool) -> Result<(), std::fmt::Error> { - debug fmt => _1; - debug num => _2; - debug upper => _3; - let mut _0: std::result::Result<(), std::fmt::Error>; - let _4: bool; - let mut _6: std::option::Option; - let mut _7: isize; - let mut _9: &mut std::fmt::Formatter<'_>; - let mut _10: &T; - let mut _11: core::num::flt2dec::Sign; - let mut _12: u32; - let mut _13: u32; - let mut _14: usize; - let mut _15: bool; - let mut _16: &mut std::fmt::Formatter<'_>; - let mut _17: &T; - let mut _18: core::num::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::flt2dec::Sign; - scope 2 { - debug sign => _5; - scope 3 { - debug precision => _8; - let _8: usize; - scope 5 (inlined Formatter::<'_>::precision) { - let mut _22: u32; - let mut _23: u32; - let mut _24: usize; - let mut _25: u16; - } - } - } - } - scope 4 (inlined Formatter::<'_>::sign_plus) { - let mut _20: u32; - let mut _21: u32; - } - - bb0: { - StorageLive(_4); - StorageLive(_20); - StorageLive(_21); - _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = MinusPlus; -+ _5 = const MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::flt2dec::Sign::Minus; -+ _5 = const core::num::flt2dec::Sign::Minus; - goto -> bb3; - } - - bb3: { - StorageLive(_6); - StorageLive(_22); - StorageLive(_23); - _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); - StorageDead(_23); - switchInt(move _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { -- StorageLive(_8); -+ nop; - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = copy _1; - StorageLive(_10); - _10 = copy _2; - StorageLive(_11); - _11 = copy _5; - StorageLive(_12); - StorageLive(_13); - StorageLive(_14); - _14 = copy _8; -- _13 = move _14 as u32 (IntToInt); -+ _13 = copy _8 as u32 (IntToInt); - StorageDead(_14); - _12 = Add(move _13, const 1_u32); - StorageDead(_13); - StorageLive(_15); - _15 = copy _3; -- _0 = float_to_exponential_common_exact::(move _9, move _10, move _11, move _12, move _15) -> [return: bb5, unwind continue]; -+ _0 = float_to_exponential_common_exact::(copy _1, copy _2, move _11, move _12, copy _3) -> [return: bb5, unwind continue]; - } - - bb5: { - StorageDead(_15); - StorageDead(_12); - StorageDead(_11); - StorageDead(_10); - StorageDead(_9); -- StorageDead(_8); -+ nop; - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = copy _1; - StorageLive(_17); - _17 = copy _2; - StorageLive(_18); - _18 = copy _5; - StorageLive(_19); - _19 = copy _3; -- _0 = float_to_exponential_common_shortest::(move _16, move _17, move _18, move _19) -> [return: bb7, unwind continue]; -+ _0 = float_to_exponential_common_shortest::(copy _1, copy _2, move _18, copy _3) -> [return: bb7, unwind continue]; - } - - bb7: { - StorageDead(_19); - StorageDead(_18); - StorageDead(_17); - StorageDead(_16); - goto -> bb8; - } - - bb8: { - StorageDead(_5); - StorageDead(_4); - StorageDead(_6); - return; - } - - bb9: { - _7 = discriminant(_6); - switchInt(move _7) -> [1: bb4, 0: bb6, otherwise: bb12]; - } - - bb10: { - StorageDead(_22); - _6 = const Option::::None; - goto -> bb9; - } - - bb11: { - StorageDead(_22); - StorageLive(_24); - StorageLive(_25); - _25 = copy (((*_1).0: std::fmt::FormattingOptions).2: u16); - _24 = move _25 as usize (IntToInt); - StorageDead(_25); - _6 = Option::::Some(move _24); - StorageDead(_24); - goto -> bb9; - } - - bb12: { - unreachable; - } - } - - ALLOC0 (size: 8, align: 4) { - 00 00 00 00 __ __ __ __ │ ....░░░░ - } - diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff deleted file mode 100644 index 41c350f3eaeb5..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff +++ /dev/null @@ -1,175 +0,0 @@ -- // MIR for `float_to_exponential_common` before GVN -+ // MIR for `float_to_exponential_common` after GVN - - fn float_to_exponential_common(_1: &mut Formatter<'_>, _2: &T, _3: bool) -> Result<(), std::fmt::Error> { - debug fmt => _1; - debug num => _2; - debug upper => _3; - let mut _0: std::result::Result<(), std::fmt::Error>; - let _4: bool; - let mut _6: std::option::Option; - let mut _7: isize; - let mut _9: &mut std::fmt::Formatter<'_>; - let mut _10: &T; - let mut _11: core::num::flt2dec::Sign; - let mut _12: u32; - let mut _13: u32; - let mut _14: usize; - let mut _15: bool; - let mut _16: &mut std::fmt::Formatter<'_>; - let mut _17: &T; - let mut _18: core::num::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::flt2dec::Sign; - scope 2 { - debug sign => _5; - scope 3 { - debug precision => _8; - let _8: usize; - scope 5 (inlined Formatter::<'_>::precision) { - let mut _22: u32; - let mut _23: u32; - let mut _24: usize; - let mut _25: u16; - } - } - } - } - scope 4 (inlined Formatter::<'_>::sign_plus) { - let mut _20: u32; - let mut _21: u32; - } - - bb0: { - StorageLive(_4); - StorageLive(_20); - StorageLive(_21); - _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = MinusPlus; -+ _5 = const MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::flt2dec::Sign::Minus; -+ _5 = const core::num::flt2dec::Sign::Minus; - goto -> bb3; - } - - bb3: { - StorageLive(_6); - StorageLive(_22); - StorageLive(_23); - _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); - StorageDead(_23); - switchInt(move _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { -- StorageLive(_8); -+ nop; - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = copy _1; - StorageLive(_10); - _10 = copy _2; - StorageLive(_11); - _11 = copy _5; - StorageLive(_12); - StorageLive(_13); - StorageLive(_14); - _14 = copy _8; -- _13 = move _14 as u32 (IntToInt); -+ _13 = copy _8 as u32 (IntToInt); - StorageDead(_14); - _12 = Add(move _13, const 1_u32); - StorageDead(_13); - StorageLive(_15); - _15 = copy _3; -- _0 = float_to_exponential_common_exact::(move _9, move _10, move _11, move _12, move _15) -> [return: bb5, unwind unreachable]; -+ _0 = float_to_exponential_common_exact::(copy _1, copy _2, move _11, move _12, copy _3) -> [return: bb5, unwind unreachable]; - } - - bb5: { - StorageDead(_15); - StorageDead(_12); - StorageDead(_11); - StorageDead(_10); - StorageDead(_9); -- StorageDead(_8); -+ nop; - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = copy _1; - StorageLive(_17); - _17 = copy _2; - StorageLive(_18); - _18 = copy _5; - StorageLive(_19); - _19 = copy _3; -- _0 = float_to_exponential_common_shortest::(move _16, move _17, move _18, move _19) -> [return: bb7, unwind unreachable]; -+ _0 = float_to_exponential_common_shortest::(copy _1, copy _2, move _18, copy _3) -> [return: bb7, unwind unreachable]; - } - - bb7: { - StorageDead(_19); - StorageDead(_18); - StorageDead(_17); - StorageDead(_16); - goto -> bb8; - } - - bb8: { - StorageDead(_5); - StorageDead(_4); - StorageDead(_6); - return; - } - - bb9: { - _7 = discriminant(_6); - switchInt(move _7) -> [1: bb4, 0: bb6, otherwise: bb12]; - } - - bb10: { - StorageDead(_22); - _6 = const Option::::None; - goto -> bb9; - } - - bb11: { - StorageDead(_22); - StorageLive(_24); - StorageLive(_25); - _25 = copy (((*_1).0: std::fmt::FormattingOptions).2: u16); - _24 = move _25 as usize (IntToInt); - StorageDead(_25); - _6 = Option::::Some(move _24); - StorageDead(_24); - goto -> bb9; - } - - bb12: { - unreachable; - } - } - - ALLOC0 (size: 16, align: 8) { - 00 00 00 00 00 00 00 00 __ __ __ __ __ __ __ __ │ ........░░░░░░░░ - } - diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff deleted file mode 100644 index b839bf81eaf45..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff +++ /dev/null @@ -1,175 +0,0 @@ -- // MIR for `float_to_exponential_common` before GVN -+ // MIR for `float_to_exponential_common` after GVN - - fn float_to_exponential_common(_1: &mut Formatter<'_>, _2: &T, _3: bool) -> Result<(), std::fmt::Error> { - debug fmt => _1; - debug num => _2; - debug upper => _3; - let mut _0: std::result::Result<(), std::fmt::Error>; - let _4: bool; - let mut _6: std::option::Option; - let mut _7: isize; - let mut _9: &mut std::fmt::Formatter<'_>; - let mut _10: &T; - let mut _11: core::num::flt2dec::Sign; - let mut _12: u32; - let mut _13: u32; - let mut _14: usize; - let mut _15: bool; - let mut _16: &mut std::fmt::Formatter<'_>; - let mut _17: &T; - let mut _18: core::num::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::flt2dec::Sign; - scope 2 { - debug sign => _5; - scope 3 { - debug precision => _8; - let _8: usize; - scope 5 (inlined Formatter::<'_>::precision) { - let mut _22: u32; - let mut _23: u32; - let mut _24: usize; - let mut _25: u16; - } - } - } - } - scope 4 (inlined Formatter::<'_>::sign_plus) { - let mut _20: u32; - let mut _21: u32; - } - - bb0: { - StorageLive(_4); - StorageLive(_20); - StorageLive(_21); - _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = MinusPlus; -+ _5 = const MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::flt2dec::Sign::Minus; -+ _5 = const core::num::flt2dec::Sign::Minus; - goto -> bb3; - } - - bb3: { - StorageLive(_6); - StorageLive(_22); - StorageLive(_23); - _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); - StorageDead(_23); - switchInt(move _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { -- StorageLive(_8); -+ nop; - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = copy _1; - StorageLive(_10); - _10 = copy _2; - StorageLive(_11); - _11 = copy _5; - StorageLive(_12); - StorageLive(_13); - StorageLive(_14); - _14 = copy _8; -- _13 = move _14 as u32 (IntToInt); -+ _13 = copy _8 as u32 (IntToInt); - StorageDead(_14); - _12 = Add(move _13, const 1_u32); - StorageDead(_13); - StorageLive(_15); - _15 = copy _3; -- _0 = float_to_exponential_common_exact::(move _9, move _10, move _11, move _12, move _15) -> [return: bb5, unwind continue]; -+ _0 = float_to_exponential_common_exact::(copy _1, copy _2, move _11, move _12, copy _3) -> [return: bb5, unwind continue]; - } - - bb5: { - StorageDead(_15); - StorageDead(_12); - StorageDead(_11); - StorageDead(_10); - StorageDead(_9); -- StorageDead(_8); -+ nop; - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = copy _1; - StorageLive(_17); - _17 = copy _2; - StorageLive(_18); - _18 = copy _5; - StorageLive(_19); - _19 = copy _3; -- _0 = float_to_exponential_common_shortest::(move _16, move _17, move _18, move _19) -> [return: bb7, unwind continue]; -+ _0 = float_to_exponential_common_shortest::(copy _1, copy _2, move _18, copy _3) -> [return: bb7, unwind continue]; - } - - bb7: { - StorageDead(_19); - StorageDead(_18); - StorageDead(_17); - StorageDead(_16); - goto -> bb8; - } - - bb8: { - StorageDead(_5); - StorageDead(_4); - StorageDead(_6); - return; - } - - bb9: { - _7 = discriminant(_6); - switchInt(move _7) -> [1: bb4, 0: bb6, otherwise: bb12]; - } - - bb10: { - StorageDead(_22); - _6 = const Option::::None; - goto -> bb9; - } - - bb11: { - StorageDead(_22); - StorageLive(_24); - StorageLive(_25); - _25 = copy (((*_1).0: std::fmt::FormattingOptions).2: u16); - _24 = move _25 as usize (IntToInt); - StorageDead(_25); - _6 = Option::::Some(move _24); - StorageDead(_24); - goto -> bb9; - } - - bb12: { - unreachable; - } - } - - ALLOC0 (size: 16, align: 8) { - 00 00 00 00 00 00 00 00 __ __ __ __ __ __ __ __ │ ........░░░░░░░░ - } - diff --git a/tests/mir-opt/funky_arms.rs b/tests/mir-opt/funky_arms.rs deleted file mode 100644 index 403a22ebed320..0000000000000 --- a/tests/mir-opt/funky_arms.rs +++ /dev/null @@ -1,58 +0,0 @@ -// skip-filecheck -// EMIT_MIR_FOR_EACH_PANIC_STRATEGY -// EMIT_MIR_FOR_EACH_BIT_WIDTH - -#![feature(flt2dec)] - -extern crate core; - -use core::num::flt2dec; -use std::fmt::{Formatter, Result}; - -// EMIT_MIR funky_arms.float_to_exponential_common.GVN.diff -pub fn float_to_exponential_common(fmt: &mut Formatter<'_>, num: &T, upper: bool) -> Result -where - T: flt2dec::DecodableFloat, -{ - let force_sign = fmt.sign_plus(); - // A bug in const propagation (never reached master, but during dev of a PR) caused the - // `sign = Minus` assignment to get propagated into all future reads of `sign`. This is - // wrong because `sign` could also have `MinusPlus` value. - let sign = match force_sign { - false => flt2dec::Sign::Minus, - true => flt2dec::Sign::MinusPlus, - }; - - if let Some(precision) = fmt.precision() { - // 1 integral digit + `precision` fractional digits = `precision + 1` total digits - float_to_exponential_common_exact(fmt, num, sign, precision as u32 + 1, upper) - } else { - float_to_exponential_common_shortest(fmt, num, sign, upper) - } -} -#[inline(never)] -fn float_to_exponential_common_exact( - fmt: &mut Formatter<'_>, - num: &T, - sign: flt2dec::Sign, - precision: u32, - upper: bool, -) -> Result -where - T: flt2dec::DecodableFloat, -{ - unimplemented!() -} - -#[inline(never)] -fn float_to_exponential_common_shortest( - fmt: &mut Formatter<'_>, - num: &T, - sign: flt2dec::Sign, - upper: bool, -) -> Result -where - T: flt2dec::DecodableFloat, -{ - unimplemented!() -}