diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs index bb70e59f960ed..6361fd9151cc4 100644 --- a/library/core/src/fmt/float.rs +++ b/library/core/src/fmt/float.rs @@ -1,5 +1,6 @@ use crate::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp}; use crate::mem::MaybeUninit; +use crate::num::FpCategory; use crate::num::imp::{flt2dec, fmt as numfmt}; #[doc(hidden)] @@ -36,7 +37,7 @@ impl_general_format! { f32 f64 } fn float_to_decimal_common_exact( fmt: &mut Formatter<'_>, num: &T, - sign: flt2dec::Sign, + sign: &'static str, precision: u16, ) -> Result where @@ -45,14 +46,14 @@ where 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, + flt2dec::format_fixed, *num, sign, precision.into(), &mut buf, &mut parts, ); - // SAFETY: `to_exact_fixed_str` and `format_exact` produce only ASCII characters. + // SAFETY: `to_exact_fixed_str` and `format_fixed` produce only ASCII characters. unsafe { fmt.pad_formatted_parts(&formatted) } } @@ -62,7 +63,7 @@ where fn float_to_decimal_common_shortest( fmt: &mut Formatter<'_>, num: &T, - sign: flt2dec::Sign, + sign: &'static str, precision: u16, ) -> Result where @@ -73,27 +74,21 @@ where [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, + flt2dec::format_short, *num, sign, precision.into(), &mut buf, &mut parts, ); - // SAFETY: `to_shortest_str` and `format_shortest` produce only ASCII characters. + // SAFETY: `to_shortest_str` and `format_short` produce only ASCII characters. unsafe { fmt.pad_formatted_parts(&formatted) } } -fn float_to_decimal_display(fmt: &mut Formatter<'_>, num: &T) -> Result +fn float_to_decimal_display(fmt: &mut Formatter<'_>, num: &T, sign: &'static str) -> Result where T: flt2dec::DecodableFloat, { - let force_sign = fmt.sign_plus(); - let sign = match force_sign { - false => flt2dec::Sign::Minus, - true => flt2dec::Sign::MinusPlus, - }; - if let Some(precision) = fmt.options.get_precision() { float_to_decimal_common_exact(fmt, num, sign, precision) } else { @@ -108,7 +103,7 @@ where fn float_to_exponential_common_exact( fmt: &mut Formatter<'_>, num: &T, - sign: flt2dec::Sign, + sign: &'static str, precision: u16, upper: bool, ) -> Result @@ -118,7 +113,7 @@ where 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, + flt2dec::format_fixed, *num, sign, precision.into(), @@ -126,7 +121,7 @@ where &mut buf, &mut parts, ); - // SAFETY: `to_exact_exp_str` and `format_exact` produce only ASCII characters. + // SAFETY: `to_exact_exp_str` and `format_fixed` produce only ASCII characters. unsafe { fmt.pad_formatted_parts(&formatted) } } @@ -136,7 +131,7 @@ where fn float_to_exponential_common_shortest( fmt: &mut Formatter<'_>, num: &T, - sign: flt2dec::Sign, + sign: &'static str, upper: bool, ) -> Result where @@ -147,7 +142,7 @@ where [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, + flt2dec::format_short, *num, sign, (0, 0), @@ -155,21 +150,20 @@ where &mut buf, &mut parts, ); - // SAFETY: `to_shortest_exp_str` and `format_shortest` produce only ASCII characters. + // SAFETY: `to_shortest_exp_str` and `format_short` produce only ASCII characters. unsafe { fmt.pad_formatted_parts(&formatted) } } // Common code of floating point LowerExp and UpperExp. -fn float_to_exponential_common(fmt: &mut Formatter<'_>, num: &T, upper: bool) -> Result +fn float_to_exponential_common( + fmt: &mut Formatter<'_>, + num: &T, + sign: &'static str, + 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, - }; - if let Some(precision) = fmt.options.get_precision() { float_to_exponential_common_exact(fmt, num, sign, precision, upper) } else { @@ -177,16 +171,10 @@ where } } -fn float_to_general_debug(fmt: &mut Formatter<'_>, num: &T) -> Result +fn float_to_general_debug(fmt: &mut Formatter<'_>, num: &T, sign: &'static str) -> 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, - }; - 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) @@ -202,36 +190,106 @@ where } } +/// Determines which sign to format, if any. +macro_rules! sign_of { + ($v:ident, $f:ident) => { + if ($v).is_sign_negative() { + "-" + } else if ($f).sign_plus() { + "+" + } else { + "" + } + }; +} + 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) + + #[stable(feature = "rust1", since = "1.0.0")] + impl Debug for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let sign: &'static str = sign_of!(self, f); + 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 => float_to_general_debug(f, self, sign), } } + } - #[stable(feature = "rust1", since = "1.0.0")] - impl Display for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_decimal_display(fmt, self) + #[stable(feature = "rust1", since = "1.0.0")] + impl Display for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let sign: &'static str = sign_of!(self, f); + 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 => float_to_decimal_display(f, self, sign), } } + } - #[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 LowerExp for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let sign: &'static str = sign_of!(self, f); + 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, 3, | w | w.write_str("0e0")), + Some(n) => f.pad_number(sign, "0.".len() + n + "e0".len(), | w | { + w.write_str("0.")?; + w.write_zeroes(n)?; + w.write_str("e0") + }), + } + }, + FpCategory::Subnormal | FpCategory::Normal => float_to_exponential_common(f, self, sign, false) } } + } - #[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 UpperExp for $T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let sign: &'static str = sign_of!(self, f); + 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, 3, | w | w.write_str("0E0")), + Some(n) => f.pad_number(sign, "0.".len() + n + "E0".len(), | w | { + w.write_str("0.")?; + w.write_zeroes(n)?; + w.write_str("E0") + }), + } + }, + FpCategory::Subnormal | FpCategory::Normal => float_to_exponential_common(f, self, sign, true) } } + } + )* }; } diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 9e5f693246f33..15dc8ad4ecf5d 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -1778,16 +1778,13 @@ 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. + /// Writes an integer representation to the `Formatter`. /// - /// # 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 + /// The `digits` must contain the absolute value of the original number, + /// without a sign. Whether a sign is written by `pad_integral` depends on + /// the `is_nonnegative` flag and the formatting parameters. The optional + /// `prefix` (e.g., `"0x"`) always gets written after any sign and before + /// any digits. /// /// This function will correctly account for the flags provided as well as /// the minimum width. It will not take precision into account. @@ -1823,8 +1820,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 { @@ -1856,7 +1853,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 @@ -1864,7 +1861,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(()) @@ -1872,11 +1869,60 @@ 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) } } + /// Writes a number representation to the `Formatter` using a closure. + /// + /// The `write_abs` closure must write the absolute value of the number, + /// without a sign, and `write_len` must match the number of characters + /// written by `write_abs`. + /// + /// The output of `write_abs` should honor any precision specified by the + /// formatting parameters. Likewise, `sign` should already account for + /// [`Self::sign_plus`]. + /// + /// This function correctly applies padding an alignment to the output. + 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 self.sign_aware_zero_pad() { + // The sign goes before the padding. + 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) + } + }; + + 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. /// @@ -1979,56 +2025,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 { - // Padding widths are capped at `u16`, so reaching this branch means - // the formatted output is also shorter than `u16::MAX`. - let len = match u16::try_from(len) { - Ok(len) => len, - Err(_) => unreachable!(), - }; - let post_padding = self.padding(width - len, Alignment::Right)?; - // SAFETY: Per the precondition. - unsafe { - self.write_formatted_parts(&formatted)?; - } - 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 @@ -2037,39 +2033,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(); - } - if nzeroes > 0 { - self.buf.write_str(&ZEROES[..nzeroes])?; + 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; + 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(()) + }) + } + + /// Writes 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 e9302a6127f5c..0a4af7b0e2c95 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::imp::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) + }) } } diff --git a/library/core/src/num/imp/flt2dec/decoder.rs b/library/core/src/num/imp/flt2dec/decoder.rs index 5ccc91f4c9faf..91365d67f420f 100644 --- a/library/core/src/num/imp/flt2dec/decoder.rs +++ b/library/core/src/num/imp/flt2dec/decoder.rs @@ -26,19 +26,6 @@ pub struct Decoded { 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), -} - /// A floating point type which can be `decode`d. pub trait DecodableFloat: FloatExt + Copy { /// The minimum positive normalized value. @@ -64,44 +51,28 @@ impl DecodableFloat for f64 { } } -/// 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(); +/// Returns a `Decoded` value from given floating point number. +pub fn decode(v: T) -> Decoded { + let (mant, exp, _) = 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, + match v.classify() { 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 }) + Decoded { mant, minus: 1, plus: 1, exp, inclusive: even } } FpCategory::Normal => { let minnorm = ::min_pos_norm_value().integer_decode(); if mant == minnorm.0 { // 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, - }) + Decoded { mant: mant << 2, minus: 1, plus: 2, exp: exp - 2, inclusive: 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, - }) + Decoded { mant: mant << 1, minus: 1, plus: 1, exp: exp - 1, inclusive: even } } } - }; - (sign < 0, decoded) + _ => panic!("need a finite non-zero value"), + } } diff --git a/library/core/src/num/imp/flt2dec/mod.rs b/library/core/src/num/imp/flt2dec/mod.rs index cf895b3e89e66..b62e6e2b7984a 100644 --- a/library/core/src/num/imp/flt2dec/mod.rs +++ b/library/core/src/num/imp/flt2dec/mod.rs @@ -73,18 +73,6 @@ 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. - -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). - The rendered digits are formatted into the actual string form with four functions: @@ -122,7 +110,7 @@ functions. issue = "none" )] -pub use self::decoder::{DecodableFloat, Decoded, FullDecoded, decode}; +pub use self::decoder::{DecodableFloat, Decoded, decode}; use super::fmt::{Formatted, Part}; use crate::mem::MaybeUninit; @@ -142,6 +130,79 @@ pub mod strategy { /// The exact formula is `ceil(# bits in mantissa * log_10 2 + 1)`. pub const MAX_SIG_DIGITS: usize = 17; +/// Formats a finite, non-zero floating-point number in decimal form. +/// +/// The return pair `(digits, pow10)` represents: +/// +/// v = (0.d₀d₁…dₙ₋₁) × 10ᵏ +/// +/// where `digits = [d₀,…,dₙ₋₁]` and `pow10 = k`. The leading digit satisfies +/// `d₀ ≠ 0`, ensuring `0.1 ≤ mantissa < 1`. +/// +/// Given an input floating-point value `f`, the produced decimal `v` is the +/// closest such `n`-digit value satisfying: +/// +/// |f − v| ≤ ½ × 10^(k−n) +/// +/// If two `n`-digit decimals are equally close, a deterministic tie-breaking +/// rule is applied so that parsing the produced decimal recovers `f` exactly. +/// +/// In short mode, the minimal `n ≥ 1` satisfying the above property is chosen. +/// The result is therefore the shortest decimal that round-trips back to the +/// original floating-point value. This formatting matches common expectations; +/// for example `0.1f32` prints as `"0.1"`. +pub fn format_short<'a>(d: &Decoded, buf: &'a mut [MaybeUninit]) -> (&'a [u8], i16) { + // 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_short` returned `None` so this is okay. + match strategy::grisu::format_short(d, unsafe { &mut *(buf as *mut _) }) { + Some(ret) => ret, + None => strategy::dragon::format_short(d, buf), + } +} + +/// Disables `format_fixed` argument explicitly. +pub const UNLIMITED_RESOLUTION: i16 = i16::MIN; + +/// Formats a finite, non-zero floating-point number in decimal form. +/// +/// The return pair `(digits, pow10)` represents: +/// +/// v = (0.d₀d₁…dₙ₋₁) × 10ᵏ +/// +/// where `digits = [d₀,…,dₙ₋₁]` and `pow10 = k`. The leading digit satisfies +/// `d₀ ≠ 0`, ensuring `0.1 ≤ mantissa < 1`. +/// +/// Given an input floating-point value `f`, the produced decimal `v` is the +/// closest such `n`-digit value satisfying: +/// +/// |f − v| ≤ ½ × 10^(k−n) +/// +/// If two `n`-digit decimals are equally close, a deterministic tie-breaking +/// rule is applied so that parsing the produced decimal recovers `f` exactly. +/// +/// In fixed mode, the number of digits `n` is limited by the 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 three decimal places, i.e., values are multiples of +/// `0.001`. Use of [`UNLIMITED_RESOLUTION`] can get expensive. +/// +/// Note: If the resolution causes the value to round to zero, then the returned +/// digit slice is empty. This preserves the invariant d₀ ≠ 0. +pub fn format_fixed<'a>( + d: &Decoded, + buf: &'a mut [MaybeUninit], + resolution: i16, +) -> (&'a [u8], i16) { + // 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 strategy::grisu::format_fixed(d, unsafe { &mut *(buf as *mut _) }, resolution) { + Some(ret) => ret, + None => strategy::dragon::format_fixed(d, buf, resolution), + } +} + /// When `d` contains decimal digits, increase the last digit and propagate carry. /// Returns a next digit when it causes the length to change. #[doc(hidden)] @@ -304,42 +365,11 @@ fn digits_to_exp_str<'a>( 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` +/// i.e., `inf` and `nan`. The first part to be rendered is always the `sign` /// (which can be an empty string if no sign is rendered). /// /// `format_shortest` should be the underlying digit-generation function. @@ -357,7 +387,7 @@ fn determine_sign(sign: Sign, decoded: &FullDecoded, negative: bool) -> &'static pub fn to_shortest_str<'a, T, F>( mut format_shortest: F, v: T, - sign: Sign, + sign: &'static str, frac_digits: usize, buf: &'a mut [MaybeUninit], parts: &'a mut [MaybeUninit>], @@ -369,43 +399,9 @@ where 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) } - } - } + let dec = decode(v); + let (buf, exp) = format_shortest(&dec, buf); + Formatted { sign, parts: digits_to_dec_str(buf, exp, frac_digits, parts) } } /// Formats the given floating point number into the decimal form or @@ -413,8 +409,8 @@ where /// 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). +/// The first part to be rendered is always the `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. @@ -431,7 +427,7 @@ where pub fn to_shortest_exp_str<'a, T, F>( mut format_shortest: F, v: T, - sign: Sign, + sign: &'static str, dec_bounds: (i16, i16), upper: bool, buf: &'a mut [MaybeUninit], @@ -445,39 +441,15 @@ where 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 } - } - } + let dec = decode(v); + let (buf, exp) = format_shortest(&dec, 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 @@ -508,8 +480,8 @@ fn estimate_max_buf_len(exp: i16) -> usize { /// exactly given number of fractional digits. The result is stored to /// the supplied parts array while utilizing given byte buffer as a scratch. /// `upper` is used to determine the case of the exponent prefix (`e` or `E`). -/// The first part to be rendered is always a `Part::Sign` (which can be -/// an empty string if no sign is rendered). +/// The first part to be rendered is always the `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. @@ -525,8 +497,8 @@ fn estimate_max_buf_len(exp: i16) -> usize { pub fn to_exact_exp_str<'a, T, F>( mut format_exact: F, v: T, - sign: Sign, - frac_digits: usize, + sign: &'static str, + ndigits: usize, upper: bool, buf: &'a mut [MaybeUninit], parts: &'a mut [MaybeUninit>], @@ -537,58 +509,20 @@ where { assert!(parts.len() >= 6); - 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][e0] - parts[0] = MaybeUninit::new(Part::Copy(b"0.")); - parts[1] = MaybeUninit::new(Part::Zero(frac_digits)); - 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); - // Scratch space is only needed for the significant digits that `format_exact` can - // actually generate. Any remaining requested fractional precision becomes a trailing - // `Part::Zero`. - let sig_digits = if frac_digits < maxlen { frac_digits + 1 } else { maxlen }; - assert!(buf.len() >= sig_digits); - - let (buf, exp) = format_exact(decoded, &mut buf[..sig_digits], i16::MIN); - Formatted { sign, parts: digits_to_exp_str(buf, exp, frac_digits, upper, parts) } - } - } + let dec = decode(v); + let maxlen = estimate_max_buf_len(dec.exp); + assert!(buf.len() >= ndigits || buf.len() >= maxlen); + + let trunc = if ndigits < maxlen { ndigits + 1 } else { maxlen }; + let (buf, exp) = format_exact(&dec, &mut buf[..trunc], UNLIMITED_RESOLUTION); + 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` +/// i.e., `inf` and `nan`. The first part to be rendered is always the `sign` /// (which can be an empty string if no sign is rendered). /// /// `format_exact` should be the underlying digit-generation function. @@ -603,7 +537,7 @@ where pub fn to_exact_fixed_str<'a, T, F>( mut format_exact: F, v: T, - sign: Sign, + sign: &'static str, frac_digits: usize, buf: &'a mut [MaybeUninit], parts: &'a mut [MaybeUninit>], @@ -614,72 +548,36 @@ where { 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() }, - } + let dec = decode(v); + let maxlen = estimate_max_buf_len(dec.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 { UNLIMITED_RESOLUTION }; + let (buf, exp) = format_exact(&dec, &mut buf[..maxlen], limit); + if buf.len() == 0 { + // The number rounds down to zero at the given resolution. + debug_assert!(exp <= limit); + 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() }, } - } - 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) } + } 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/imp/flt2dec/strategy/dragon.rs b/library/core/src/num/imp/flt2dec/strategy/dragon.rs index de19ca6bb664d..8ec1b149b3c9c 100644 --- a/library/core/src/num/imp/flt2dec/strategy/dragon.rs +++ b/library/core/src/num/imp/flt2dec/strategy/dragon.rs @@ -100,11 +100,11 @@ fn div_rem_upto_16<'a>( (d, x) } -/// The shortest mode implementation for Dragon. -pub fn format_shortest<'a>( +/// Provides a Dragon implementation for flt2dec::format_short. +pub fn format_short<'a>( d: &Decoded, buf: &'a mut [MaybeUninit], -) -> (/*digits*/ &'a [u8], /*exp*/ i16) { +) -> (/*digits*/ &'a [u8], /*pow10*/ i16) { // 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 @@ -260,12 +260,12 @@ pub fn format_shortest<'a>( (unsafe { buf[..i].assume_init_ref() }, k) } -/// The exact and fixed mode implementation for Dragon. -pub fn format_exact<'a>( +/// Provides a Dragon implementation for flt2dec::format_fixed. +pub fn format_fixed<'a>( d: &Decoded, buf: &'a mut [MaybeUninit], - limit: i16, -) -> (/*digits*/ &'a [u8], /*exp*/ i16) { + resolution: i16, +) -> (/*digits*/ &'a [u8], /*pow10*/ i16) { assert!(d.mant > 0); assert!(d.minus > 0); assert!(d.plus > 0); @@ -305,14 +305,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 as i32 - resolution as i32) as usize) < buf.len() { + (k - resolution) as usize } else { buf.len() }; @@ -377,9 +377,9 @@ 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; } diff --git a/library/core/src/num/imp/flt2dec/strategy/grisu.rs b/library/core/src/num/imp/flt2dec/strategy/grisu.rs index f7ee465829340..a08294967121e 100644 --- a/library/core/src/num/imp/flt2dec/strategy/grisu.rs +++ b/library/core/src/num/imp/flt2dec/strategy/grisu.rs @@ -161,13 +161,13 @@ pub fn max_pow10_no_more_than(x: u32) -> (u8, u32) { } } -/// The shortest mode implementation for Grisu. +/// Provides a Grisu implementation for flt2dec::format_short. /// /// It returns `None` when it would return an inexact representation otherwise. -pub fn format_shortest_opt<'a>( +pub fn format_short<'a>( d: &Decoded, buf: &'a mut [MaybeUninit], -) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> { +) -> Option<(/*digits*/ &'a [u8], /*pow10*/ i16)> { assert!(d.mant > 0); assert!(d.minus > 0); assert!(d.plus > 0); @@ -450,31 +450,14 @@ pub fn format_shortest_opt<'a>( } } -/// 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 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), - } -} - -/// The exact and fixed mode implementation for Grisu. +/// Provides a Grisu implementation for flt2dec::format_fixed. /// /// It returns `None` when it would return an inexact representation otherwise. -pub fn format_exact_opt<'a>( +pub fn format_fixed<'a>( d: &Decoded, buf: &'a mut [MaybeUninit], - limit: i16, -) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> { + resolution: i16, +) -> Option<(/*digits*/ &'a [u8], /*pow10*/ i16)> { assert!(d.mant > 0); assert!(d.mant < (1 << 61)); // we need at least three bits of additional precision assert!(!buf.is_empty()); @@ -528,7 +511,7 @@ pub fn format_exact_opt<'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 len = if exp <= limit { + let len = if exp <= 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. // @@ -540,10 +523,10 @@ 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, exp, 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 ((exp as i32 - resolution as i32) as usize) < buf.len() { + (exp - resolution) as usize } else { buf.len() }; @@ -573,7 +556,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, exp, resolution, vrem, (ten_kappa as u64) << e, err << e) }; } @@ -625,7 +608,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, exp, resolution, r, 1 << e, err) }; } // restore invariants @@ -651,7 +634,7 @@ pub fn format_exact_opt<'a>( buf: &mut [MaybeUninit], mut len: usize, mut exp: i16, - limit: i16, + resolution: i16, remainder: u64, ten_kappa: u64, ulp: u64, @@ -742,9 +725,9 @@ pub fn format_exact_opt<'a>( { // 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). + // the additional digit can only be added when `exp == resolution` (edge case). exp += 1; - if exp > limit && len < buf.len() { + if exp > resolution && len < buf.len() { buf[len] = MaybeUninit::new(c); len += 1; } @@ -758,21 +741,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 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/coretests/benches/num/flt2dec/mod.rs b/library/coretests/benches/num/flt2dec/mod.rs index 0cd94ec5e1cfe..41479695d9eb0 100644 --- a/library/coretests/benches/num/flt2dec/mod.rs +++ b/library/coretests/benches/num/flt2dec/mod.rs @@ -3,18 +3,11 @@ mod strategy { mod grisu; } -use core::num::imp::flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; +use core::num::imp::flt2dec::{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 d98b3df9b2f35..465f173b88db1 100644 --- a/library/coretests/benches/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/benches/num/flt2dec/strategy/dragon.rs @@ -5,72 +5,72 @@ use super::super::*; #[bench] fn bench_small_shortest(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&dec), &mut buf); }); } #[bench] fn bench_big_shortest(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&dec), &mut buf); }); } #[bench] fn bench_small_exact_3(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN); }); } #[bench] fn bench_big_exact_3(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN); }); } #[bench] fn bench_small_exact_12(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN); }); } #[bench] fn bench_big_exact_12(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN); }); } #[bench] fn bench_small_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN); }); } #[bench] fn bench_big_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); 1024]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN); }); } diff --git a/library/coretests/benches/num/flt2dec/strategy/grisu.rs b/library/coretests/benches/num/flt2dec/strategy/grisu.rs index 5f73fdebf5e77..27ed49f8b7d48 100644 --- a/library/coretests/benches/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/benches/num/flt2dec/strategy/grisu.rs @@ -3,108 +3,56 @@ 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"), - } -} - #[bench] fn bench_small_shortest(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&dec), &mut buf).unwrap(); }); } #[bench] fn bench_big_shortest(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&dec), &mut buf).unwrap(); }); } #[bench] fn bench_small_exact_3(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN).unwrap(); }); } #[bench] fn bench_big_exact_3(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN).unwrap(); }); } #[bench] fn bench_small_exact_12(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); + let dec = decode(3.141592f64); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN).unwrap(); }); } #[bench] fn bench_big_exact_12(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); + let dec = decode(f64::MAX); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); - }); -} - -#[bench] -fn bench_small_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(3.141592f64); - let mut buf = [MaybeUninit::new(0); 1024]; - b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); - }); -} - -#[bench] -fn bench_big_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(f64::MAX); - let mut buf = [MaybeUninit::new(0); 1024]; - b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); - }); -} - -#[bench] -fn bench_one_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(1.0); - let mut buf = [MaybeUninit::new(0); 1024]; - b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); - }); -} - -#[bench] -fn bench_trailing_zero_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(250.000000000000000000000000); - let mut buf = [MaybeUninit::new(0); 1024]; - b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); - }); -} - -#[bench] -fn bench_halfway_point_exact_inf(b: &mut Bencher) { - let decoded = decode_finite(1.00000000000000011102230246251565404236316680908203125); - let mut buf = [MaybeUninit::new(0); 1024]; - b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&dec), &mut buf, i16::MIN).unwrap(); }); } diff --git a/library/coretests/tests/fmt/float.rs b/library/coretests/tests/fmt/float.rs index 4f7357761744b..17b9f5c1d47c0 100644 --- a/library/coretests/tests/fmt/float.rs +++ b/library/coretests/tests/fmt/float.rs @@ -28,6 +28,8 @@ fn test_format_f64() { #[test] fn test_format_f64_rounds_ties_to_even() { + // Use only values exactly representable in IEEE floating-point, i.e., + // multiples of 1/2^n. assert_eq!("0", format!("{:.0}", 0.5f64)); assert_eq!("2", format!("{:.0}", 1.5f64)); assert_eq!("2", format!("{:.0}", 2.5f64)); @@ -40,7 +42,7 @@ fn test_format_f64_rounds_ties_to_even() { assert_eq!("0.8", format!("{:.1}", 0.75f64)); assert_eq!("0.12", format!("{:.2}", 0.125f64)); assert_eq!("0.88", format!("{:.2}", 0.875f64)); - assert_eq!("0.062", format!("{:.3}", 0.062f64)); + assert_eq!("0.062", format!("{:.3}", 0.0625f64)); assert_eq!("-0", format!("{:.0}", -0.5f64)); assert_eq!("-2", format!("{:.0}", -1.5f64)); assert_eq!("-2", format!("{:.0}", -2.5f64)); @@ -53,7 +55,7 @@ fn test_format_f64_rounds_ties_to_even() { assert_eq!("-0.8", format!("{:.1}", -0.75f64)); assert_eq!("-0.12", format!("{:.2}", -0.125f64)); assert_eq!("-0.88", format!("{:.2}", -0.875f64)); - assert_eq!("-0.062", format!("{:.3}", -0.062f64)); + assert_eq!("-0.062", format!("{:.3}", -0.0625f64)); assert_eq!("2e0", format!("{:.0e}", 1.5f64)); assert_eq!("2e0", format!("{:.0e}", 2.5f64)); @@ -114,6 +116,8 @@ fn test_format_f32() { #[test] fn test_format_f32_rounds_ties_to_even() { + // Use only values exactly representable in IEEE floating-point, i.e., + // multiples of 1/2^n. assert_eq!("0", format!("{:.0}", 0.5f32)); assert_eq!("2", format!("{:.0}", 1.5f32)); assert_eq!("2", format!("{:.0}", 2.5f32)); @@ -126,7 +130,7 @@ fn test_format_f32_rounds_ties_to_even() { assert_eq!("0.8", format!("{:.1}", 0.75f32)); assert_eq!("0.12", format!("{:.2}", 0.125f32)); assert_eq!("0.88", format!("{:.2}", 0.875f32)); - assert_eq!("0.062", format!("{:.3}", 0.062f32)); + assert_eq!("0.062", format!("{:.3}", 0.0625f32)); assert_eq!("-0", format!("{:.0}", -0.5f32)); assert_eq!("-2", format!("{:.0}", -1.5f32)); assert_eq!("-2", format!("{:.0}", -2.5f32)); @@ -139,7 +143,7 @@ fn test_format_f32_rounds_ties_to_even() { assert_eq!("-0.8", format!("{:.1}", -0.75f32)); assert_eq!("-0.12", format!("{:.2}", -0.125f32)); assert_eq!("-0.88", format!("{:.2}", -0.875f32)); - assert_eq!("-0.062", format!("{:.3}", -0.062f32)); + assert_eq!("-0.062", format!("{:.3}", -0.0625f32)); assert_eq!("2e0", format!("{:.0e}", 1.5f32)); assert_eq!("2e0", format!("{:.0e}", 2.5f32)); @@ -238,6 +242,63 @@ fn test_format_f64_max_precision_exponential() { assert_exact_exp(format_args!("{:65535.65535e}", 1.0f64), "1.", "e0"); } +#[test] +fn test_format_zero() { + let pos = 0.0_f32; + let neg = -0.0_f32; + + // defaults + assert_eq!("0 0.0 0E0 0e0", format!("{pos} {pos:?} {pos:E} {pos:e}")); + assert_eq!("-0 -0.0 -0E0 -0e0", format!("{neg} {neg:?} {neg:E} {neg:e}")); + + // explicit sign + assert_eq!("+0 +0.0 +0E0 +0e0", format!("{pos:+} {pos:+?} {pos:+E} {pos:+e}")); + assert_eq!("-0 -0.0 -0E0 -0e0", format!("{neg:+} {neg:+?} {neg:+E} {neg:+e}")); + + // fixed precision + assert_eq!("0 0 0E0 0e0", format!("{pos:.0} {pos:.0?} {pos:.0E} {pos:.0e}")); + assert_eq!("-0 -0 -0E0 -0e0", format!("{neg:.0} {neg:.0?} {neg:.0E} {neg:.0e}")); + assert_eq!("0.0 0.0 0.0E0 0.0e0", format!("{pos:.1} {pos:.1?} {pos:.1E} {pos:.1e}")); + assert_eq!("-0.0 -0.0 -0.0E0 -0.0e0", format!("{neg:.1} {neg:.1?} {neg:.1E} {neg:.1e}")); + assert_eq!("0.00 0.00 0.00E0 0.00e0", format!("{pos:.2} {pos:.2?} {pos:.2E} {pos:.2e}")); + assert_eq!("-0.00 -0.00 -0.00E0 -0.00e0", format!("{neg:.2} {neg:.2?} {neg:.2E} {neg:.2e}")); +} + +#[test] +fn test_format_inf() { + let pos = 1.0_f32 / 0.0; + let neg = -1.0_f32 / 0.0; + + // defaults + assert_eq!("inf inf inf inf", format!("{pos} {pos:?} {pos:E} {pos:e}")); + assert_eq!("-inf -inf -inf -inf", format!("{neg} {neg:?} {neg:E} {neg:e}")); + + // explicit sign + assert_eq!("+inf +inf +inf +inf", format!("{pos:+} {pos:+?} {pos:+E} {pos:+e}")); + assert_eq!("-inf -inf -inf -inf", format!("{neg:+} {neg:+?} {neg:+E} {neg:+e}")); + + // fixed precision + assert_eq!("inf inf inf inf", format!("{pos:.0} {pos:.0?} {pos:.0E} {pos:.0e}")); + assert_eq!("-inf -inf -inf -inf", format!("{neg:.0} {neg:.0?} {neg:.0E} {neg:.0e}")); + assert_eq!("inf inf inf inf", format!("{pos:.1} {pos:.1?} {pos:.1E} {pos:.1e}")); + assert_eq!("-inf -inf -inf -inf", format!("{neg:.1} {neg:.1?} {neg:.1E} {neg:.1e}")); + assert_eq!("inf inf inf inf", format!("{pos:.2} {pos:.2?} {pos:.2E} {pos:.2e}")); + assert_eq!("-inf -inf -inf -inf", format!("{neg:.2} {neg:.1?} {neg:.1E} {neg:.1e}")); +} + +#[test] +fn test_format_nan() { + let nan = 0.0_f32 / 0.0; + // defaults + assert_eq!("NaN NaN NaN NaN", format!("{nan} {nan:?} {nan:E} {nan:e}")); + // explicit sign + assert_eq!("NaN NaN NaN NaN", format!("{nan:+} {nan:+?} {nan:+E} {nan:+e}")); + // fixed precision + assert_eq!("NaN NaN NaN NaN", format!("{nan:.0} {nan:.0?} {nan:.0E} {nan:.0e}")); + assert_eq!("NaN NaN NaN NaN", format!("{nan:.1} {nan:.1?} {nan:.1E} {nan:.1e}")); + assert_eq!("NaN NaN NaN NaN", format!("{nan:.2} {nan:.2?} {nan:.2E} {nan:.2e}")); +} + fn is_exponential(s: &str) -> bool { s.contains("e") || s.contains("E") } diff --git a/library/coretests/tests/num/flt2dec/equiv.rs b/library/coretests/tests/num/flt2dec/equiv.rs new file mode 100644 index 0000000000000..d038408692556 --- /dev/null +++ b/library/coretests/tests/num/flt2dec/equiv.rs @@ -0,0 +1,212 @@ +#![cfg(not(target_arch = "wasm32"))] + +use core::num::imp::flt2dec; +use std::mem::MaybeUninit; +use std::string::String; + +use flt2dec::strategy::{dragon, grisu}; +use flt2dec::{Decoded, MAX_SIG_DIGITS, decode}; +use rand::distr::{Distribution, Uniform}; + +// Bits 0u16..0x7C00 cover the positive finite-range, +// with 1u16..0x7C00 for non-zero. +const F16_POS_FIN_RANGE: std::ops::Range = 1..f16::INFINITY.to_bits(); + +// 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..f32::INFINITY.to_bits(); + +// 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..f64::INFINITY.to_bits(); + +/// Verifies whether `format_short` of Grisu and Dragon both have the same +/// outcome for n `Decoded` values from `v`. Values which get `None` from Grisu +/// are omitted. The return counts the amount omitted, range 0..n. +fn n_short_equiv(n: usize, mut v: V) -> usize +where + V: FnMut(usize) -> Decoded, +{ + let mut omitted = 0; + let mut failed = 0; + for i in 0..n { + if (i & 0xf_ffff) == 0 { + println!("did {i} out of {n}, with {omitted} omitted"); + } + let dec = v(i); + + let mut grisu_buf = [MaybeUninit::new(b'_'); MAX_SIG_DIGITS]; + let mut dragon_buf = [MaybeUninit::new(b'_'); MAX_SIG_DIGITS]; + if let Some(grisu_return) = grisu::format_short(&dec, &mut grisu_buf) { + let dragon_return = dragon::format_short(&dec, &mut dragon_buf); + if grisu_return != dragon_return { + failed += 1; + println!( + "grisu got ({}, {}) while dragon got ({}, {}) for {:?}", + String::from_utf8_lossy(grisu_return.0), + grisu_return.1, + String::from_utf8_lossy(dragon_return.0), + dragon_return.1, + dec, + ); + } + } else { + omitted += 1; + } + } + assert_eq!(failed, 0, "number of different outcomes"); + omitted +} + +/// Verifies whether `format_fixed` of Grisu and Dragon both have the same +/// outcome for n `Decoded` values from `v`. Values which get `None` from Grisu +/// are omitted. The return counts the amount omitted, range 0..n. +fn n_fixed_equiv(n: usize, bufn: usize, mut v: V) -> usize +where + V: FnMut(usize) -> Decoded, +{ + const BUF_CAP: usize = 64; + assert!(bufn <= BUF_CAP, "just increase capacity when needed"); + const RESOLUTION: i16 = i16::MIN; // unlimited + + let mut omitted = 0; + let mut failed = 0; + for i in 0..n { + if (i & 0xf_ffff) == 0 { + println!("did {i} out of {n}, with {omitted} omitted"); + } + let dec = v(i); + + let mut grisu_buf = [MaybeUninit::new(b'_'); BUF_CAP]; + let mut dragon_buf = [MaybeUninit::new(b'_'); BUF_CAP]; + if let Some(grisu_return) = grisu::format_fixed(&dec, &mut grisu_buf[..bufn], RESOLUTION) { + let dragon_return = dragon::format_fixed(&dec, &mut dragon_buf[..bufn], RESOLUTION); + if grisu_return != dragon_return { + failed += 1; + println!( + "grisu got ({}, {}) while dragon got ({}, {}) for {:?} with a {}-byte buffer", + String::from_utf8_lossy(grisu_return.0), + grisu_return.1, + String::from_utf8_lossy(dragon_return.0), + dragon_return.1, + dec, + bufn, + ); + } + } else { + omitted += 1; + } + } + assert_eq!(failed, 0, "number of different outcomes"); + omitted +} + +#[test] +#[cfg(target_has_reliable_f16)] +fn test_short_f16_random_equiv() { + // Miri is too slow + let n = if cfg!(miri) { 10 } else { 10_000 }; + + let mut rng = crate::test_rng(); + let u = Uniform::new(F16_POS_FIN_RANGE.start, F16_POS_FIN_RANGE.end).unwrap(); + n_short_equiv(n, |_| { + let x = f16::from_bits(u.sample(&mut rng)); + decode(x) + }); +} + +#[test] +fn test_short_f32_random_equiv() { + // Miri is too slow + let n = if cfg!(miri) { 10 } else { 10_000 }; + + let mut rng = crate::test_rng(); + let u = Uniform::new(F32_POS_FIN_RANGE.start, F32_POS_FIN_RANGE.end).unwrap(); + n_short_equiv(n, |_| { + let x = f32::from_bits(u.sample(&mut rng)); + decode(x) + }); +} + +#[test] +fn test_short_f64_random_equiv() { + // Miri is too slow + let n = if cfg!(miri) { 10 } else { 10_000 }; + + let mut rng = crate::test_rng(); + let u = Uniform::new(F64_POS_FIN_RANGE.start, F64_POS_FIN_RANGE.end).unwrap(); + n_short_equiv(n, |_| { + let x = f64::from_bits(u.sample(&mut rng)); + decode(x) + }); +} + +/// 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. +#[test] +#[cfg_attr(miri, ignore)] // Miri is to slow +#[cfg(target_has_reliable_f16)] +fn test_short_f16_exhaustive_equiv() { + let omitted = n_short_equiv(F16_POS_FIN_RANGE.len(), |i: usize| { + let x = f16::from_bits(F16_POS_FIN_RANGE.start + i as u16); + decode(x) + }); + assert_eq!(omitted, 2008, "number of inputs in {F16_POS_FIN_RANGE:?} not covered by Grisu"); +} + +#[test] +#[ignore] // takes about 40 seconds to run +#[cfg_attr(miri, ignore)] // Miri is to slow +fn test_short_f32_exhaustive_equiv() { + let omitted = n_short_equiv(F32_POS_FIN_RANGE.len(), |i: usize| { + let x = f32::from_bits(F32_POS_FIN_RANGE.start + i as u32); + decode(x) + }); + assert_eq!(omitted, 17643158, "number of inputs in {F32_POS_FIN_RANGE:?} not covered by Grisu"); +} + +#[test] +#[cfg(target_has_reliable_f16)] +fn test_fixed_f16_random_equiv() { + // Miri is too slow + let n = if cfg!(miri) { 3 } else { 1_000 }; + + 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(); + n_fixed_equiv(n, bufn, |_| { + let x = f16::from_bits(f16_range.sample(&mut rng)); + decode(x) + }); + } +} + +#[test] +fn test_fixed_f32_random_equiv() { + // Miri is too slow + let n = if cfg!(miri) { 3 } else { 1_000 }; + + 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(); + n_fixed_equiv(n, bufn, |_| { + let x = f32::from_bits(f32_range.sample(&mut rng)); + decode(x) + }); + } +} + +#[test] +fn test_fixed_f64_random_equiv() { + // Miri is too slow + let n = if cfg!(miri) { 2 } else { 1_000 }; + + 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(); + n_fixed_equiv(n, bufn, |_| { + let x = f64::from_bits(f64_range.sample(&mut rng)); + decode(x) + }); + } +} diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index 02a5d31553da1..efd61c9240b1a 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -1,219 +1,131 @@ +use core::num::imp::flt2dec; use core::num::imp::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, + DecodableFloat, Decoded, MAX_SIG_DIGITS, UNLIMITED_RESOLUTION, decode, round_up, + to_exact_exp_str, to_exact_fixed_str, to_shortest_exp_str, to_shortest_str, }; use core::num::imp::fmt::{Formatted, Part}; use std::mem::MaybeUninit; -use std::{fmt, str}; - -use Sign::{Minus, MinusPlus}; use crate::num::{ldexp_f32, ldexp_f64}; +mod equiv; mod estimator; mod strategy { mod dragon; mod grisu; } -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:?}"), - } -} -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)) - ); +/// Assert format_short outcome. +macro_rules! check_short { + ($v:expr => $digits:expr, $pow10:expr) => { + let label = stringify!($v); + let dec = decode($v); + let want: (&[u8], i16) = ($digits, $pow10); - ($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),+ }) - ); - - ($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 = flt2dec::format_short(&dec, &mut buf); + assert_eq!(got, want, "short format of {label}"); + }; } -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),*); - }) +/// Assert format_fixed outcome with a specific resolution. +macro_rules! check_fixed_resolution { + ($v:expr, $resolution:expr => $digits:expr, $pow10:expr) => { + let label = stringify!($v); + let dec = decode($v); + let resolution: i16 = $resolution; + let want: (&[u8], i16) = ($digits, $pow10); + + let mut large_buf = [MaybeUninit::new(b'_'); 1024]; + let got = flt2dec::format_fixed(&dec, &mut large_buf, resolution); + assert_eq!(got, want, "short format of {label}"); + }; } -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),*); - }) +/// Assert format_fixed outcome with a matching buffer size. +macro_rules! check_fixed_buf_match { + ($v:expr => $digits:expr, $pow10:expr) => { + let label = stringify!($v); + let dec = decode($v); + let want: (&[u8], i16) = ($digits, $pow10); + + let mut buf = [MaybeUninit::new(b'_'); 1024]; + let mut matched_size = &mut buf[..want.0.len()]; + let got = flt2dec::format_fixed(&dec, &mut matched_size, UNLIMITED_RESOLUTION); + assert_eq!(got, want, "fixed format of {label}"); + }; } -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 - 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; +/// Assert format_fixed outcome including variants at a lower resolution. +/// An equals suffix ("=") on `$digits` triggers testing of infinite zeroes. +macro_rules! check_fixed_mix { + ($v:expr => $digits:expr, $pow10:expr) => ( + let (digits, inf_zero): (&[u8], bool) = { + let d: &[u8] = $digits; + match d.strip_suffix(b"=".as_slice()) { + Some(s) => (s, true), + None => (d, false), + } + }; + let pow10: i16 = $pow10; + + // Verify base assertion. + check_fixed_buf_match!($v => &digits, pow10); + check_fixed_resolution!($v, pow10 - digits.len() as i16 => &digits, pow10); + + let mut want_buf = [b'_'; 1024]; + + // Verify at a lower resolution with substrings of `$digits`. + for i in 1..(digits.len() - 1) { + want_buf[..i].copy_from_slice(&digits[..i]); + let mut want_pow10 = pow10; + if digits[i] >= b'5' { + // Include rounding-to-even case (with equals suffix). + if !(digits[i - 1] & 1 == 0 + && digits[i] == b'5' + && digits[i + 1] == b'=') + { + if let Some(_) = round_up(&mut want_buf[..i]) { + // All '9's got rounded up to an extra digit. + want_pow10 += 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); - } + check_fixed_buf_match!($v => &want_buf[..i], want_pow10); + check_fixed_resolution!($v, want_pow10 - i as i16 => &want_buf[..i], want_pow10); + } - // 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; - } else { - start = 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); - } + // Verify digit loss by resolution. + let skip_first = digits[0] > b'5'; + if skip_first { + check_fixed_resolution!($v, pow10 => b"1", pow10 + 1); + } + for i in i16::from(skip_first)..-10 { + // Digit loss must still maintain the exponent. + check_fixed_resolution!($v, pow10 - i => b"", pow10); + } - // 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'; + // Verify infinite zero digits. + if inf_zero { + let zero_trail = b"0000000"; + let trail_range = digits.len()..(digits.len() + zero_trail.len()); + want_buf[..trail_range.start].copy_from_slice(digits); + want_buf[trail_range.clone()].copy_from_slice(&zero_trail[..]); + for last in trail_range { + let end = last + 1; + check_fixed_buf_match!($v => &want_buf[..end], pow10); + check_fixed_resolution!($v, pow10 - end as i16 => &want_buf[..end], pow10); } - - 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); } - } -} - -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; -} - -#[cfg(target_has_reliable_f16)] -impl TestableFloat for f16 { - fn ldexpi(f: i64, exp: isize) -> Self { - f as Self * (exp as Self).exp2() - } -} - -impl TestableFloat for f32 { - fn ldexpi(f: i64, exp: isize) -> Self { - f as Self * (exp as Self).exp2() - } -} - -impl TestableFloat for f64 { - fn ldexpi(f: i64, exp: isize) -> Self { - f as Self * (exp as Self).exp2() - } -} - -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); -} - -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) - }; +macro_rules! check_coef_pow2 { + ($coef:expr, $pow2:expr => $digits:expr, $pow10:expr) => ( + check_fixed_buf_match!($coef * $pow2.exp2() => $digits, $pow10); + check_fixed_resolution!($coef * $pow2.exp2(), $pow10 - $digits.len() as i16 => $digits, $pow10); + ) } // in the following comments, three numbers are spaced by 1 ulp apart, @@ -225,327 +137,331 @@ 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), -{ +fn f16_short_sanity_test() { // 0.0999145507813 // 0.0999755859375 // 0.100036621094 - check_shortest!(f(0.1f16) => b"1", 0); + check_short!(0.1f16 => b"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!(1.0f16/3.0 => b"3333", 0); // 10^1 * 0.3138671875 // 10^1 * 0.3140625 // 10^1 * 0.3142578125 - check_shortest!(f(3.14f16) => b"314", 1); + check_short!(3.14f16 => b"314", 1); // 10^18 * 0.31415916243714048 // 10^18 * 0.314159196796878848 // 10^18 * 0.314159231156617216 - check_shortest!(f(3.1415e4f16) => b"3141", 5); + check_short!(3.1415e4f16 => b"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!(crate::num::ldexp_f16(1.0, 5) => b"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::MAX => b"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::MIN_POSITIVE => b"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!(crate::num::ldexp_f16(1.0, -24) => b"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), -{ +fn f16_fixed_sanity_test() { 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); + check_fixed_mix!(0.1f16 => b"999755859375=", -1); + check_fixed_mix!(0.5f16 => b"5=", 0); + check_fixed_mix!(1.0f16/3.0 => b"333251953125=", 0); + check_fixed_mix!(3.141f16 => b"3140625=", 1); + check_fixed_mix!(3.141e4f16 => b"31408=", 5); + check_fixed_mix!(f16::MAX => b"65504=", 5); + check_fixed_mix!(f16::MIN_POSITIVE => b"6103515625=", -4); + check_fixed_mix!(minf16 => b"59604644775390625", -7); - // FIXME(f16): these should gain the check_exact_one tests like `f32` and `f64` have, + // FIXME(f16): these should gain the check_coef_pow2 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] +fn f32_short_sanity_test() { // 0.0999999940395355224609375 // 0.100000001490116119384765625 // 0.10000000894069671630859375 - check_shortest!(f(0.1f32) => b"1", 0); + check_short!(0.1f32 => b"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!(1.0f32/3.0 => b"33333334", 0); // 10^1 * 0.31415917873382568359375 // 10^1 * 0.31415920257568359375 // 10^1 * 0.31415922641754150390625 - check_shortest!(f(3.141592f32) => b"3141592", 1); + check_short!(3.141592f32 => b"3141592", 1); // 10^18 * 0.31415916243714048 // 10^18 * 0.314159196796878848 // 10^18 * 0.314159231156617216 - check_shortest!(f(3.141592e17f32) => b"3141592", 18); + check_short!(3.141592e17f32 => b"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!(ldexp_f32(1.0, 25) => b"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::MAX => b"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::MIN_POSITIVE => b"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!(ldexp_f32(1.0, -149) => b"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] +fn f32_fixed_sanity_test() { + check_fixed_mix!(0.1f32 => b"100000001490116119384765625=", 0); + check_fixed_mix!(0.5f32 => b"5=", 0); + check_fixed_mix!(1.0f32/3.0 => b"3333333432674407958984375=", 0); + check_fixed_mix!(3.141592f32 => b"31415920257568359375=", 1); + check_fixed_mix!(3.141592e17f32 => b"314159196796878848=", 18); + check_fixed_mix!(f32::MAX => b"34028234663852885981170418348451692544=", 39); + check_fixed_mix!(f32::MIN_POSITIVE => b"1175494350822287507968736537222245677819", -37); + check_fixed_mix!(ldexp_f32(1.0, -149) => b"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_coef_pow2!(12676506_f32, -102_f32 => b"2", -23); + check_coef_pow2!(12676506_f32, -103_f32 => b"12", -23); + check_coef_pow2!(15445013_f32, 86_f32 => b"119", 34); + check_coef_pow2!(13734123_f32, -138_f32 => b"3941", -34); + check_coef_pow2!(12428269_f32, -130_f32 => b"91308", -32); + check_coef_pow2!(15334037_f32, -146_f32 => b"171900", -36); + check_coef_pow2!(11518287_f32, -41_f32 => b"5237910", -5); + check_coef_pow2!(12584953_f32, -145_f32 => b"28216440", -36); + check_coef_pow2!(15961084_f32, -125_f32 => b"375243281", -30); + check_coef_pow2!(14915817_f32, -146_f32 => b"1672120916", -36); + check_coef_pow2!(10845484_f32, -102_f32 => b"21388945814", -23); + check_coef_pow2!(16431059_f32, -61_f32 => b"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_coef_pow2!(16093626_f32, 69_f32 => b"1", 29); + check_coef_pow2!( 9983778_f32, 25_f32 => b"34", 15); + check_coef_pow2!(12745034_f32, 104_f32 => b"259", 39); + check_coef_pow2!(12706553_f32, 72_f32 => b"6001", 29); + check_coef_pow2!(11005028_f32, 45_f32 => b"38721", 21); + check_coef_pow2!(15059547_f32, 71_f32 => b"355584", 29); + check_coef_pow2!(16015691_f32, -99_f32 => b"2526831", -22); + check_coef_pow2!( 8667859_f32, 56_f32 => b"62458507", 24); + check_coef_pow2!(14855922_f32, -82_f32 => b"307213267", -17); + check_coef_pow2!(14855922_f32, -83_f32 => b"1536066333", -17); + check_coef_pow2!(10144164_f32, -110_f32 => b"78147796834", -26); + check_coef_pow2!(13248074_f32, 95_f32 => b"524810279937", 36); } -pub fn f64_shortest_sanity_test(mut f: F) -where - F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), -{ +#[test] +fn f64_short_sanity_test() { // 0.0999999999999999777955395074968691915273... // 0.1000000000000000055511151231257827021181... // 0.1000000000000000333066907387546962127089... - check_shortest!(f(0.1f64) => b"1", 0); + check_short!(0.1f64 => b"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!(100.0f64 => b"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!(1.0f64/3.0 => b"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!(1.00000762939453125f64 => b"10000076293945313", 1); // 10^1 * 0.3141591999999999718085064159822650253772... // 10^1 * 0.3141592000000000162174274009885266423225... // 10^1 * 0.3141592000000000606263483859947882592678... - check_shortest!(f(3.141592f64) => b"3141592", 1); + check_short!(3.141592f64 => b"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!(3.141592e17f64 => b"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!(ldexp_f64(1.0, 64) => b"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!(1.0e23f64 => b"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::MAX => b"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::MIN_POSITIVE => b"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!(ldexp_f64(1.0, -1074) => b"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); +/// 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. +#[test] +#[cfg_attr(all(not(target_env = "msvc"), miri), ignore)] // Miri is too slow +fn f64_fixed_sanity_test() { + check_fixed_mix!(0.1f64 => b"1000000000000000055511151231257827021182", 0); + check_fixed_mix!(0.45f64 => b"4500000000000000111022302462515654042363", 0); + check_fixed_mix!(0.5f64 => b"5=", 0); + check_fixed_mix!(0.95f64 => b"9499999999999999555910790149937383830547", 0); + check_fixed_mix!(100.0f64 => b"1=", 3); + check_fixed_mix!(999.5f64 => b"9995000000000000000000000000000000000000", 3); + check_fixed_mix!(1.0f64/3.0 => b"3333333333333333148296162562473909929395", 0); + check_fixed_mix!(3.141592f64 => b"3141592000000000162174274009885266423225", 1); + check_fixed_mix!(3.141592e17f64 => b"3141592=", 18); + check_fixed_mix!(1.0e23f64 => b"99999999999999991611392=", 23); + check_fixed_mix!(f64::MAX => b"1797693134862315708145274237317043567981", 309); + check_fixed_mix!(f64::MIN_POSITIVE => b"2225073858507201383090232717332404064219", -307); + check_fixed_mix!(ldexp_f64(1.0, -1074) => b"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_coef_pow2!(8511030020275656_f64, -342_f64 => b"9", -87); + check_coef_pow2!(5201988407066741_f64, -824_f64 => b"46", -232); + check_coef_pow2!(6406892948269899_f64, 237_f64 => b"141", 88); + check_coef_pow2!(8431154198732492_f64, 72_f64 => b"3981", 38); + check_coef_pow2!(6475049196144587_f64, 99_f64 => b"41040", 46); + check_coef_pow2!(8274307542972842_f64, 726_f64 => b"292084", 235); + check_coef_pow2!(5381065484265332_f64, -456_f64 => b"2891946", -121); + check_coef_pow2!(6761728585499734_f64, -1057_f64 => b"43787718", -302); + check_coef_pow2!(7976538478610756_f64, 376_f64 => b"122770163", 130); + check_coef_pow2!(5982403858958067_f64, 377_f64 => b"1841552452", 130); + check_coef_pow2!(5536995190630837_f64, 93_f64 => b"54835744350", 44); + check_coef_pow2!(7225450889282194_f64, 710_f64 => b"389190181146", 230); + check_coef_pow2!(7225450889282194_f64, 709_f64 => b"1945950905732", 230); + check_coef_pow2!(8703372741147379_f64, 117_f64 => b"14460958381605", 52); + check_coef_pow2!(8944262675275217_f64, -1001_f64 => b"417367747458531", -285); + check_coef_pow2!(7459803696087692_f64, -707_f64 => b"1107950772878888", -196); + check_coef_pow2!(6080469016670379_f64, -381_f64 => b"12345501366327440", -98); + check_coef_pow2!(8385515147034757_f64, 721_f64 => b"925031711960365024", 233); + check_coef_pow2!(7514216811389786_f64, -828_f64 => b"4198047150284889840", -233); + check_coef_pow2!(8397297803260511_f64, -345_f64 => b"11716315319786511046", -87); + check_coef_pow2!(6733459239310543_f64, 202_f64 => b"432810072844612493629", 77); + check_coef_pow2!(8091450587292794_f64, -473_f64 => b"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); + check_coef_pow2!(6567258882077402_f64, 952_f64 => b"3", 303); + check_coef_pow2!(6712731423444934_f64, 535_f64 => b"76", 177); + check_coef_pow2!(6712731423444934_f64, 534_f64 => b"378", 177); + check_coef_pow2!(5298405411573037_f64, -957_f64 => b"4350", -272); + check_coef_pow2!(5137311167659507_f64, -144_f64 => b"23037", -27); + check_coef_pow2!(6722280709661868_f64, 363_f64 => b"126301", 126); + check_coef_pow2!(5344436398034927_f64, -169_f64 => b"7142211", -35); + check_coef_pow2!(8369123604277281_f64, -853_f64 => b"13934574", -240); + check_coef_pow2!(8995822108487663_f64, -780_f64 => b"141463449", -218); + check_coef_pow2!(8942832835564782_f64, -383_f64 => b"4539277920", -99); + check_coef_pow2!(8942832835564782_f64, -384_f64 => b"22696389598", -99); + check_coef_pow2!(8942832835564782_f64, -385_f64 => b"113481947988", -99); + check_coef_pow2!(6965949469487146_f64, -249_f64 => b"7700366561890", -59); + check_coef_pow2!(6965949469487146_f64, -250_f64 => b"38501832809448", -59); + check_coef_pow2!(6965949469487146_f64, -251_f64 => b"192509164047238", -59); + check_coef_pow2!(7487252720986826_f64, 548_f64 => b"6898586531774201", 181); + check_coef_pow2!(5592117679628511_f64, 164_f64 => b"13076622631878654", 66); + check_coef_pow2!(8887055249355788_f64, 665_f64 => b"136052020756121240", 217); + check_coef_pow2!(6994187472632449_f64, 690_f64 => b"3592810217475959676", 224); + check_coef_pow2!(8797576579012143_f64, 588_f64 => b"89125197712484551899", 193); + check_coef_pow2!(7363326733505337_f64, 272_f64 => b"558769757362301140950", 98); + check_coef_pow2!(8549497411294502_f64, -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); +#[test] +fn more_short_sanity_test() { + let mut buf = [MaybeUninit::new(b'_'); MAX_SIG_DIGITS]; + assert_eq!( + flt2dec::format_short( + &Decoded { mant: 99_999_999_999_999_999, minus: 1, plus: 1, exp: 0, inclusive: true }, + &mut buf, + ), + ("1".as_bytes(), 18), + ); + assert_eq!( + flt2dec::format_short( + &Decoded { mant: 99_999_999_999_999_999, minus: 1, plus: 1, exp: 0, inclusive: false }, + &mut buf, + ), + ("99999999999999999".as_bytes(), 17), + ); } fn to_string_with_parts(mut f: F) -> String @@ -564,7 +480,7 @@ pub fn to_shortest_str_test(mut f_: F) where F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), { - fn to_string(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String + fn to_string(f: &mut F, v: T, sign: &'static str, frac_digits: usize) -> String where T: DecodableFloat, F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), @@ -576,87 +492,62 @@ where 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"); + assert_eq!(to_string(f, 3.14, "", 0), "3.14"); + assert_eq!(to_string(f, 3.14, "+", 0), "+3.14"); + assert_eq!(to_string(f, -3.14, "-", 0), "-3.14"); + assert_eq!(to_string(f, -3.14, "-", 0), "-3.14"); + assert_eq!(to_string(f, 3.14, "", 1), "3.14"); + assert_eq!(to_string(f, 3.14, "+", 4), "+3.1400"); + assert_eq!(to_string(f, -3.14, "-", 8), "-3.14000000"); + assert_eq!(to_string(f, -3.14, "-", 8), "-3.14000000"); + + assert_eq!(to_string(f, 7.5e-11, "", 0), "0.000000000075"); + assert_eq!(to_string(f, 7.5e-11, "", 3), "0.000000000075"); + assert_eq!(to_string(f, 7.5e-11, "", 12), "0.000000000075"); + assert_eq!(to_string(f, 7.5e-11, "", 13), "0.0000000000750"); + + assert_eq!(to_string(f, 1.9971e20, "", 0), "199710000000000000000"); + assert_eq!(to_string(f, 1.9971e20, "", 1), "199710000000000000000.0"); + assert_eq!(to_string(f, 1.9971e20, "", 8), "199710000000000000000.00000000"); #[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!(to_string(f, f16::MAX, "", 0), "65500"); + assert_eq!(to_string(f, f16::MAX, "", 1), "65500.0"); + assert_eq!(to_string(f, f16::MAX, "", 8), "65500.00000000"); 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"); + assert_eq!(to_string(f, minf16, "", 0), "0.00000006"); + assert_eq!(to_string(f, minf16, "", 8), "0.00000006"); + assert_eq!(to_string(f, minf16, "", 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", "")); + assert_eq!(to_string(f, f32::MAX, "", 0), format!("34028235{:0>31}", "")); + assert_eq!(to_string(f, f32::MAX, "", 1), format!("34028235{:0>31}.0", "")); + assert_eq!(to_string(f, f32::MAX, "", 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!(to_string(f, minf32, "", 0), format!("0.{:0>44}1", "")); + assert_eq!(to_string(f, minf32, "", 45), format!("0.{:0>44}1", "")); + assert_eq!(to_string(f, minf32, "", 46), format!("0.{:0>44}10", "")); } { // 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, "", 0), format!("17976931348623157{:0>292}", "")); + assert_eq!(to_string(f, f64::MAX, "", 1), format!("17976931348623157{:0>292}.0", "")); assert_eq!( - to_string(f, f64::MAX, Minus, 8), + to_string(f, f64::MAX, "", 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", "")); + assert_eq!(to_string(f, minf64, "", 0), format!("0.{:0>323}5", "")); + assert_eq!(to_string(f, minf64, "", 324), format!("0.{:0>323}5", "")); + assert_eq!(to_string(f, minf64, "", 325), format!("0.{:0>323}50", "")); } if cfg!(miri) { @@ -665,14 +556,20 @@ where } // very large output - assert_eq!(to_string(f, 1.1, Minus, 50000), format!("1.1{:0>49999}", "")); + assert_eq!(to_string(f, 1.1, "", 50000), format!("1.1{:0>49999}", "")); } pub fn to_shortest_exp_str_test(mut f_: F) where F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), { - fn to_string(f: &mut F, v: T, sign: Sign, exp_bounds: (i16, i16), upper: bool) -> String + fn to_string( + f: &mut F, + v: T, + sign: &'static str, + exp_bounds: (i16, i16), + upper: bool, + ) -> String where T: DecodableFloat, F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), @@ -684,110 +581,88 @@ where 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"); + assert_eq!(to_string(f, 3.14, "", (-4, 16), false), "3.14"); + assert_eq!(to_string(f, 3.14, "+", (-4, 16), false), "+3.14"); + assert_eq!(to_string(f, -3.14, "-", (-4, 16), false), "-3.14"); + assert_eq!(to_string(f, -3.14, "-", (-4, 16), false), "-3.14"); + assert_eq!(to_string(f, 3.14, "", (0, 0), true), "3.14E0"); + assert_eq!(to_string(f, 3.14, "", (0, 0), false), "3.14e0"); + assert_eq!(to_string(f, 3.14, "+", (5, 9), false), "+3.14e0"); + assert_eq!(to_string(f, -3.14, "-", (0, 0), true), "-3.14E0"); + assert_eq!(to_string(f, -3.14, "-", (0, 0), false), "-3.14e0"); + assert_eq!(to_string(f, -3.14, "-", (5, 9), false), "-3.14e0"); + + assert_eq!(to_string(f, 0.1, "", (-4, 16), false), "0.1"); + assert_eq!(to_string(f, 0.1, "+", (-4, 16), false), "+0.1"); + assert_eq!(to_string(f, -0.1, "-", (-4, 16), false), "-0.1"); + assert_eq!(to_string(f, -0.1, "-", (-4, 16), false), "-0.1"); + assert_eq!(to_string(f, 0.1, "", (0, 0), true), "1E-1"); + assert_eq!(to_string(f, 0.1, "", (0, 0), false), "1e-1"); + assert_eq!(to_string(f, 0.1, "+", (5, 9), false), "+1e-1"); + assert_eq!(to_string(f, -0.1, "-", (0, 0), true), "-1E-1"); + assert_eq!(to_string(f, -0.1, "-", (0, 0), false), "-1e-1"); + assert_eq!(to_string(f, -0.1, "-", (5, 9), false), "-1e-1"); + + assert_eq!(to_string(f, 7.5e-11, "", (-4, 16), false), "7.5e-11"); + assert_eq!(to_string(f, 7.5e-11, "", (-11, 10), false), "0.000000000075"); + assert_eq!(to_string(f, 7.5e-11, "", (-10, 11), false), "7.5e-11"); + + assert_eq!(to_string(f, 1.9971e20, "", (-4, 16), false), "1.9971e20"); + assert_eq!(to_string(f, 1.9971e20, "", (-20, 21), false), "199710000000000000000"); + assert_eq!(to_string(f, 1.9971e20, "", (-21, 20), false), "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!(to_string(f, 1.0e23, "", (22, 23), false), "1e23"); + assert_eq!(to_string(f, 1.0e23, "", (23, 24), false), "100000000000000000000000"); + assert_eq!(to_string(f, 1.0e23, "", (24, 25), false), "1e23"); #[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!(to_string(f, f16::MAX, "", (-2, 2), false), "6.55e4"); + assert_eq!(to_string(f, f16::MAX, "", (-4, 4), false), "6.55e4"); + assert_eq!(to_string(f, f16::MAX, "", (-5, 5), false), "65500"); 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!(to_string(f, minf16, "", (-2, 2), false), "6e-8"); + assert_eq!(to_string(f, minf16, "", (-7, 7), false), "6e-8"); + assert_eq!(to_string(f, minf16, "", (-8, 8), false), "0.00000006"); } { // 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}", "")); + assert_eq!(to_string(f, f32::MAX, "", (-4, 16), false), "3.4028235e38"); + assert_eq!(to_string(f, f32::MAX, "", (-39, 38), false), "3.4028235e38"); + assert_eq!(to_string(f, f32::MAX, "", (-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", "")); + assert_eq!(to_string(f, minf32, "", (-4, 16), false), "1e-45"); + assert_eq!(to_string(f, minf32, "", (-44, 45), false), "1e-45"); + assert_eq!(to_string(f, minf32, "", (-45, 44), false), format!("0.{:0>44}1", "")); } { // f64 - assert_eq!(to_string(f, f64::MAX, Minus, (-4, 16), false), "1.7976931348623157e308"); + assert_eq!(to_string(f, f64::MAX, "", (-4, 16), false), "1.7976931348623157e308"); assert_eq!( - to_string(f, f64::MAX, Minus, (-308, 309), false), + to_string(f, f64::MAX, "", (-308, 309), false), format!("17976931348623157{:0>292}", "") ); - assert_eq!(to_string(f, f64::MAX, Minus, (-309, 308), false), "1.7976931348623157e308"); + assert_eq!(to_string(f, f64::MAX, "", (-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, minf64, "", (-4, 16), false), "5e-324"); + assert_eq!(to_string(f, minf64, "", (-324, 323), false), format!("0.{:0>323}5", "")); + assert_eq!(to_string(f, minf64, "", (-323, 324), false), "5e-324"); } - assert_eq!(to_string(f, 1.1, Minus, (i16::MIN, i16::MAX), false), "1.1"); + assert_eq!(to_string(f, 1.1, "", (i16::MIN, i16::MAX), false), "1.1"); } pub fn to_exact_exp_str_test(mut f_: F) where F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), { - fn to_string(f: &mut F, v: T, sign: Sign, ndigits: usize, upper: bool) -> String + fn to_string(f: &mut F, v: T, sign: &'static str, ndigits: usize, upper: bool) -> String where T: DecodableFloat, F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), @@ -807,146 +682,120 @@ where 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, 3.14, "", 1, true), "3E0"); + assert_eq!(to_string(f, 3.14, "", 1, false), "3e0"); + assert_eq!(to_string(f, 3.14, "+", 1, false), "+3e0"); + assert_eq!(to_string(f, -3.14, "-", 2, true), "-3.1E0"); + assert_eq!(to_string(f, -3.14, "-", 2, false), "-3.1e0"); + assert_eq!(to_string(f, 3.14, "", 3, true), "3.14E0"); + assert_eq!(to_string(f, 3.14, "", 3, false), "3.14e0"); + assert_eq!(to_string(f, 3.14, "+", 3, false), "+3.14e0"); + assert_eq!(to_string(f, -3.14, "-", 4, true), "-3.140E0"); + assert_eq!(to_string(f, -3.14, "-", 4, false), "-3.140e0"); + + assert_eq!(to_string(f, 0.195, "", 1, false), "2e-1"); + assert_eq!(to_string(f, 0.195, "", 1, true), "2E-1"); + assert_eq!(to_string(f, 0.195, "+", 1, true), "+2E-1"); + assert_eq!(to_string(f, -0.195, "-", 2, false), "-2.0e-1"); + assert_eq!(to_string(f, -0.195, "-", 2, true), "-2.0E-1"); + assert_eq!(to_string(f, -0.195, "-", 2, true), "-2.0E-1"); + assert_eq!(to_string(f, 0.195, "", 3, false), "1.95e-1"); + assert_eq!(to_string(f, 0.195, "", 3, true), "1.95E-1"); + assert_eq!(to_string(f, 0.195, "+", 3, true), "+1.95E-1"); + assert_eq!(to_string(f, -0.195, "-", 4, false), "-1.950e-1"); + assert_eq!(to_string(f, -0.195, "-", 4, true), "-1.950E-1"); + assert_eq!(to_string(f, -0.195, "-", 4, true), "-1.950E-1"); + + assert_eq!(to_string(f, 9.5, "", 1, false), "1e1"); + assert_eq!(to_string(f, 9.5, "", 2, false), "9.5e0"); + assert_eq!(to_string(f, 9.5, "", 3, false), "9.50e0"); + assert_eq!(to_string(f, 9.5, "", 30, false), "9.50000000000000000000000000000e0"); + + assert_eq!(to_string(f, 1.0e25, "", 1, false), "1e25"); + assert_eq!(to_string(f, 1.0e25, "", 2, false), "1.0e25"); + assert_eq!(to_string(f, 1.0e25, "", 15, false), "1.00000000000000e25"); + assert_eq!(to_string(f, 1.0e25, "", 16, false), "1.000000000000000e25"); + assert_eq!(to_string(f, 1.0e25, "", 17, false), "1.0000000000000001e25"); + assert_eq!(to_string(f, 1.0e25, "", 18, false), "1.00000000000000009e25"); + assert_eq!(to_string(f, 1.0e25, "", 19, false), "1.000000000000000091e25"); + assert_eq!(to_string(f, 1.0e25, "", 20, false), "1.0000000000000000906e25"); + assert_eq!(to_string(f, 1.0e25, "", 21, false), "1.00000000000000009060e25"); + assert_eq!(to_string(f, 1.0e25, "", 22, false), "1.000000000000000090597e25"); + assert_eq!(to_string(f, 1.0e25, "", 23, false), "1.0000000000000000905970e25"); + assert_eq!(to_string(f, 1.0e25, "", 24, false), "1.00000000000000009059697e25"); + assert_eq!(to_string(f, 1.0e25, "", 25, false), "1.000000000000000090596966e25"); + assert_eq!(to_string(f, 1.0e25, "", 26, false), "1.0000000000000000905969664e25"); + assert_eq!(to_string(f, 1.0e25, "", 27, false), "1.00000000000000009059696640e25"); + assert_eq!(to_string(f, 1.0e25, "", 30, false), "1.00000000000000009059696640000e25"); + + assert_eq!(to_string(f, 1.0e-6, "", 1, false), "1e-6"); + assert_eq!(to_string(f, 1.0e-6, "", 2, false), "1.0e-6"); + assert_eq!(to_string(f, 1.0e-6, "", 16, false), "1.000000000000000e-6"); + assert_eq!(to_string(f, 1.0e-6, "", 17, false), "9.9999999999999995e-7"); + assert_eq!(to_string(f, 1.0e-6, "", 18, false), "9.99999999999999955e-7"); + assert_eq!(to_string(f, 1.0e-6, "", 19, false), "9.999999999999999547e-7"); + assert_eq!(to_string(f, 1.0e-6, "", 20, false), "9.9999999999999995475e-7"); + assert_eq!(to_string(f, 1.0e-6, "", 30, false), "9.99999999999999954748111825886e-7"); + assert_eq!(to_string(f, 1.0e-6, "", 40, false), "9.999999999999999547481118258862586856139e-7"); assert_eq!( - to_string(f, 1.0e-6, Minus, 40, false), - "9.999999999999999547481118258862586856139e-7" - ); - assert_eq!( - to_string(f, 1.0e-6, Minus, 50, false), + to_string(f, 1.0e-6, "", 50, false), "9.9999999999999995474811182588625868561393872369081e-7" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 60, false), + to_string(f, 1.0e-6, "", 60, false), "9.99999999999999954748111825886258685613938723690807819366455e-7" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 70, false), + to_string(f, 1.0e-6, "", 70, false), "9.999999999999999547481118258862586856139387236908078193664550781250000e-7" ); #[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!(to_string(f, f16::MAX, "", 1, false), "7e4"); + assert_eq!(to_string(f, f16::MAX, "", 2, false), "6.6e4"); + assert_eq!(to_string(f, f16::MAX, "", 4, false), "6.550e4"); + assert_eq!(to_string(f, f16::MAX, "", 5, false), "6.5504e4"); + assert_eq!(to_string(f, f16::MAX, "", 6, false), "6.55040e4"); + assert_eq!(to_string(f, f16::MAX, "", 16, false), "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!(to_string(f, minf16, "", 1, false), "6e-8"); + assert_eq!(to_string(f, minf16, "", 2, false), "6.0e-8"); + assert_eq!(to_string(f, minf16, "", 4, false), "5.960e-8"); + assert_eq!(to_string(f, minf16, "", 8, false), "5.9604645e-8"); + assert_eq!(to_string(f, minf16, "", 16, false), "5.960464477539062e-8"); + assert_eq!(to_string(f, minf16, "", 17, false), "5.9604644775390625e-8"); + assert_eq!(to_string(f, minf16, "", 18, false), "5.96046447753906250e-8"); + assert_eq!(to_string(f, minf16, "", 24, false), "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"); + assert_eq!(to_string(f, f32::MAX, "", 1, false), "3e38"); + assert_eq!(to_string(f, f32::MAX, "", 2, false), "3.4e38"); + assert_eq!(to_string(f, f32::MAX, "", 4, false), "3.403e38"); + assert_eq!(to_string(f, f32::MAX, "", 8, false), "3.4028235e38"); + assert_eq!(to_string(f, f32::MAX, "", 16, false), "3.402823466385289e38"); + assert_eq!(to_string(f, f32::MAX, "", 32, false), "3.4028234663852885981170418348452e38"); assert_eq!( - to_string(f, f32::MAX, Minus, 64, false), + to_string(f, f32::MAX, "", 64, false), "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!(to_string(f, minf32, "", 1, false), "1e-45"); + assert_eq!(to_string(f, minf32, "", 2, false), "1.4e-45"); + assert_eq!(to_string(f, minf32, "", 4, false), "1.401e-45"); + assert_eq!(to_string(f, minf32, "", 8, false), "1.4012985e-45"); + assert_eq!(to_string(f, minf32, "", 16, false), "1.401298464324817e-45"); + assert_eq!(to_string(f, minf32, "", 32, false), "1.4012984643248170709237295832899e-45"); assert_eq!( - to_string(f, minf32, Minus, 64, false), + to_string(f, minf32, "", 64, false), "1.401298464324817070923729583289916131280261941876515771757068284e-45" ); assert_eq!( - to_string(f, minf32, Minus, 128, false), + to_string(f, minf32, "", 128, false), "1.401298464324817070923729583289916131280261941876515771757068283\ - 8897910826858606014866381883621215820312500000000000000000000000e-45" + 8897910826858606014866381883621215820312500000000000000000000000e-45" ); if cfg!(miri) { @@ -954,30 +803,30 @@ 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"); + assert_eq!(to_string(f, f64::MAX, "", 1, false), "2e308"); + assert_eq!(to_string(f, f64::MAX, "", 2, false), "1.8e308"); + assert_eq!(to_string(f, f64::MAX, "", 4, false), "1.798e308"); + assert_eq!(to_string(f, f64::MAX, "", 8, false), "1.7976931e308"); + assert_eq!(to_string(f, f64::MAX, "", 16, false), "1.797693134862316e308"); + assert_eq!(to_string(f, f64::MAX, "", 32, false), "1.7976931348623157081452742373170e308"); assert_eq!( - to_string(f, f64::MAX, Minus, 64, false), + to_string(f, f64::MAX, "", 64, false), "1.797693134862315708145274237317043567980705675258449965989174768e308" ); assert_eq!( - to_string(f, f64::MAX, Minus, 128, false), + to_string(f, f64::MAX, "", 128, false), "1.797693134862315708145274237317043567980705675258449965989174768\ 0315726078002853876058955863276687817154045895351438246423432133e308" ); assert_eq!( - to_string(f, f64::MAX, Minus, 256, false), + to_string(f, f64::MAX, "", 256, false), "1.797693134862315708145274237317043567980705675258449965989174768\ 0315726078002853876058955863276687817154045895351438246423432132\ 6889464182768467546703537516986049910576551282076245490090389328\ 9440758685084551339423045832369032229481658085593321233482747978e308" ); assert_eq!( - to_string(f, f64::MAX, Minus, 512, false), + to_string(f, f64::MAX, "", 512, false), "1.797693134862315708145274237317043567980705675258449965989174768\ 0315726078002853876058955863276687817154045895351438246423432132\ 6889464182768467546703537516986049910576551282076245490090389328\ @@ -990,65 +839,64 @@ where // 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!(to_string(f, minf64, "", 1, false), "5e-324"); + assert_eq!(to_string(f, minf64, "", 2, false), "4.9e-324"); + assert_eq!(to_string(f, minf64, "", 4, false), "4.941e-324"); + assert_eq!(to_string(f, minf64, "", 8, false), "4.9406565e-324"); + assert_eq!(to_string(f, minf64, "", 16, false), "4.940656458412465e-324"); + assert_eq!(to_string(f, minf64, "", 32, false), "4.9406564584124654417656879286822e-324"); assert_eq!( - to_string(f, minf64, Minus, 64, false), + to_string(f, minf64, "", 64, false), "4.940656458412465441765687928682213723650598026143247644255856825e-324" ); assert_eq!( - to_string(f, minf64, Minus, 128, false), + to_string(f, minf64, "", 128, false), "4.940656458412465441765687928682213723650598026143247644255856825\ - 0067550727020875186529983636163599237979656469544571773092665671e-324" + 0067550727020875186529983636163599237979656469544571773092665671e-324" ); assert_eq!( - to_string(f, minf64, Minus, 256, false), + to_string(f, minf64, "", 256, false), "4.940656458412465441765687928682213723650598026143247644255856825\ - 0067550727020875186529983636163599237979656469544571773092665671\ - 0355939796398774796010781878126300713190311404527845817167848982\ - 1036887186360569987307230500063874091535649843873124733972731696e-324" + 0067550727020875186529983636163599237979656469544571773092665671\ + 0355939796398774796010781878126300713190311404527845817167848982\ + 1036887186360569987307230500063874091535649843873124733972731696e-324" ); assert_eq!( - to_string(f, minf64, Minus, 512, false), + to_string(f, minf64, "", 512, false), "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), + to_string(f, minf64, "", 1024, false), "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!(to_string(f, 1.0e1, "", 50000, false), format!("1.{:0>49999}e1", "")); + assert_eq!(to_string(f, 1.0e0, "", 50000, false), format!("1.{:0>49999}e0", "")); assert_eq!( - to_string(f, 1.0e-1, Minus, 50000, false), + to_string(f, 1.0e-1, "", 50000, false), format!( "1.000000000000000055511151231257827021181583404541015625{:0>49945}\ e-1", @@ -1056,7 +904,7 @@ where ) ); assert_eq!( - to_string(f, 1.0e-20, Minus, 50000, false), + to_string(f, 1.0e-20, "", 50000, false), format!( "9.999999999999999451532714542095716517295037027873924471077157760\ 66783064379706047475337982177734375{:0>49901}e-21", @@ -1069,7 +917,7 @@ pub fn to_exact_fixed_str_test(mut f_: F) where F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), { - fn to_string(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String + fn to_string(f: &mut F, v: T, sign: &'static str, frac_digits: usize) -> String where T: DecodableFloat, F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), @@ -1081,132 +929,111 @@ where 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"); + assert_eq!(to_string(f, 3.14, "", 0), "3"); + assert_eq!(to_string(f, 3.14, "+", 0), "+3"); + assert_eq!(to_string(f, -3.14, "-", 0), "-3"); + assert_eq!(to_string(f, -3.14, "-", 0), "-3"); + assert_eq!(to_string(f, 3.14, "", 1), "3.1"); + assert_eq!(to_string(f, 3.14, "", 2), "3.14"); + assert_eq!(to_string(f, 3.14, "+", 4), "+3.1400"); + assert_eq!(to_string(f, -3.14, "-", 8), "-3.14000000"); + assert_eq!(to_string(f, -3.14, "-", 8), "-3.14000000"); + assert_eq!(to_string(f, -3.14, "-", 8), "-3.14000000"); + + assert_eq!(to_string(f, 0.195, "", 0), "0"); + assert_eq!(to_string(f, 0.195, "+", 0), "+0"); + assert_eq!(to_string(f, -0.195, "-", 0), "-0"); + assert_eq!(to_string(f, -0.195, "-", 0), "-0"); + assert_eq!(to_string(f, -0.195, "-", 0), "-0"); + assert_eq!(to_string(f, 0.195, "", 1), "0.2"); + assert_eq!(to_string(f, 0.195, "", 2), "0.20"); + assert_eq!(to_string(f, 0.195, "+", 4), "+0.1950"); + assert_eq!(to_string(f, -0.195, "-", 5), "-0.19500"); + assert_eq!(to_string(f, -0.195, "-", 6), "-0.195000"); + assert_eq!(to_string(f, -0.195, "-", 8), "-0.19500000"); + + assert_eq!(to_string(f, 999.5, "", 0), "1000"); + assert_eq!(to_string(f, 999.5, "", 1), "999.5"); + assert_eq!(to_string(f, 999.5, "", 2), "999.50"); + assert_eq!(to_string(f, 999.5, "", 3), "999.500"); + assert_eq!(to_string(f, 999.5, "", 30), "999.500000000000000000000000000000"); + + assert_eq!(to_string(f, 0.5, "", 0), "0"); + assert_eq!(to_string(f, 0.5, "", 1), "0.5"); + assert_eq!(to_string(f, 0.5, "", 2), "0.50"); + assert_eq!(to_string(f, 0.5, "", 3), "0.500"); + + assert_eq!(to_string(f, 0.95, "", 0), "1"); + assert_eq!(to_string(f, 0.95, "", 1), "0.9"); // because it really is less than 0.95 + assert_eq!(to_string(f, 0.95, "", 2), "0.95"); + assert_eq!(to_string(f, 0.95, "", 3), "0.950"); + assert_eq!(to_string(f, 0.95, "", 10), "0.9500000000"); + assert_eq!(to_string(f, 0.95, "", 30), "0.949999999999999955591079014994"); + + assert_eq!(to_string(f, 0.095, "", 0), "0"); + assert_eq!(to_string(f, 0.095, "", 1), "0.1"); + assert_eq!(to_string(f, 0.095, "", 2), "0.10"); + assert_eq!(to_string(f, 0.095, "", 3), "0.095"); + assert_eq!(to_string(f, 0.095, "", 4), "0.0950"); + assert_eq!(to_string(f, 0.095, "", 10), "0.0950000000"); + assert_eq!(to_string(f, 0.095, "", 30), "0.095000000000000001110223024625"); + + assert_eq!(to_string(f, 0.0095, "", 0), "0"); + assert_eq!(to_string(f, 0.0095, "", 1), "0.0"); + assert_eq!(to_string(f, 0.0095, "", 2), "0.01"); + assert_eq!(to_string(f, 0.0095, "", 3), "0.009"); // really is less than 0.0095 + assert_eq!(to_string(f, 0.0095, "", 4), "0.0095"); + assert_eq!(to_string(f, 0.0095, "", 5), "0.00950"); + assert_eq!(to_string(f, 0.0095, "", 10), "0.0095000000"); + assert_eq!(to_string(f, 0.0095, "", 30), "0.009499999999999999764077607267"); + + assert_eq!(to_string(f, 7.5e-11, "", 0), "0"); + assert_eq!(to_string(f, 7.5e-11, "", 3), "0.000"); + assert_eq!(to_string(f, 7.5e-11, "", 10), "0.0000000001"); + assert_eq!(to_string(f, 7.5e-11, "", 11), "0.00000000007"); // ditto + assert_eq!(to_string(f, 7.5e-11, "", 12), "0.000000000075"); + assert_eq!(to_string(f, 7.5e-11, "", 13), "0.0000000000750"); + assert_eq!(to_string(f, 7.5e-11, "", 20), "0.00000000007500000000"); + assert_eq!(to_string(f, 7.5e-11, "", 30), "0.000000000074999999999999999501"); + + assert_eq!(to_string(f, 1.0e25, "", 0), "10000000000000000905969664"); + assert_eq!(to_string(f, 1.0e25, "", 1), "10000000000000000905969664.0"); + assert_eq!(to_string(f, 1.0e25, "", 3), "10000000000000000905969664.000"); + + assert_eq!(to_string(f, 1.0e-6, "", 0), "0"); + assert_eq!(to_string(f, 1.0e-6, "", 3), "0.000"); + assert_eq!(to_string(f, 1.0e-6, "", 6), "0.000001"); + assert_eq!(to_string(f, 1.0e-6, "", 9), "0.000001000"); + assert_eq!(to_string(f, 1.0e-6, "", 12), "0.000001000000"); + assert_eq!(to_string(f, 1.0e-6, "", 22), "0.0000010000000000000000"); + assert_eq!(to_string(f, 1.0e-6, "", 23), "0.00000099999999999999995"); + assert_eq!(to_string(f, 1.0e-6, "", 24), "0.000000999999999999999955"); + assert_eq!(to_string(f, 1.0e-6, "", 25), "0.0000009999999999999999547"); + assert_eq!(to_string(f, 1.0e-6, "", 35), "0.00000099999999999999995474811182589"); + assert_eq!(to_string(f, 1.0e-6, "", 45), "0.000000999999999999999954748111825886258685614"); assert_eq!( - to_string(f, 1.0e-6, Minus, 55), + to_string(f, 1.0e-6, "", 55), "0.0000009999999999999999547481118258862586856139387236908" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 65), + to_string(f, 1.0e-6, "", 65), "0.00000099999999999999995474811182588625868561393872369080781936646" ); assert_eq!( - to_string(f, 1.0e-6, Minus, 75), + to_string(f, 1.0e-6, "", 75), "0.000000999999999999999954748111825886258685613938723690807819366455078125000" ); #[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!(to_string(f, f16::MAX, "", 0), "65504"); + assert_eq!(to_string(f, f16::MAX, "", 1), "65504.0"); + assert_eq!(to_string(f, f16::MAX, "", 2), "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"); + assert_eq!(to_string(f, f32::MAX, "", 0), "340282346638528859811704183484516925440"); + assert_eq!(to_string(f, f32::MAX, "", 1), "340282346638528859811704183484516925440.0"); + assert_eq!(to_string(f, f32::MAX, "", 2), "340282346638528859811704183484516925440.00"); if cfg!(miri) { // Miri is too slow @@ -1216,99 +1043,98 @@ 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!(to_string(f, minf16, "", 0), "0"); + assert_eq!(to_string(f, minf16, "", 1), "0.0"); + assert_eq!(to_string(f, minf16, "", 2), "0.00"); + assert_eq!(to_string(f, minf16, "", 4), "0.0000"); + assert_eq!(to_string(f, minf16, "", 8), "0.00000006"); + assert_eq!(to_string(f, minf16, "", 10), "0.0000000596"); + assert_eq!(to_string(f, minf16, "", 15), "0.000000059604645"); + assert_eq!(to_string(f, minf16, "", 20), "0.00000005960464477539"); + assert_eq!(to_string(f, minf16, "", 24), "0.000000059604644775390625"); + assert_eq!(to_string(f, minf16, "", 32), "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!(to_string(f, minf32, "", 0), "0"); + assert_eq!(to_string(f, minf32, "", 1), "0.0"); + assert_eq!(to_string(f, minf32, "", 2), "0.00"); + assert_eq!(to_string(f, minf32, "", 4), "0.0000"); + assert_eq!(to_string(f, minf32, "", 8), "0.00000000"); + assert_eq!(to_string(f, minf32, "", 16), "0.0000000000000000"); + assert_eq!(to_string(f, minf32, "", 32), "0.00000000000000000000000000000000"); assert_eq!( - to_string(f, minf32, Minus, 64), + to_string(f, minf32, "", 64), "0.0000000000000000000000000000000000000000000014012984643248170709" ); assert_eq!( - to_string(f, minf32, Minus, 128), + to_string(f, minf32, "", 128), "0.0000000000000000000000000000000000000000000014012984643248170709\ - 2372958328991613128026194187651577175706828388979108268586060149" + 2372958328991613128026194187651577175706828388979108268586060149" ); assert_eq!( - to_string(f, minf32, Minus, 256), + to_string(f, minf32, "", 256), "0.0000000000000000000000000000000000000000000014012984643248170709\ - 2372958328991613128026194187651577175706828388979108268586060148\ - 6638188362121582031250000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000000000000000000000000000" + 2372958328991613128026194187651577175706828388979108268586060148\ + 6638188362121582031250000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000" ); assert_eq!( - to_string(f, f64::MAX, Minus, 0), + to_string(f, f64::MAX, "", 0), "1797693134862315708145274237317043567980705675258449965989174768\ - 0315726078002853876058955863276687817154045895351438246423432132\ - 6889464182768467546703537516986049910576551282076245490090389328\ - 9440758685084551339423045832369032229481658085593321233482747978\ - 26204144723168738177180919299881250404026184124858368" + 0315726078002853876058955863276687817154045895351438246423432132\ + 6889464182768467546703537516986049910576551282076245490090389328\ + 9440758685084551339423045832369032229481658085593321233482747978\ + 26204144723168738177180919299881250404026184124858368" ); assert_eq!( - to_string(f, f64::MAX, Minus, 10), + to_string(f, f64::MAX, "", 10), "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!(to_string(f, minf64, "", 0), "0"); + assert_eq!(to_string(f, minf64, "", 1), "0.0"); + assert_eq!(to_string(f, minf64, "", 10), "0.0000000000"); assert_eq!( - to_string(f, minf64, Minus, 100), + to_string(f, minf64, "", 100), "0.0000000000000000000000000000000000000000000000000000000000000000\ - 000000000000000000000000000000000000" + 000000000000000000000000000000000000" ); assert_eq!( - to_string(f, minf64, Minus, 1000), + to_string(f, minf64, "", 1000), "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!(to_string(f, 1.0e1, "", 50000), format!("10.{:0>50000}", "")); + assert_eq!(to_string(f, 1.0e0, "", 50000), format!("1.{:0>50000}", "")); assert_eq!( - to_string(f, 1.0e-1, Minus, 50000), + to_string(f, 1.0e-1, "", 50000), format!("0.1000000000000000055511151231257827021181583404541015625{:0>49945}", "") ); assert_eq!( - to_string(f, 1.0e-20, Minus, 50000), + to_string(f, 1.0e-20, "", 50000), format!( "0.0000000000000000000099999999999999994515327145420957165172950370\ 2787392447107715776066783064379706047475337982177734375{:0>49881}", diff --git a/library/coretests/tests/num/flt2dec/random.rs b/library/coretests/tests/num/flt2dec/random.rs deleted file mode 100644 index e47aa6d37a804..0000000000000 --- a/library/coretests/tests/num/flt2dec/random.rs +++ /dev/null @@ -1,252 +0,0 @@ -#![cfg(not(target_arch = "wasm32"))] - -use core::num::imp::flt2dec; -use std::mem::MaybeUninit; -use std::str; - -use flt2dec::strategy::grisu::{format_exact_opt, format_shortest_opt}; -use flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; -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) -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, -{ - assert!(k <= 1024); - - let mut npassed = 0; // f(x) = Some(g(x)) - let mut nignored = 0; // f(x) = None - - 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 = 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 { - 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 - ); - } - } else { - nignored += 1; - } - } - println!( - "{}({}): done, ignored={} passed={} failed={}", - func, - k, - nignored, - npassed, - n - nignored - npassed - ); - assert!( - nignored + npassed == n, - "{}({}): {} out of {} values returns an incorrect value!", - func, - k, - n - nignored - npassed, - n - ); - (npassed, nignored) -} - -#[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), -{ - let mut rng = crate::test_rng(); - let f16_range = Uniform::new(0x0001u16, 0x7c00).unwrap(); - iterate("f16_random_equivalence_test", k, n, f, g, |_| { - let x = f16::from_bits(f16_range.sample(&mut rng)); - decode_finite(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), -{ - 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 x = f32::from_bits(f32_range.sample(&mut rng)); - decode_finite(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), -{ - 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 x = f64::from_bits(f64_range.sample(&mut rng)); - decode_finite(x) - }); -} - -#[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) - }); - 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) - }); - assert_eq!((npassed, nignored), (2121451881, 17643158)); -} - -#[test] -fn shortest_random_equivalence_test() { - use flt2dec::strategy::dragon::format_shortest as fallback; - // 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); - #[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 flt2dec::strategy::dragon::format_shortest as fallback; - f16_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS); -} - -#[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 flt2dec::strategy::dragon::format_shortest as fallback; - f32_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS); -} - -#[test] -#[ignore] // it is too expensive -fn shortest_f64_hard_random_equivalence_test() { - // this again probably has to use appropriate rustc flags. - - use flt2dec::strategy::dragon::format_shortest as fallback; - f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, 100_000_000); -} - -#[test] -#[cfg(target_has_reliable_f16)] -fn exact_f16_random_equivalence_test() { - use flt2dec::strategy::dragon::format_exact as fallback; - // 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, - ); - } -} - -#[test] -fn exact_f32_random_equivalence_test() { - use flt2dec::strategy::dragon::format_exact as fallback; - // 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, - ); - } -} - -#[test] -fn exact_f64_random_equivalence_test() { - use flt2dec::strategy::dragon::format_exact as fallback; - // 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, - ); - } -} diff --git a/library/coretests/tests/num/flt2dec/strategy/dragon.rs b/library/coretests/tests/num/flt2dec/strategy/dragon.rs index 886f50064a503..7df75c69b4083 100644 --- a/library/coretests/tests/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/tests/num/flt2dec/strategy/dragon.rs @@ -14,56 +14,22 @@ fn test_mul_pow10() { } } -#[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); + to_shortest_str_test(format_short); } #[test] fn test_to_shortest_exp_str() { - to_shortest_exp_str_test(format_shortest); + to_shortest_exp_str_test(format_short); } #[test] fn test_to_exact_exp_str() { - to_exact_exp_str_test(format_exact); + to_exact_exp_str_test(format_fixed); } #[test] fn test_to_exact_fixed_str() { - to_exact_fixed_str_test(format_exact); + to_exact_fixed_str_test(format_fixed); } diff --git a/library/coretests/tests/num/flt2dec/strategy/grisu.rs b/library/coretests/tests/num/flt2dec/strategy/grisu.rs index 8350480333380..aae45d6df11d8 100644 --- a/library/coretests/tests/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/tests/num/flt2dec/strategy/grisu.rs @@ -1,18 +1,19 @@ -use core::num::imp::flt2dec::strategy::grisu::*; +use core::num::imp::flt2dec; +use core::num::imp::flt2dec::strategy::grisu; use super::super::*; #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_cached_power() { - assert_eq!(CACHED_POW10.first().unwrap().1, CACHED_POW10_FIRST_E); - assert_eq!(CACHED_POW10.last().unwrap().1, CACHED_POW10_LAST_E); + assert_eq!(grisu::CACHED_POW10.first().unwrap().1, grisu::CACHED_POW10_FIRST_E); + assert_eq!(grisu::CACHED_POW10.last().unwrap().1, grisu::CACHED_POW10_LAST_E); for e in -1137..961 { // full range for f64 - let low = ALPHA - e - 64; - let high = GAMMA - e - 64; - let (_k, cached) = cached_power(low, high); + let low = grisu::ALPHA - e - 64; + let high = grisu::GAMMA - e - 64; + let (_k, cached) = grisu::cached_power(low, high); assert!( low <= cached.e && cached.e <= high, "cached_power({}, {}) = {:?} is incorrect", @@ -28,50 +29,28 @@ fn test_max_pow10_no_more_than() { let mut prevtenk = 1; for k in 1..10 { let tenk = prevtenk * 10; - assert_eq!(max_pow10_no_more_than(tenk - 1), (k - 1, prevtenk)); - assert_eq!(max_pow10_no_more_than(tenk), (k, tenk)); + assert_eq!(grisu::max_pow10_no_more_than(tenk - 1), (k - 1, prevtenk)); + assert_eq!(grisu::max_pow10_no_more_than(tenk), (k, tenk)); 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); + to_shortest_str_test(flt2dec::format_short); } #[test] fn test_to_shortest_exp_str() { - to_shortest_exp_str_test(format_shortest); + to_shortest_exp_str_test(flt2dec::format_short); } #[test] fn test_to_exact_exp_str() { - to_exact_exp_str_test(format_exact); + to_exact_exp_str_test(flt2dec::format_fixed); } #[test] fn test_to_exact_fixed_str() { - to_exact_fixed_str_test(format_exact); + to_exact_fixed_str_test(flt2dec::format_fixed); } 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 703811633f853..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff +++ /dev/null @@ -1,173 +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::imp::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::imp::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::imp::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 2097152_u32); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = core::num::imp::flt2dec::Sign::MinusPlus; -+ _5 = const core::num::imp::flt2dec::Sign::MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::imp::flt2dec::Sign::Minus; -+ _5 = const core::num::imp::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 268435456_u32); - StorageDead(_23); - switchInt(copy _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { - StorageLive(_8); - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = no_retag 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); - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = no_retag 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 a7187876e663e..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff +++ /dev/null @@ -1,173 +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::imp::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::imp::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::imp::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 2097152_u32); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = core::num::imp::flt2dec::Sign::MinusPlus; -+ _5 = const core::num::imp::flt2dec::Sign::MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::imp::flt2dec::Sign::Minus; -+ _5 = const core::num::imp::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 268435456_u32); - StorageDead(_23); - switchInt(copy _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { - StorageLive(_8); - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = no_retag 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); - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = no_retag 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 1e2ee782b444a..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff +++ /dev/null @@ -1,173 +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::imp::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::imp::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::imp::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 2097152_u32); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = core::num::imp::flt2dec::Sign::MinusPlus; -+ _5 = const core::num::imp::flt2dec::Sign::MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::imp::flt2dec::Sign::Minus; -+ _5 = const core::num::imp::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 268435456_u32); - StorageDead(_23); - switchInt(copy _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { - StorageLive(_8); - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = no_retag 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); - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = no_retag 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 0640cb2d2b8dd..0000000000000 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff +++ /dev/null @@ -1,173 +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::imp::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::imp::flt2dec::Sign; - let mut _19: bool; - scope 1 { - debug force_sign => _4; - let _5: core::num::imp::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 2097152_u32); - StorageDead(_21); - _4 = Ne(move _20, const 0_u32); - StorageDead(_20); - StorageLive(_5); - switchInt(copy _4) -> [0: bb2, otherwise: bb1]; - } - - bb1: { -- _5 = core::num::imp::flt2dec::Sign::MinusPlus; -+ _5 = const core::num::imp::flt2dec::Sign::MinusPlus; - goto -> bb3; - } - - bb2: { -- _5 = core::num::imp::flt2dec::Sign::Minus; -+ _5 = const core::num::imp::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 268435456_u32); - StorageDead(_23); - switchInt(copy _22) -> [0: bb10, otherwise: bb11]; - } - - bb4: { - StorageLive(_8); - _8 = copy ((_6 as Some).0: usize); - StorageLive(_9); - _9 = no_retag 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); - goto -> bb8; - } - - bb6: { - StorageLive(_16); - _16 = no_retag 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 6edfe27162f96..0000000000000 --- a/tests/mir-opt/funky_arms.rs +++ /dev/null @@ -1,59 +0,0 @@ -//@ skip-filecheck -// EMIT_MIR_FOR_EACH_PANIC_STRATEGY -// EMIT_MIR_FOR_EACH_BIT_WIDTH - -#![feature(flt2dec)] -#![feature(num_internals)] - -extern crate core; - -use core::num::imp::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!() -}