From d8b98c6440efdbcbf81ac5a2e242c55eb10ffdf4 Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Mon, 23 Mar 2026 15:52:24 +0100 Subject: [PATCH 1/8] flt2dec front with format_short and format_fixed * Fallback from Grisu to Dragon in flt2dec front * Together with name change to ensure all use checked * Drop of Grisu benchmarks which tested Dragon fallback * Documentation at functions instead of module level The values dropped in the benchmarks of Grisu are still covered in the Dragon benchmarks. All use of the format functions shows in this patch due to the name change. This ensures we don't leave any code behind which still uses strategy directly by accident. --- library/core/src/fmt/float.rs | 16 ++-- library/core/src/num/imp/flt2dec/mod.rs | 87 ++++++++++++++++--- .../src/num/imp/flt2dec/strategy/dragon.rs | 26 +++--- .../src/num/imp/flt2dec/strategy/grisu.rs | 67 ++++---------- .../benches/num/flt2dec/strategy/dragon.rs | 16 ++-- .../benches/num/flt2dec/strategy/grisu.rs | 57 ++---------- library/coretests/tests/num/flt2dec/mod.rs | 6 +- library/coretests/tests/num/flt2dec/random.rs | 34 ++++---- .../tests/num/flt2dec/strategy/dragon.rs | 22 ++--- .../tests/num/flt2dec/strategy/grisu.rs | 39 +++++---- 10 files changed, 175 insertions(+), 195 deletions(-) diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs index bb70e59f960ed..1876998222275 100644 --- a/library/core/src/fmt/float.rs +++ b/library/core/src/fmt/float.rs @@ -45,14 +45,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) } } @@ -73,14 +73,14 @@ 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) } } @@ -118,7 +118,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 +126,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) } } @@ -147,7 +147,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,7 +155,7 @@ 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) } } diff --git a/library/core/src/num/imp/flt2dec/mod.rs b/library/core/src/num/imp/flt2dec/mod.rs index cf895b3e89e66..1efbb3c214c38 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: @@ -142,6 +130,76 @@ 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. +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)] @@ -578,7 +636,7 @@ where 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); + let (buf, exp) = format_exact(decoded, &mut buf[..sig_digits], UNLIMITED_RESOLUTION); Formatted { sign, parts: digits_to_exp_str(buf, exp, frac_digits, upper, parts) } } } @@ -653,7 +711,8 @@ where // 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 limit = + if frac_digits < 0x8000 { -(frac_digits as i16) } else { UNLIMITED_RESOLUTION }; 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 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/strategy/dragon.rs b/library/coretests/benches/num/flt2dec/strategy/dragon.rs index d98b3df9b2f35..c79f0a4f38f7f 100644 --- a/library/coretests/benches/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/benches/num/flt2dec/strategy/dragon.rs @@ -8,7 +8,7 @@ fn bench_small_shortest(b: &mut Bencher) { let decoded = decode_finite(3.141592f64); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf); }); } @@ -17,7 +17,7 @@ fn bench_big_shortest(b: &mut Bencher) { let decoded = decode_finite(f64::MAX); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf); }); } @@ -26,7 +26,7 @@ fn bench_small_exact_3(b: &mut Bencher) { let decoded = decode_finite(3.141592f64); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN); }); } @@ -35,7 +35,7 @@ fn bench_big_exact_3(b: &mut Bencher) { let decoded = decode_finite(f64::MAX); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN); }); } @@ -44,7 +44,7 @@ fn bench_small_exact_12(b: &mut Bencher) { let decoded = decode_finite(3.141592f64); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN); }); } @@ -53,7 +53,7 @@ fn bench_big_exact_12(b: &mut Bencher) { let decoded = decode_finite(f64::MAX); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN); }); } @@ -62,7 +62,7 @@ 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); + format_fixed(black_box(&decoded), &mut buf, i16::MIN); }); } @@ -71,6 +71,6 @@ 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); + format_fixed(black_box(&decoded), &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..bb3a979842f28 100644 --- a/library/coretests/benches/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/benches/num/flt2dec/strategy/grisu.rs @@ -15,7 +15,7 @@ fn bench_small_shortest(b: &mut Bencher) { let decoded = decode_finite(3.141592f64); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf).unwrap(); }); } @@ -24,7 +24,7 @@ fn bench_big_shortest(b: &mut Bencher) { let decoded = decode_finite(f64::MAX); let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS]; b.iter(|| { - format_shortest(black_box(&decoded), &mut buf); + format_short(black_box(&decoded), &mut buf).unwrap(); }); } @@ -33,7 +33,7 @@ fn bench_small_exact_3(b: &mut Bencher) { let decoded = decode_finite(3.141592f64); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); }); } @@ -42,7 +42,7 @@ fn bench_big_exact_3(b: &mut Bencher) { let decoded = decode_finite(f64::MAX); let mut buf = [MaybeUninit::new(0); 3]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); }); } @@ -51,7 +51,7 @@ fn bench_small_exact_12(b: &mut Bencher) { let decoded = decode_finite(3.141592f64); let mut buf = [MaybeUninit::new(0); 12]; b.iter(|| { - format_exact(black_box(&decoded), &mut buf, i16::MIN); + format_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); }); } @@ -60,51 +60,6 @@ fn bench_big_exact_12(b: &mut Bencher) { let decoded = decode_finite(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(&decoded), &mut buf, i16::MIN).unwrap(); }); } diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index 02a5d31553da1..17b99770c1f8c 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -1,6 +1,6 @@ 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, FullDecoded, MAX_SIG_DIGITS, Sign, 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; @@ -60,7 +60,7 @@ macro_rules! check_shortest { 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); + let (buf, k) = $f($decoded, &mut $buf[..$expected.len()], UNLIMITED_RESOLUTION); assert!((buf, k) == ($expected, $expectedk), $fmt, actual = (str::from_utf8(buf).unwrap(), k), expected = (str::from_utf8($expected).unwrap(), $expectedk), diff --git a/library/coretests/tests/num/flt2dec/random.rs b/library/coretests/tests/num/flt2dec/random.rs index e47aa6d37a804..bbfd3cece68cd 100644 --- a/library/coretests/tests/num/flt2dec/random.rs +++ b/library/coretests/tests/num/flt2dec/random.rs @@ -4,7 +4,7 @@ use core::num::imp::flt2dec; use std::mem::MaybeUninit; use std::str; -use flt2dec::strategy::grisu::{format_exact_opt, format_shortest_opt}; +use flt2dec::strategy::{dragon, grisu}; use flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; use rand::distr::{Distribution, Uniform}; @@ -160,14 +160,14 @@ where #[test] fn shortest_random_equivalence_test() { - use flt2dec::strategy::dragon::format_shortest as fallback; + use dragon::format_short 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); + f64_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, n); + f32_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, n); #[cfg(target_has_reliable_f16)] - f16_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n); + f16_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, n); } #[test] @@ -175,8 +175,8 @@ fn shortest_random_equivalence_test() { #[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); + use dragon::format_short as fallback; + f16_exhaustive_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS); } #[test] @@ -189,8 +189,8 @@ fn shortest_f32_exhaustive_equivalence_test() { // 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); + use dragon::format_short as fallback; + f32_exhaustive_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS); } #[test] @@ -198,20 +198,20 @@ fn shortest_f32_exhaustive_equivalence_test() { 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); + use dragon::format_short as fallback; + f64_random_equivalence_test(grisu::format_short, 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; + use dragon::format_fixed 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| grisu::format_fixed(d, buf, i16::MIN), |d, buf| fallback(d, buf, i16::MIN), k, n, @@ -221,13 +221,13 @@ fn exact_f16_random_equivalence_test() { #[test] fn exact_f32_random_equivalence_test() { - use flt2dec::strategy::dragon::format_exact as fallback; + use dragon::format_fixed 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| grisu::format_fixed(d, buf, i16::MIN), |d, buf| fallback(d, buf, i16::MIN), k, n, @@ -237,13 +237,13 @@ fn exact_f32_random_equivalence_test() { #[test] fn exact_f64_random_equivalence_test() { - use flt2dec::strategy::dragon::format_exact as fallback; + use dragon::format_fixed 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| grisu::format_fixed(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..90b0412676bca 100644 --- a/library/coretests/tests/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/tests/num/flt2dec/strategy/dragon.rs @@ -16,11 +16,11 @@ fn test_mul_pow10() { #[test] fn shortest_sanity_test() { - f64_shortest_sanity_test(format_shortest); - f32_shortest_sanity_test(format_shortest); + f64_shortest_sanity_test(format_short); + f32_shortest_sanity_test(format_short); #[cfg(target_has_reliable_f16)] - f16_shortest_sanity_test(format_shortest); - more_shortest_sanity_test(format_shortest); + f16_shortest_sanity_test(format_short); + more_shortest_sanity_test(format_short); } #[test] @@ -40,30 +40,30 @@ fn exact_sanity_test() { // 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); + f64_exact_sanity_test(format_fixed); } - f32_exact_sanity_test(format_exact); + f32_exact_sanity_test(format_fixed); #[cfg(target_has_reliable_f16)] - f16_exact_sanity_test(format_exact); + f16_exact_sanity_test(format_fixed); } #[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..b65cd0da2b741 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,19 +29,19 @@ 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); + f64_shortest_sanity_test(flt2dec::format_short); + f32_shortest_sanity_test(flt2dec::format_short); #[cfg(target_has_reliable_f16)] - f16_shortest_sanity_test(format_shortest); - more_shortest_sanity_test(format_shortest); + f16_shortest_sanity_test(flt2dec::format_short); + more_shortest_sanity_test(flt2dec::format_short); } #[test] @@ -49,29 +50,29 @@ 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); + f64_exact_sanity_test(flt2dec::format_fixed); } - f32_exact_sanity_test(format_exact); + f32_exact_sanity_test(flt2dec::format_fixed); #[cfg(target_has_reliable_f16)] - f16_exact_sanity_test(format_exact); + f16_exact_sanity_test(flt2dec::format_fixed); } #[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); } From e53b13c62c1d8f047c8d037263274c47ecc18e89 Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Mon, 23 Mar 2026 20:00:15 +0100 Subject: [PATCH 2/8] less abstraction in equivelance of flt2dec/random.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "A little copying is better than a little dependency." — Rob Pike * Equivalence comparison explicit instead of generic * Bit patterns specified as ops::Range constants * No panic on invalid UTF-8 in equivalence test --- library/coretests/tests/num/flt2dec/random.rs | 317 ++++++++---------- 1 file changed, 142 insertions(+), 175 deletions(-) diff --git a/library/coretests/tests/num/flt2dec/random.rs b/library/coretests/tests/num/flt2dec/random.rs index bbfd3cece68cd..98bb2cad40b5a 100644 --- a/library/coretests/tests/num/flt2dec/random.rs +++ b/library/coretests/tests/num/flt2dec/random.rs @@ -2,251 +2,218 @@ use core::num::imp::flt2dec; use std::mem::MaybeUninit; -use std::str; +use std::string::String; use flt2dec::strategy::{dragon, grisu}; use flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; use rand::distr::{Distribution, Uniform}; -pub fn decode_finite(v: T) -> Decoded { +// 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(); + +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) +/// 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 - 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 - + let mut omitted = 0; + let mut failed = 0; for i in 0..n { - if (i & 0xfffff) == 0 { - println!( - "in progress, {:x}/{:x} (ignored={} passed={} failed={})", - i, - n, - nignored, - npassed, - i - nignored - npassed - ); + if (i & 0xf_ffff) == 0 { + println!("did {i} out of {n}, with {omitted} omitted"); } - - 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 { + 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!( - "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 + "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 { - nignored += 1; + omitted += 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) + assert_eq!(failed, 0, "number of different outcomes"); + omitted } -#[cfg(target_has_reliable_f16)] -pub fn f16_random_equivalence_test(f: F, g: G, k: usize, n: usize) +/// 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 - 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, { - 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) - }); + 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 } -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), -{ +#[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 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)); + 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_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), -{ +#[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 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)); + 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_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 dragon::format_short as fallback; +fn test_short_f64_random_equiv() { // Miri is too slow let n = if cfg!(miri) { 10 } else { 10_000 }; - f64_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, n); - f32_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, n); - #[cfg(target_has_reliable_f16)] - f16_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, n); + 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_finite(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 shortest_f16_exhaustive_equivalence_test() { - // see the f32 version - use dragon::format_short as fallback; - f16_exhaustive_equivalence_test(grisu::format_short, 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 dragon::format_short as fallback; - f32_exhaustive_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS); +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_finite(x) + }); + assert_eq!(omitted, 2008, "number of inputs in {F16_POS_FIN_RANGE:?} not covered by Grisu"); } #[test] -#[ignore] // it is too expensive -fn shortest_f64_hard_random_equivalence_test() { - // this again probably has to use appropriate rustc flags. - - use dragon::format_short as fallback; - f64_random_equivalence_test(grisu::format_short, fallback, MAX_SIG_DIGITS, 100_000_000); +#[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_finite(x) + }); + assert_eq!(omitted, 17643158, "number of inputs in {F32_POS_FIN_RANGE:?} not covered by Grisu"); } #[test] #[cfg(target_has_reliable_f16)] -fn exact_f16_random_equivalence_test() { - use dragon::format_fixed as fallback; +fn test_fixed_f16_random_equiv() { // Miri is too slow let n = if cfg!(miri) { 3 } else { 1_000 }; - for k in 1..21 { - f16_random_equivalence_test( - |d, buf| grisu::format_fixed(d, buf, i16::MIN), - |d, buf| fallback(d, buf, i16::MIN), - k, - n, - ); + 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_finite(x) + }); } } #[test] -fn exact_f32_random_equivalence_test() { - use dragon::format_fixed as fallback; +fn test_fixed_f32_random_equiv() { // Miri is too slow let n = if cfg!(miri) { 3 } else { 1_000 }; - for k in 1..21 { - f32_random_equivalence_test( - |d, buf| grisu::format_fixed(d, buf, i16::MIN), - |d, buf| fallback(d, buf, i16::MIN), - k, - n, - ); + 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_finite(x) + }); } } #[test] -fn exact_f64_random_equivalence_test() { - use dragon::format_fixed as fallback; +fn test_fixed_f64_random_equiv() { // Miri is too slow let n = if cfg!(miri) { 2 } else { 1_000 }; - for k in 1..21 { - f64_random_equivalence_test( - |d, buf| grisu::format_fixed(d, buf, i16::MIN), - |d, buf| fallback(d, buf, i16::MIN), - k, - n, - ); + 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_finite(x) + }); } } From 62dceb3d33f21d943beec033366c7c0609b2b64d Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Thu, 26 Mar 2026 15:09:12 +0100 Subject: [PATCH 3/8] test flt2dec front of format_short & format_fixed * Test functionality as provided instead of inners * No generics abstraction of Grisu and Dragon * The stringify! of input in assertion message * TestableFloat abstraction omitted with macro * MSVC omission central, direct on respective test Unified signature plus description on assertion tools: * check_shortest! -> check_short! * try_exact! -> check_fixed_buf_match! * try_fixed! -> check_fixed_resolution! * check_exact & check_exact! -> check_fixed_mix! * check_exact_one -> check_coef_pow2! --- library/coretests/tests/num/flt2dec/mod.rs | 641 ++++++++---------- .../tests/num/flt2dec/strategy/dragon.rs | 34 - .../tests/num/flt2dec/strategy/grisu.rs | 22 - 3 files changed, 284 insertions(+), 413 deletions(-) diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index 17b99770c1f8c..3e13c12f02961 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -1,10 +1,10 @@ +use core::num::imp::flt2dec; use core::num::imp::flt2dec::{ DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, Sign, 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}; @@ -17,203 +17,126 @@ mod strategy { } mod random; -pub fn decode_finite(v: T) -> Decoded { +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)) - ); - - ($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),+ }) - ); +/// Assert format_short outcome. +macro_rules! check_short { + ($v:expr => $digits:expr, $pow10:expr) => { + let label = stringify!($v); + let dec = decode_finite($v); + let want: (&[u8], i16) = ($digits, $pow10); - ($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()], UNLIMITED_RESOLUTION); - 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_finite($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_finite($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); - } + // check exact rounding for zero- and negative-width cases + let start; + if digits[0] > b'5' { + check_fixed_resolution!($v, pow10 => b"1", pow10 + 1); + start = 1; + } else { + start = 0; + } + for i in start..-10 { + 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 +148,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 diff --git a/library/coretests/tests/num/flt2dec/strategy/dragon.rs b/library/coretests/tests/num/flt2dec/strategy/dragon.rs index 90b0412676bca..7df75c69b4083 100644 --- a/library/coretests/tests/num/flt2dec/strategy/dragon.rs +++ b/library/coretests/tests/num/flt2dec/strategy/dragon.rs @@ -14,40 +14,6 @@ fn test_mul_pow10() { } } -#[test] -fn shortest_sanity_test() { - f64_shortest_sanity_test(format_short); - f32_shortest_sanity_test(format_short); - #[cfg(target_has_reliable_f16)] - f16_shortest_sanity_test(format_short); - more_shortest_sanity_test(format_short); -} - -#[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_fixed); - } - f32_exact_sanity_test(format_fixed); - - #[cfg(target_has_reliable_f16)] - f16_exact_sanity_test(format_fixed); -} - #[test] fn test_to_shortest_str() { to_shortest_str_test(format_short); diff --git a/library/coretests/tests/num/flt2dec/strategy/grisu.rs b/library/coretests/tests/num/flt2dec/strategy/grisu.rs index b65cd0da2b741..aae45d6df11d8 100644 --- a/library/coretests/tests/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/tests/num/flt2dec/strategy/grisu.rs @@ -35,28 +35,6 @@ fn test_max_pow10_no_more_than() { } } -#[test] -fn shortest_sanity_test() { - f64_shortest_sanity_test(flt2dec::format_short); - f32_shortest_sanity_test(flt2dec::format_short); - #[cfg(target_has_reliable_f16)] - f16_shortest_sanity_test(flt2dec::format_short); - more_shortest_sanity_test(flt2dec::format_short); -} - -#[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(flt2dec::format_fixed); - } - f32_exact_sanity_test(flt2dec::format_fixed); - #[cfg(target_has_reliable_f16)] - f16_exact_sanity_test(flt2dec::format_fixed); -} - #[test] fn test_to_shortest_str() { to_shortest_str_test(flt2dec::format_short); From 60d93a03a27f01f25403c67f39286110126dd70e Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Fri, 27 Mar 2026 16:30:09 +0100 Subject: [PATCH 4/8] typo in test value from round-to-even in fmt Both the current and the correct value are equivalent when rounded to three decimals. --- library/core/src/num/imp/flt2dec/mod.rs | 11 ++++++----- library/coretests/tests/fmt/float.rs | 12 ++++++++---- library/coretests/tests/num/flt2dec/mod.rs | 12 +++++------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/library/core/src/num/imp/flt2dec/mod.rs b/library/core/src/num/imp/flt2dec/mod.rs index 1efbb3c214c38..3a84d09071a78 100644 --- a/library/core/src/num/imp/flt2dec/mod.rs +++ b/library/core/src/num/imp/flt2dec/mod.rs @@ -186,6 +186,9 @@ pub const UNLIMITED_RESOLUTION: i16 = i16::MIN; /// 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], @@ -714,11 +717,9 @@ where let limit = if frac_digits < 0x8000 { -(frac_digits as i16) } else { UNLIMITED_RESOLUTION }; 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 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.")); diff --git a/library/coretests/tests/fmt/float.rs b/library/coretests/tests/fmt/float.rs index 4f7357761744b..18225363617f1 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)); diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index 3e13c12f02961..793f1a811b7b0 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -105,15 +105,13 @@ macro_rules! check_fixed_mix { 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 digits[0] > b'5' { + // Verify digit loss by resolution. + let skip_first = digits[0] > b'5'; + if skip_first { check_fixed_resolution!($v, pow10 => b"1", pow10 + 1); - start = 1; - } else { - start = 0; } - for i in start..-10 { + for i in i16::from(skip_first)..-10 { + // Digit loss must still maintain the exponent. check_fixed_resolution!($v, pow10 - i => b"", pow10); } From 294331fcd0b72470c4f63c020c4e54d6fc18d53c Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Tue, 31 Mar 2026 12:07:43 +0200 Subject: [PATCH 5/8] flt2dec/random.rs renamed to flt2dec/equiv.rs --- library/coretests/tests/num/flt2dec/{random.rs => equiv.rs} | 0 library/coretests/tests/num/flt2dec/mod.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename library/coretests/tests/num/flt2dec/{random.rs => equiv.rs} (100%) diff --git a/library/coretests/tests/num/flt2dec/random.rs b/library/coretests/tests/num/flt2dec/equiv.rs similarity index 100% rename from library/coretests/tests/num/flt2dec/random.rs rename to library/coretests/tests/num/flt2dec/equiv.rs diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index 793f1a811b7b0..d2723b45d411b 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -10,12 +10,12 @@ use Sign::{Minus, MinusPlus}; use crate::num::{ldexp_f32, ldexp_f64}; +mod equiv; mod estimator; mod strategy { mod dragon; mod grisu; } -mod random; fn decode_finite(v: T) -> Decoded { match decode(v).1 { From ddb06f2fa14ee171b5bb099240f79044497d9450 Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Fri, 5 Sep 2025 13:34:20 +0200 Subject: [PATCH 6/8] formatted padding with clojure --- library/core/src/fmt/mod.rs | 192 +++++++++++++++++++----------------- library/core/src/fmt/num.rs | 19 ++-- 2 files changed, 110 insertions(+), 101 deletions(-) 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) + }) } } From 58bf90cde4d5f60c9713d3b1d6de5d7a2a47377d Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Tue, 31 Mar 2026 17:32:29 +0200 Subject: [PATCH 7/8] inline formatting of NaN, Inf and zero, plus omission of sign enum The represenation of NaN, Inf and zero each is fully dependend on the formatter. Differences are easy to see when the classify switch appears directly in the fmt function. Using the standard functions of each floating-point directly releases the need for trait abstractions. --- library/core/src/fmt/float.rs | 142 ++- library/core/src/num/imp/flt2dec/decoder.rs | 47 +- library/core/src/num/imp/flt2dec/mod.rs | 280 ++---- library/coretests/benches/num/flt2dec/mod.rs | 9 +- .../benches/num/flt2dec/strategy/dragon.rs | 32 +- .../benches/num/flt2dec/strategy/grisu.rs | 31 +- library/coretests/tests/fmt/float.rs | 57 ++ library/coretests/tests/num/flt2dec/equiv.rs | 25 +- library/coretests/tests/num/flt2dec/mod.rs | 881 ++++++++---------- 9 files changed, 654 insertions(+), 850 deletions(-) diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs index 1876998222275..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 @@ -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 @@ -84,16 +85,10 @@ where 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 @@ -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 @@ -160,16 +155,15 @@ where } // 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/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 3a84d09071a78..b62e6e2b7984a 100644 --- a/library/core/src/num/imp/flt2dec/mod.rs +++ b/library/core/src/num/imp/flt2dec/mod.rs @@ -110,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; @@ -365,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. @@ -418,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>], @@ -430,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 @@ -474,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. @@ -492,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], @@ -506,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 @@ -569,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. @@ -586,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>], @@ -598,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], UNLIMITED_RESOLUTION); - 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. @@ -664,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>], @@ -675,71 +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 { UNLIMITED_RESOLUTION }; - let (buf, exp) = format_exact(decoded, &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() }, - } - } 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/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 c79f0a4f38f7f..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_short(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_short(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_fixed(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_fixed(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_fixed(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_fixed(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_fixed(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_fixed(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 bb3a979842f28..27ed49f8b7d48 100644 --- a/library/coretests/benches/num/flt2dec/strategy/grisu.rs +++ b/library/coretests/benches/num/flt2dec/strategy/grisu.rs @@ -3,63 +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_short(black_box(&decoded), &mut buf).unwrap(); + 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_short(black_box(&decoded), &mut buf).unwrap(); + 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_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); + 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_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); + 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_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); + 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_fixed(black_box(&decoded), &mut buf, i16::MIN).unwrap(); + 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 18225363617f1..17b9f5c1d47c0 100644 --- a/library/coretests/tests/fmt/float.rs +++ b/library/coretests/tests/fmt/float.rs @@ -242,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 index 98bb2cad40b5a..d038408692556 100644 --- a/library/coretests/tests/num/flt2dec/equiv.rs +++ b/library/coretests/tests/num/flt2dec/equiv.rs @@ -5,7 +5,7 @@ use std::mem::MaybeUninit; use std::string::String; use flt2dec::strategy::{dragon, grisu}; -use flt2dec::{DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, decode}; +use flt2dec::{Decoded, MAX_SIG_DIGITS, decode}; use rand::distr::{Distribution, Uniform}; // Bits 0u16..0x7C00 cover the positive finite-range, @@ -20,13 +20,6 @@ const F32_POS_FIN_RANGE: std::ops::Range = 1..f32::INFINITY.to_bits(); // with 1u64..0x7FF0_0000_0000_0000 for non-zero. const F64_POS_FIN_RANGE: std::ops::Range = 1..f64::INFINITY.to_bits(); -fn decode_finite(v: T) -> Decoded { - match decode(v).1 { - FullDecoded::Finite(decoded) => decoded, - full_decoded => panic!("expected finite, got {full_decoded:?} instead"), - } -} - /// 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. @@ -118,7 +111,7 @@ fn test_short_f16_random_equiv() { 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_finite(x) + decode(x) }); } @@ -131,7 +124,7 @@ fn test_short_f32_random_equiv() { 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_finite(x) + decode(x) }); } @@ -144,7 +137,7 @@ fn test_short_f64_random_equiv() { 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_finite(x) + decode(x) }); } @@ -156,7 +149,7 @@ fn test_short_f64_random_equiv() { 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_finite(x) + decode(x) }); assert_eq!(omitted, 2008, "number of inputs in {F16_POS_FIN_RANGE:?} not covered by Grisu"); } @@ -167,7 +160,7 @@ fn test_short_f16_exhaustive_equiv() { 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_finite(x) + decode(x) }); assert_eq!(omitted, 17643158, "number of inputs in {F32_POS_FIN_RANGE:?} not covered by Grisu"); } @@ -183,7 +176,7 @@ fn test_fixed_f16_random_equiv() { 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_finite(x) + decode(x) }); } } @@ -198,7 +191,7 @@ fn test_fixed_f32_random_equiv() { 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_finite(x) + decode(x) }); } } @@ -213,7 +206,7 @@ fn test_fixed_f64_random_equiv() { 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_finite(x) + decode(x) }); } } diff --git a/library/coretests/tests/num/flt2dec/mod.rs b/library/coretests/tests/num/flt2dec/mod.rs index d2723b45d411b..efd61c9240b1a 100644 --- a/library/coretests/tests/num/flt2dec/mod.rs +++ b/library/coretests/tests/num/flt2dec/mod.rs @@ -1,13 +1,11 @@ use core::num::imp::flt2dec; use core::num::imp::flt2dec::{ - DecodableFloat, Decoded, FullDecoded, MAX_SIG_DIGITS, Sign, UNLIMITED_RESOLUTION, 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 Sign::{Minus, MinusPlus}; - use crate::num::{ldexp_f32, ldexp_f64}; mod equiv; @@ -17,18 +15,11 @@ mod strategy { mod grisu; } -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:?}"), - } -} - /// Assert format_short outcome. macro_rules! check_short { ($v:expr => $digits:expr, $pow10:expr) => { let label = stringify!($v); - let dec = decode_finite($v); + let dec = decode($v); let want: (&[u8], i16) = ($digits, $pow10); let mut buf = [MaybeUninit::new(b'_'); MAX_SIG_DIGITS]; @@ -41,7 +32,7 @@ macro_rules! check_short { macro_rules! check_fixed_resolution { ($v:expr, $resolution:expr => $digits:expr, $pow10:expr) => { let label = stringify!($v); - let dec = decode_finite($v); + let dec = decode($v); let resolution: i16 = $resolution; let want: (&[u8], i16) = ($digits, $pow10); @@ -55,7 +46,7 @@ macro_rules! check_fixed_resolution { macro_rules! check_fixed_buf_match { ($v:expr => $digits:expr, $pow10:expr) => { let label = stringify!($v); - let dec = decode_finite($v); + let dec = decode($v); let want: (&[u8], i16) = ($digits, $pow10); let mut buf = [MaybeUninit::new(b'_'); 1024]; @@ -489,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), @@ -501,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) { @@ -590,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), @@ -609,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), @@ -732,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) { @@ -879,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\ @@ -915,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", @@ -981,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", @@ -994,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), @@ -1006,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 @@ -1141,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}", From f7f1cf0097dc2513f1b8c0353462721d56aee3bb Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Sun, 17 May 2026 17:24:04 +0200 Subject: [PATCH 8/8] help requested: How to drop the funky_arms test? --- ...onential_common.GVN.32bit.panic-abort.diff | 173 ------------------ ...nential_common.GVN.32bit.panic-unwind.diff | 173 ------------------ ...onential_common.GVN.64bit.panic-abort.diff | 173 ------------------ ...nential_common.GVN.64bit.panic-unwind.diff | 173 ------------------ tests/mir-opt/funky_arms.rs | 59 ------ 5 files changed, 751 deletions(-) delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff delete mode 100644 tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff delete mode 100644 tests/mir-opt/funky_arms.rs diff --git a/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!() -}