flt2dec: properly handle uninitialized memory

This commit is contained in:
Ralf Jung 2020-09-02 12:18:07 +02:00
parent d9cd4a33f5
commit 56129d39c0
8 changed files with 387 additions and 293 deletions

View File

@ -1,59 +1,76 @@
use super::super::*;
use core::num::flt2dec::strategy::dragon::*;
use std::mem::MaybeUninit;
use test::Bencher;
#[bench]
fn bench_small_shortest(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; MAX_SIG_DIGITS];
b.iter(|| format_shortest(&decoded, &mut buf));
let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS];
b.iter(|| {
format_shortest(&decoded, &mut buf);
});
}
#[bench]
fn bench_big_shortest(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; MAX_SIG_DIGITS];
b.iter(|| format_shortest(&decoded, &mut buf));
let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS];
b.iter(|| {
format_shortest(&decoded, &mut buf);
});
}
#[bench]
fn bench_small_exact_3(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; 3];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 3];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_big_exact_3(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; 3];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 3];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_small_exact_12(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; 12];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 12];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_big_exact_12(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; 12];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 12];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_small_exact_inf(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; 1024];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 1024];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_big_exact_inf(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; 1024];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 1024];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}

View File

@ -1,5 +1,6 @@
use super::super::*;
use core::num::flt2dec::strategy::grisu::*;
use std::mem::MaybeUninit;
use test::Bencher;
pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
@ -12,55 +13,71 @@ pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
#[bench]
fn bench_small_shortest(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; MAX_SIG_DIGITS];
b.iter(|| format_shortest(&decoded, &mut buf));
let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS];
b.iter(|| {
format_shortest(&decoded, &mut buf);
});
}
#[bench]
fn bench_big_shortest(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; MAX_SIG_DIGITS];
b.iter(|| format_shortest(&decoded, &mut buf));
let mut buf = [MaybeUninit::new(0); MAX_SIG_DIGITS];
b.iter(|| {
format_shortest(&decoded, &mut buf);
});
}
#[bench]
fn bench_small_exact_3(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; 3];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 3];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_big_exact_3(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; 3];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 3];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_small_exact_12(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; 12];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 12];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_big_exact_12(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; 12];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 12];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_small_exact_inf(b: &mut Bencher) {
let decoded = decode_finite(3.141592f64);
let mut buf = [0; 1024];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 1024];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}
#[bench]
fn bench_big_exact_inf(b: &mut Bencher) {
let decoded = decode_finite(f64::MAX);
let mut buf = [0; 1024];
b.iter(|| format_exact(&decoded, &mut buf, i16::MIN));
let mut buf = [MaybeUninit::new(0); 1024];
b.iter(|| {
format_exact(&decoded, &mut buf, i16::MIN);
});
}

View File

@ -14,25 +14,17 @@ fn float_to_decimal_common_exact<T>(
where
T: flt2dec::DecodableFloat,
{
// SAFETY: Possible undefined behavior, see FIXME(#76092)
unsafe {
let mut buf = MaybeUninit::<[u8; 1024]>::uninit(); // enough for f32 and f64
let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 4]>::uninit();
// FIXME(#76092): This is calling `assume_init_mut` on an uninitialized
// `MaybeUninit` (here and elsewhere in this file). Revisit this once
// we decided whether that is valid or not.
// We can do this only because we are libstd and coupled to the compiler.
// (FWIW, using `freeze` would not be enough; `flt2dec::Part` is an enum!)
let formatted = flt2dec::to_exact_fixed_str(
flt2dec::strategy::grisu::format_exact,
*num,
sign,
precision,
buf.assume_init_mut(),
parts.assume_init_mut(),
);
fmt.pad_formatted_parts(&formatted)
}
let mut buf: [MaybeUninit<u8>; 1024] = MaybeUninit::uninit_array(); // enough for f32 and f64
let mut parts: [MaybeUninit<flt2dec::Part<'_>>; 4] = MaybeUninit::uninit_array();
let formatted = flt2dec::to_exact_fixed_str(
flt2dec::strategy::grisu::format_exact,
*num,
sign,
precision,
&mut buf,
&mut parts,
);
fmt.pad_formatted_parts(&formatted)
}
// Don't inline this so callers that call both this and the above won't wind
@ -47,22 +39,18 @@ fn float_to_decimal_common_shortest<T>(
where
T: flt2dec::DecodableFloat,
{
// SAFETY: Possible undefined behavior, see FIXME(#76092)
unsafe {
// enough for f32 and f64
let mut buf = MaybeUninit::<[u8; flt2dec::MAX_SIG_DIGITS]>::uninit();
let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 4]>::uninit();
// FIXME(#76092)
let formatted = flt2dec::to_shortest_str(
flt2dec::strategy::grisu::format_shortest,
*num,
sign,
precision,
buf.assume_init_mut(),
parts.assume_init_mut(),
);
fmt.pad_formatted_parts(&formatted)
}
// enough for f32 and f64
let mut buf: [MaybeUninit<u8>; flt2dec::MAX_SIG_DIGITS] = MaybeUninit::uninit_array();
let mut parts: [MaybeUninit<flt2dec::Part<'_>>; 4] = MaybeUninit::uninit_array();
let formatted = flt2dec::to_shortest_str(
flt2dec::strategy::grisu::format_shortest,
*num,
sign,
precision,
&mut buf,
&mut parts,
);
fmt.pad_formatted_parts(&formatted)
}
// Common code of floating point Debug and Display.
@ -103,22 +91,18 @@ fn float_to_exponential_common_exact<T>(
where
T: flt2dec::DecodableFloat,
{
// SAFETY: Possible undefined behavior, see FIXME(#76092)
unsafe {
let mut buf = MaybeUninit::<[u8; 1024]>::uninit(); // enough for f32 and f64
let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 6]>::uninit();
// FIXME(#76092)
let formatted = flt2dec::to_exact_exp_str(
flt2dec::strategy::grisu::format_exact,
*num,
sign,
precision,
upper,
buf.assume_init_mut(),
parts.assume_init_mut(),
);
fmt.pad_formatted_parts(&formatted)
}
let mut buf: [MaybeUninit<u8>; 1024] = MaybeUninit::uninit_array(); // enough for f32 and f64
let mut parts: [MaybeUninit<flt2dec::Part<'_>>; 6] = MaybeUninit::uninit_array();
let formatted = flt2dec::to_exact_exp_str(
flt2dec::strategy::grisu::format_exact,
*num,
sign,
precision,
upper,
&mut buf,
&mut parts,
);
fmt.pad_formatted_parts(&formatted)
}
// Don't inline this so callers that call both this and the above won't wind
@ -133,23 +117,19 @@ fn float_to_exponential_common_shortest<T>(
where
T: flt2dec::DecodableFloat,
{
// SAFETY: Possible undefined behavior, see FIXME(#76092)
unsafe {
// enough for f32 and f64
let mut buf = MaybeUninit::<[u8; flt2dec::MAX_SIG_DIGITS]>::uninit();
let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 6]>::uninit();
// FIXME(#76092)
let formatted = flt2dec::to_shortest_exp_str(
flt2dec::strategy::grisu::format_shortest,
*num,
sign,
(0, 0),
upper,
buf.assume_init_mut(),
parts.assume_init_mut(),
);
fmt.pad_formatted_parts(&formatted)
}
// enough for f32 and f64
let mut buf: [MaybeUninit<u8>; flt2dec::MAX_SIG_DIGITS] = MaybeUninit::uninit_array();
let mut parts: [MaybeUninit<flt2dec::Part<'_>>; 6] = MaybeUninit::uninit_array();
let formatted = flt2dec::to_shortest_exp_str(
flt2dec::strategy::grisu::format_shortest,
*num,
sign,
(0, 0),
upper,
&mut buf,
&mut parts,
);
fmt.pad_formatted_parts(&formatted)
}
// Common code of floating point LowerExp and UpperExp.

View File

@ -124,6 +124,8 @@ functions.
pub use self::decoder::{decode, DecodableFloat, Decoded, FullDecoded};
use crate::mem::MaybeUninit;
pub mod decoder;
pub mod estimator;
@ -140,23 +142,23 @@ pub mod strategy {
/// The exact formula is `ceil(# bits in mantissa * log_10 2 + 1)`.
pub const MAX_SIG_DIGITS: usize = 17;
/// When `d[..n]` contains decimal digits, increase the last digit and propagate carry.
/// Returns a next digit when it causes the length change.
/// 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)]
pub fn round_up(d: &mut [u8], n: usize) -> Option<u8> {
match d[..n].iter().rposition(|&c| c != b'9') {
pub fn round_up(d: &mut [u8]) -> Option<u8> {
match d.iter().rposition(|&c| c != b'9') {
Some(i) => {
// d[i+1..n] is all nines
d[i] += 1;
for j in i + 1..n {
for j in i + 1..d.len() {
d[j] = b'0';
}
None
}
None if n > 0 => {
None if d.len() > 0 => {
// 999..999 rounds to 1000..000 with an increased exponent
d[0] = b'1';
for j in 1..n {
for j in 1..d.len() {
d[j] = b'0';
}
Some(b'0')
@ -281,7 +283,7 @@ fn digits_to_dec_str<'a>(
buf: &'a [u8],
exp: i16,
frac_digits: usize,
parts: &'a mut [Part<'a>],
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> &'a [Part<'a>] {
assert!(!buf.is_empty());
assert!(buf[0] > b'0');
@ -303,38 +305,44 @@ fn digits_to_dec_str<'a>(
if exp <= 0 {
// the decimal point is before rendered digits: [0.][000...000][1234][____]
let minus_exp = -(exp as i32) as usize;
parts[0] = Part::Copy(b"0.");
parts[1] = Part::Zero(minus_exp);
parts[2] = Part::Copy(buf);
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
parts[1] = MaybeUninit::new(Part::Zero(minus_exp));
parts[2] = MaybeUninit::new(Part::Copy(buf));
if frac_digits > buf.len() && frac_digits - buf.len() > minus_exp {
parts[3] = Part::Zero((frac_digits - buf.len()) - minus_exp);
&parts[..4]
parts[3] = MaybeUninit::new(Part::Zero((frac_digits - buf.len()) - minus_exp));
// SAFETY: we just initialized the elements `..4`.
unsafe { MaybeUninit::slice_get_ref(&parts[..4]) }
} else {
&parts[..3]
// SAFETY: we just initialized the elements `..3`.
unsafe { MaybeUninit::slice_get_ref(&parts[..3]) }
}
} else {
let exp = exp as usize;
if exp < buf.len() {
// the decimal point is inside rendered digits: [12][.][34][____]
parts[0] = Part::Copy(&buf[..exp]);
parts[1] = Part::Copy(b".");
parts[2] = Part::Copy(&buf[exp..]);
parts[0] = MaybeUninit::new(Part::Copy(&buf[..exp]));
parts[1] = MaybeUninit::new(Part::Copy(b"."));
parts[2] = MaybeUninit::new(Part::Copy(&buf[exp..]));
if frac_digits > buf.len() - exp {
parts[3] = Part::Zero(frac_digits - (buf.len() - exp));
&parts[..4]
parts[3] = MaybeUninit::new(Part::Zero(frac_digits - (buf.len() - exp)));
// SAFETY: we just initialized the elements `..4`.
unsafe { MaybeUninit::slice_get_ref(&parts[..4]) }
} else {
&parts[..3]
// SAFETY: we just initialized the elements `..3`.
unsafe { MaybeUninit::slice_get_ref(&parts[..3]) }
}
} else {
// the decimal point is after rendered digits: [1234][____0000] or [1234][__][.][__].
parts[0] = Part::Copy(buf);
parts[1] = Part::Zero(exp - buf.len());
parts[0] = MaybeUninit::new(Part::Copy(buf));
parts[1] = MaybeUninit::new(Part::Zero(exp - buf.len()));
if frac_digits > 0 {
parts[2] = Part::Copy(b".");
parts[3] = Part::Zero(frac_digits);
&parts[..4]
parts[2] = MaybeUninit::new(Part::Copy(b"."));
parts[3] = MaybeUninit::new(Part::Zero(frac_digits));
// SAFETY: we just initialized the elements `..4`.
unsafe { MaybeUninit::slice_get_ref(&parts[..4]) }
} else {
&parts[..2]
// SAFETY: we just initialized the elements `..2`.
unsafe { MaybeUninit::slice_get_ref(&parts[..2]) }
}
}
}
@ -354,7 +362,7 @@ fn digits_to_exp_str<'a>(
exp: i16,
min_ndigits: usize,
upper: bool,
parts: &'a mut [Part<'a>],
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> &'a [Part<'a>] {
assert!(!buf.is_empty());
assert!(buf[0] > b'0');
@ -362,15 +370,15 @@ fn digits_to_exp_str<'a>(
let mut n = 0;
parts[n] = Part::Copy(&buf[..1]);
parts[n] = MaybeUninit::new(Part::Copy(&buf[..1]));
n += 1;
if buf.len() > 1 || min_ndigits > 1 {
parts[n] = Part::Copy(b".");
parts[n + 1] = Part::Copy(&buf[1..]);
parts[n] = MaybeUninit::new(Part::Copy(b"."));
parts[n + 1] = MaybeUninit::new(Part::Copy(&buf[1..]));
n += 2;
if min_ndigits > buf.len() {
parts[n] = Part::Zero(min_ndigits - buf.len());
parts[n] = MaybeUninit::new(Part::Zero(min_ndigits - buf.len()));
n += 1;
}
}
@ -378,13 +386,14 @@ fn digits_to_exp_str<'a>(
// 0.1234 x 10^exp = 1.234 x 10^(exp-1)
let exp = exp as i32 - 1; // avoid underflow when exp is i16::MIN
if exp < 0 {
parts[n] = Part::Copy(if upper { b"E-" } else { b"e-" });
parts[n + 1] = Part::Num(-exp as u16);
parts[n] = MaybeUninit::new(Part::Copy(if upper { b"E-" } else { b"e-" }));
parts[n + 1] = MaybeUninit::new(Part::Num(-exp as u16));
} else {
parts[n] = Part::Copy(if upper { b"E" } else { b"e" });
parts[n + 1] = Part::Num(exp as u16);
parts[n] = MaybeUninit::new(Part::Copy(if upper { b"E" } else { b"e" }));
parts[n + 1] = MaybeUninit::new(Part::Num(exp as u16));
}
&parts[..n + 2]
// SAFETY: we just initialized the elements `..n + 2`.
unsafe { MaybeUninit::slice_get_ref(&parts[..n + 2]) }
}
/// Sign formatting options.
@ -446,6 +455,7 @@ fn determine_sign(sign: Sign, decoded: &FullDecoded, negative: bool) -> &'static
/// (which can be an empty string if no sign is rendered).
///
/// `format_shortest` should be the underlying digit-generation function.
/// It should return the part of the buffer that it initialized.
/// You probably would want `strategy::grisu::format_shortest` for this.
///
/// `frac_digits` can be less than the number of actual fractional digits in `v`;
@ -461,12 +471,12 @@ pub fn to_shortest_str<'a, T, F>(
v: T,
sign: Sign,
frac_digits: usize,
buf: &'a mut [u8],
parts: &'a mut [Part<'a>],
buf: &'a mut [MaybeUninit<u8>],
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> Formatted<'a>
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
assert!(parts.len() >= 4);
assert!(buf.len() >= MAX_SIG_DIGITS);
@ -475,27 +485,31 @@ where
let sign = determine_sign(sign, &full_decoded, negative);
match full_decoded {
FullDecoded::Nan => {
parts[0] = Part::Copy(b"NaN");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"NaN"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Infinite => {
parts[0] = Part::Copy(b"inf");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"inf"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Zero => {
if frac_digits > 0 {
// [0.][0000]
parts[0] = Part::Copy(b"0.");
parts[1] = Part::Zero(frac_digits);
Formatted { sign, parts: &parts[..2] }
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
parts[1] = MaybeUninit::new(Part::Zero(frac_digits));
// SAFETY: we just initialized the elements `..2`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..2]) } }
} else {
parts[0] = Part::Copy(b"0");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"0"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
}
FullDecoded::Finite(ref decoded) => {
let (len, exp) = format_shortest(decoded, buf);
Formatted { sign, parts: digits_to_dec_str(&buf[..len], exp, frac_digits, parts) }
let (buf, exp) = format_shortest(decoded, buf);
Formatted { sign, parts: digits_to_dec_str(buf, exp, frac_digits, parts) }
}
}
}
@ -509,6 +523,7 @@ where
/// an empty string if no sign is rendered).
///
/// `format_shortest` should be the underlying digit-generation function.
/// It should return the part of the buffer that it initialized.
/// You probably would want `strategy::grisu::format_shortest` for this.
///
/// The `dec_bounds` is a tuple `(lo, hi)` such that the number is formatted
@ -525,12 +540,12 @@ pub fn to_shortest_exp_str<'a, T, F>(
sign: Sign,
dec_bounds: (i16, i16),
upper: bool,
buf: &'a mut [u8],
parts: &'a mut [Part<'a>],
buf: &'a mut [MaybeUninit<u8>],
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> Formatted<'a>
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
assert!(parts.len() >= 6);
assert!(buf.len() >= MAX_SIG_DIGITS);
@ -540,28 +555,31 @@ where
let sign = determine_sign(sign, &full_decoded, negative);
match full_decoded {
FullDecoded::Nan => {
parts[0] = Part::Copy(b"NaN");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"NaN"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Infinite => {
parts[0] = Part::Copy(b"inf");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"inf"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Zero => {
parts[0] = if dec_bounds.0 <= 0 && 0 < dec_bounds.1 {
Part::Copy(b"0")
MaybeUninit::new(Part::Copy(b"0"))
} else {
Part::Copy(if upper { b"0E0" } else { b"0e0" })
MaybeUninit::new(Part::Copy(if upper { b"0E0" } else { b"0e0" }))
};
Formatted { sign, parts: &parts[..1] }
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Finite(ref decoded) => {
let (len, exp) = format_shortest(decoded, buf);
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[..len], exp, 0, parts)
digits_to_dec_str(buf, exp, 0, parts)
} else {
digits_to_exp_str(&buf[..len], exp, 0, upper, parts)
digits_to_exp_str(buf, exp, 0, upper, parts)
};
Formatted { sign, parts }
}
@ -600,6 +618,7 @@ fn estimate_max_buf_len(exp: i16) -> usize {
/// an empty string if no sign is rendered).
///
/// `format_exact` should be the underlying digit-generation function.
/// It should return the part of the buffer that it initialized.
/// You probably would want `strategy::grisu::format_exact` for this.
///
/// The byte buffer should be at least `ndigits` bytes long unless `ndigits` is
@ -613,12 +632,12 @@ pub fn to_exact_exp_str<'a, T, F>(
sign: Sign,
ndigits: usize,
upper: bool,
buf: &'a mut [u8],
parts: &'a mut [Part<'a>],
buf: &'a mut [MaybeUninit<u8>],
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> Formatted<'a>
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
assert!(parts.len() >= 6);
assert!(ndigits > 0);
@ -627,23 +646,27 @@ where
let sign = determine_sign(sign, &full_decoded, negative);
match full_decoded {
FullDecoded::Nan => {
parts[0] = Part::Copy(b"NaN");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"NaN"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Infinite => {
parts[0] = Part::Copy(b"inf");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"inf"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Zero => {
if ndigits > 1 {
// [0.][0000][e0]
parts[0] = Part::Copy(b"0.");
parts[1] = Part::Zero(ndigits - 1);
parts[2] = Part::Copy(if upper { b"E0" } else { b"e0" });
Formatted { sign, parts: &parts[..3] }
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
parts[1] = MaybeUninit::new(Part::Zero(ndigits - 1));
parts[2] = MaybeUninit::new(Part::Copy(if upper { b"E0" } else { b"e0" }));
// SAFETY: we just initialized the elements `..3`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..3]) } }
} else {
parts[0] = Part::Copy(if upper { b"0E0" } else { b"0e0" });
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(if upper { b"0E0" } else { b"0e0" }));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
}
FullDecoded::Finite(ref decoded) => {
@ -651,8 +674,8 @@ where
assert!(buf.len() >= ndigits || buf.len() >= maxlen);
let trunc = if ndigits < maxlen { ndigits } else { maxlen };
let (len, exp) = format_exact(decoded, &mut buf[..trunc], i16::MIN);
Formatted { sign, parts: digits_to_exp_str(&buf[..len], exp, ndigits, upper, parts) }
let (buf, exp) = format_exact(decoded, &mut buf[..trunc], i16::MIN);
Formatted { sign, parts: digits_to_exp_str(buf, exp, ndigits, upper, parts) }
}
}
}
@ -665,6 +688,7 @@ where
/// (which can be an empty string if no sign is rendered).
///
/// `format_exact` should be the underlying digit-generation function.
/// It should return the part of the buffer that it initialized.
/// You probably would want `strategy::grisu::format_exact` for this.
///
/// The byte buffer should be enough for the output unless `frac_digits` is
@ -677,12 +701,12 @@ pub fn to_exact_fixed_str<'a, T, F>(
v: T,
sign: Sign,
frac_digits: usize,
buf: &'a mut [u8],
parts: &'a mut [Part<'a>],
buf: &'a mut [MaybeUninit<u8>],
parts: &'a mut [MaybeUninit<Part<'a>>],
) -> Formatted<'a>
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
assert!(parts.len() >= 4);
@ -690,22 +714,26 @@ where
let sign = determine_sign(sign, &full_decoded, negative);
match full_decoded {
FullDecoded::Nan => {
parts[0] = Part::Copy(b"NaN");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"NaN"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Infinite => {
parts[0] = Part::Copy(b"inf");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"inf"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
FullDecoded::Zero => {
if frac_digits > 0 {
// [0.][0000]
parts[0] = Part::Copy(b"0.");
parts[1] = Part::Zero(frac_digits);
Formatted { sign, parts: &parts[..2] }
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
parts[1] = MaybeUninit::new(Part::Zero(frac_digits));
// SAFETY: we just initialized the elements `..2`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..2]) } }
} else {
parts[0] = Part::Copy(b"0");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"0"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
}
FullDecoded::Finite(ref decoded) => {
@ -716,23 +744,25 @@ where
// `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 (len, exp) = format_exact(decoded, &mut buf[..maxlen], limit);
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!(len, 0);
debug_assert_eq!(buf.len(), 0);
if frac_digits > 0 {
// [0.][0000]
parts[0] = Part::Copy(b"0.");
parts[1] = Part::Zero(frac_digits);
Formatted { sign, parts: &parts[..2] }
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
parts[1] = MaybeUninit::new(Part::Zero(frac_digits));
// SAFETY: we just initialized the elements `..2`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..2]) } }
} else {
parts[0] = Part::Copy(b"0");
Formatted { sign, parts: &parts[..1] }
parts[0] = MaybeUninit::new(Part::Copy(b"0"));
// SAFETY: we just initialized the elements `..1`.
Formatted { sign, parts: unsafe { MaybeUninit::slice_get_ref(&parts[..1]) } }
}
} else {
Formatted { sign, parts: digits_to_dec_str(&buf[..len], exp, frac_digits, parts) }
Formatted { sign, parts: digits_to_dec_str(buf, exp, frac_digits, parts) }
}
}
}

View File

@ -5,6 +5,7 @@
//! quickly and accurately. SIGPLAN Not. 31, 5 (May. 1996), 108-116.
use crate::cmp::Ordering;
use crate::mem::MaybeUninit;
use crate::num::bignum::Big32x40 as Big;
use crate::num::bignum::Digit32 as Digit;
@ -97,7 +98,10 @@ fn div_rem_upto_16<'a>(
}
/// The shortest mode implementation for Dragon.
pub fn format_shortest(d: &Decoded, buf: &mut [u8]) -> (/*#digits*/ usize, /*exp*/ i16) {
pub fn format_shortest<'a>(
d: &Decoded,
buf: &'a mut [MaybeUninit<u8>],
) -> (/*digits*/ &'a [u8], /*exp*/ 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
@ -186,7 +190,7 @@ pub fn format_shortest(d: &Decoded, buf: &mut [u8]) -> (/*#digits*/ usize, /*exp
// generate one digit: `d[n] = floor(mant / scale) < 10`.
let (d, _) = div_rem_upto_16(&mut mant, &scale, &scale2, &scale4, &scale8);
debug_assert!(d < 10);
buf[i] = b'0' + d;
buf[i] = MaybeUninit::new(b'0' + d);
i += 1;
// this is a simplified description of the modified Dragon algorithm.
@ -241,18 +245,24 @@ pub fn format_shortest(d: &Decoded, buf: &mut [u8]) -> (/*#digits*/ usize, /*exp
// if rounding up changes the length, the exponent should also change.
// it seems that this condition is very hard to satisfy (possibly impossible),
// but we are just being safe and consistent here.
if let Some(c) = round_up(buf, i) {
buf[i] = c;
// SAFETY: we initialized that memory above.
if let Some(c) = round_up(unsafe { MaybeUninit::slice_get_mut(&mut buf[..i]) }) {
buf[i] = MaybeUninit::new(c);
i += 1;
k += 1;
}
}
(i, k)
// SAFETY: we initialized that memory above.
(unsafe { MaybeUninit::slice_get_ref(&buf[..i]) }, k)
}
/// The exact and fixed mode implementation for Dragon.
pub fn format_exact(d: &Decoded, buf: &mut [u8], limit: i16) -> (/*#digits*/ usize, /*exp*/ i16) {
pub fn format_exact<'a>(
d: &Decoded,
buf: &'a mut [MaybeUninit<u8>],
limit: i16,
) -> (/*digits*/ &'a [u8], /*exp*/ i16) {
assert!(d.mant > 0);
assert!(d.minus > 0);
assert!(d.plus > 0);
@ -319,9 +329,10 @@ pub fn format_exact(d: &Decoded, buf: &mut [u8], limit: i16) -> (/*#digits*/ usi
// following digits are all zeroes, we stop here
// do *not* try to perform rounding! rather, fill remaining digits.
for c in &mut buf[i..len] {
*c = b'0';
*c = MaybeUninit::new(b'0');
}
return (len, k);
// SAFETY: we initialized that memory above.
return (unsafe { MaybeUninit::slice_get_ref(&buf[..len]) }, k);
}
let mut d = 0;
@ -343,7 +354,7 @@ pub fn format_exact(d: &Decoded, buf: &mut [u8], limit: i16) -> (/*#digits*/ usi
}
debug_assert!(mant < scale);
debug_assert!(d < 10);
buf[i] = b'0' + d;
buf[i] = MaybeUninit::new(b'0' + d);
mant.mul_small(10);
}
}
@ -353,21 +364,25 @@ pub fn format_exact(d: &Decoded, buf: &mut [u8], limit: i16) -> (/*#digits*/ usi
// round to even (i.e., avoid rounding up when the prior digit is even).
let order = mant.cmp(scale.mul_small(5));
if order == Ordering::Greater
|| (order == Ordering::Equal && (len == 0 || buf[len - 1] & 1 == 1))
|| (order == Ordering::Equal
// SAFETY: `buf[len-1]` is initialized.
&& (len == 0 || unsafe { buf[len - 1].assume_init() } & 1 == 1))
{
// if rounding up changes the length, the exponent should also change.
// but we've been requested a fixed number of digits, so do not alter the buffer...
if let Some(c) = round_up(buf, len) {
// SAFETY: we initialized that memory above.
if let Some(c) = round_up(unsafe { MaybeUninit::slice_get_mut(&mut buf[..len]) }) {
// ...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).
k += 1;
if k > limit && len < buf.len() {
buf[len] = c;
buf[len] = MaybeUninit::new(c);
len += 1;
}
}
}
(len, k)
// SAFETY: we initialized that memory above.
(unsafe { MaybeUninit::slice_get_ref(&buf[..len]) }, k)
}

View File

@ -5,6 +5,7 @@
//! [^1]: Florian Loitsch. 2010. Printing floating-point numbers quickly and
//! accurately with integers. SIGPLAN Not. 45, 6 (June 2010), 233-243.
use crate::mem::MaybeUninit;
use crate::num::diy_float::Fp;
use crate::num::flt2dec::{round_up, Decoded, MAX_SIG_DIGITS};
@ -161,10 +162,10 @@ pub fn max_pow10_no_more_than(x: u32) -> (u8, u32) {
/// The shortest mode implementation for Grisu.
///
/// It returns `None` when it would return an inexact representation otherwise.
pub fn format_shortest_opt(
pub fn format_shortest_opt<'a>(
d: &Decoded,
buf: &mut [u8],
) -> Option<(/*#digits*/ usize, /*exp*/ i16)> {
buf: &'a mut [MaybeUninit<u8>],
) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> {
assert!(d.mant > 0);
assert!(d.minus > 0);
assert!(d.plus > 0);
@ -266,14 +267,23 @@ pub fn format_shortest_opt(
let q = remainder / ten_kappa;
let r = remainder % ten_kappa;
debug_assert!(q < 10);
buf[i] = b'0' + q as u8;
buf[i] = MaybeUninit::new(b'0' + q as u8);
i += 1;
let plus1rem = ((r as u64) << e) + plus1frac; // == (plus1 % 10^kappa) * 2^e
if plus1rem < delta1 {
// `plus1 % 10^kappa < delta1 = plus1 - minus1`; we've found the correct `kappa`.
let ten_kappa = (ten_kappa as u64) << e; // scale 10^kappa back to the shared exponent
return round_and_weed(&mut buf[..i], exp, plus1rem, delta1, plus1 - v.f, ten_kappa, 1);
return round_and_weed(
// SAFETY: we initialized that memory above.
unsafe { MaybeUninit::slice_get_mut(&mut buf[..i]) },
exp,
plus1rem,
delta1,
plus1 - v.f,
ten_kappa,
1,
);
}
// break the loop when we have rendered all integral digits.
@ -310,13 +320,14 @@ pub fn format_shortest_opt(
let q = remainder >> e;
let r = remainder & ((1 << e) - 1);
debug_assert!(q < 10);
buf[i] = b'0' + q as u8;
buf[i] = MaybeUninit::new(b'0' + q as u8);
i += 1;
if r < threshold {
let ten_kappa = 1 << e; // implicit divisor
return round_and_weed(
&mut buf[..i],
// SAFETY: we initialized that memory above.
unsafe { MaybeUninit::slice_get_mut(&mut buf[..i]) },
exp,
r,
threshold,
@ -355,7 +366,7 @@ pub fn format_shortest_opt(
plus1v: u64,
ten_kappa: u64,
ulp: u64,
) -> Option<(usize, i16)> {
) -> Option<(&[u8], i16)> {
assert!(!buf.is_empty());
// produce two approximations to `v` (actually `plus1 - v`) within 1.5 ulps.
@ -437,20 +448,22 @@ pub fn format_shortest_opt(
// this is too liberal, though, so we reject any `w(n)` not between `plus0` and `minus0`,
// i.e., `plus1 - plus1w(n) <= minus0` or `plus1 - plus1w(n) >= plus0`. we utilize the facts
// that `threshold = plus1 - minus1` and `plus1 - plus0 = minus0 - minus1 = 2 ulp`.
if 2 * ulp <= plus1w && plus1w <= threshold - 4 * ulp {
Some((buf.len(), exp))
} else {
None
}
if 2 * ulp <= plus1w && plus1w <= threshold - 4 * ulp { Some((buf, exp)) } else { None }
}
}
/// The shortest mode implementation for Grisu with Dragon fallback.
///
/// This should be used for most cases.
pub fn format_shortest(d: &Decoded, buf: &mut [u8]) -> (/*#digits*/ usize, /*exp*/ i16) {
pub fn format_shortest<'a>(
d: &Decoded,
buf: &'a mut [MaybeUninit<u8>],
) -> (/*digits*/ &'a [u8], /*exp*/ i16) {
use crate::num::flt2dec::strategy::dragon::format_shortest as fallback;
match format_shortest_opt(d, buf) {
// 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),
}
@ -459,11 +472,11 @@ pub fn format_shortest(d: &Decoded, buf: &mut [u8]) -> (/*#digits*/ usize, /*exp
/// The exact and fixed mode implementation for Grisu.
///
/// It returns `None` when it would return an inexact representation otherwise.
pub fn format_exact_opt(
pub fn format_exact_opt<'a>(
d: &Decoded,
buf: &mut [u8],
buf: &'a mut [MaybeUninit<u8>],
limit: i16,
) -> Option<(/*#digits*/ usize, /*exp*/ i16)> {
) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> {
assert!(d.mant > 0);
assert!(d.mant < (1 << 61)); // we need at least three bits of additional precision
assert!(!buf.is_empty());
@ -510,7 +523,11 @@ pub fn format_exact_opt(
// thus we are being sloppy here and widen the error range by a factor of 10.
// this will increase the false negative rate, but only very, *very* slightly;
// it can only matter noticeably when the mantissa is bigger than 60 bits.
return possibly_round(buf, 0, exp, limit, v.f / 10, (max_ten_kappa as u64) << e, err << e);
//
// 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)
};
} else if ((exp as i32 - limit as i32) as usize) < buf.len() {
(exp - limit) as usize
} else {
@ -534,13 +551,16 @@ pub fn format_exact_opt(
let q = remainder / ten_kappa;
let r = remainder % ten_kappa;
debug_assert!(q < 10);
buf[i] = b'0' + q as u8;
buf[i] = MaybeUninit::new(b'0' + q as u8);
i += 1;
// is the buffer full? run the rounding pass with the remainder.
if i == len {
let vrem = ((r as u64) << e) + vfrac; // == (v % 10^kappa) * 2^e
return possibly_round(buf, len, exp, limit, vrem, (ten_kappa as u64) << e, err << e);
// SAFETY: we have initialized `len` many bytes.
return unsafe {
possibly_round(buf, len, exp, limit, vrem, (ten_kappa as u64) << e, err << e)
};
}
// break the loop when we have rendered all integral digits.
@ -585,12 +605,13 @@ pub fn format_exact_opt(
let q = remainder >> e;
let r = remainder & ((1 << e) - 1);
debug_assert!(q < 10);
buf[i] = b'0' + q as u8;
buf[i] = MaybeUninit::new(b'0' + q as u8);
i += 1;
// is the buffer full? run the rounding pass with the remainder.
if i == len {
return possibly_round(buf, len, exp, limit, r, 1 << e, err);
// SAFETY: we have initialized `len` many bytes.
return unsafe { possibly_round(buf, len, exp, limit, r, 1 << e, err) };
}
// restore invariants
@ -610,15 +631,17 @@ pub fn format_exact_opt(
// - `remainder = (v % 10^kappa) * k`
// - `ten_kappa = 10^kappa * k`
// - `ulp = 2^-e * k`
fn possibly_round(
buf: &mut [u8],
//
// SAFETY: the first `len` bytes of `buf` must be initialized.
unsafe fn possibly_round(
buf: &mut [MaybeUninit<u8>],
mut len: usize,
mut exp: i16,
limit: i16,
remainder: u64,
ten_kappa: u64,
ulp: u64,
) -> Option<(usize, i16)> {
) -> Option<(&[u8], i16)> {
debug_assert!(remainder < ten_kappa);
// 10^kappa
@ -677,7 +700,8 @@ pub fn format_exact_opt(
// we've already verified that `ulp < 10^kappa / 2`, so as long as
// `10^kappa` did not overflow after all, the second check is fine.
if ten_kappa - remainder > remainder && ten_kappa - 2 * remainder >= 2 * ulp {
return Some((len, exp));
// SAFETY: our caller initialized that memory.
return Some((unsafe { MaybeUninit::slice_get_ref(&buf[..len]) }, exp));
}
// :<------- remainder ------>| :
@ -698,17 +722,19 @@ pub fn format_exact_opt(
// as `10^kappa` is never zero). also note that `remainder - ulp <= 10^kappa`,
// so the second check does not overflow.
if remainder > ulp && ten_kappa - (remainder - ulp) <= remainder - ulp {
if let Some(c) = round_up(buf, len) {
// SAFETY: our caller must have initialized that memory.
if let Some(c) = round_up(unsafe { MaybeUninit::slice_get_mut(&mut buf[..len]) }) {
// only add an additional digit when we've been requested the fixed precision.
// we also need to check that, if the original buffer was empty,
// the additional digit can only be added when `exp == limit` (edge case).
exp += 1;
if exp > limit && len < buf.len() {
buf[len] = c;
buf[len] = MaybeUninit::new(c);
len += 1;
}
}
return Some((len, exp));
// SAFETY: we and our caller initialized that memory.
return Some((unsafe { MaybeUninit::slice_get_ref(&buf[..len]) }, exp));
}
// otherwise we are doomed (i.e., some values between `v - 1 ulp` and `v + 1 ulp` are
@ -720,9 +746,16 @@ pub fn format_exact_opt(
/// The exact and fixed mode implementation for Grisu with Dragon fallback.
///
/// This should be used for most cases.
pub fn format_exact(d: &Decoded, buf: &mut [u8], limit: i16) -> (/*#digits*/ usize, /*exp*/ i16) {
pub fn format_exact<'a>(
d: &Decoded,
buf: &'a mut [MaybeUninit<u8>],
limit: i16,
) -> (/*digits*/ &'a [u8], /*exp*/ i16) {
use crate::num::flt2dec::strategy::dragon::format_exact as fallback;
match format_exact_opt(d, buf, limit) {
// 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),
}

View File

@ -1,3 +1,4 @@
use std::mem::MaybeUninit;
use std::{fmt, str};
use core::num::flt2dec::{decode, DecodableFloat, Decoded, FullDecoded};
@ -36,20 +37,20 @@ macro_rules! check_shortest {
);
($f:ident($v:expr) => $buf:expr, $exp:expr; $fmt:expr, $($key:ident = $val:expr),*) => ({
let mut buf = [b'_'; MAX_SIG_DIGITS];
let (len, k) = $f(&decode_finite($v), &mut buf);
assert!((&buf[..len], k) == ($buf, $exp),
$fmt, actual = (str::from_utf8(&buf[..len]).unwrap(), k),
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 = [b'_'; MAX_SIG_DIGITS];
let (len, k) = $f(&Decoded { $($k: $v),+ }, &mut buf);
assert!((&buf[..len], k) == ($buf, $exp),
$fmt, actual = (str::from_utf8(&buf[..len]).unwrap(), k),
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),*);
})
@ -58,9 +59,9 @@ 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 (len, k) = $f($decoded, &mut $buf[..$expected.len()], i16::MIN);
assert!((&$buf[..len], k) == ($expected, $expectedk),
$fmt, actual = (str::from_utf8(&$buf[..len]).unwrap(), k),
let (buf, k) = $f($decoded, &mut $buf[..$expected.len()], i16::MIN);
assert!((buf, k) == ($expected, $expectedk),
$fmt, actual = (str::from_utf8(buf).unwrap(), k),
expected = (str::from_utf8($expected).unwrap(), $expectedk),
$($key = $val),*);
})
@ -69,9 +70,9 @@ macro_rules! try_exact {
macro_rules! try_fixed {
($f:ident($decoded:expr) => $buf:expr, $request:expr, $expected:expr, $expectedk:expr;
$fmt:expr, $($key:ident = $val:expr),*) => ({
let (len, k) = $f($decoded, &mut $buf[..], $request);
assert!((&$buf[..len], k) == ($expected, $expectedk),
$fmt, actual = (str::from_utf8(&$buf[..len]).unwrap(), k),
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),*);
})
@ -93,10 +94,10 @@ fn ldexp_f64(a: f64, b: i32) -> f64 {
fn check_exact<F, T>(mut f: F, v: T, vstr: &str, expected: &[u8], expectedk: i16)
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
// use a large enough buffer
let mut buf = [b'_'; 1024];
let mut buf = [MaybeUninit::new(b'_'); 1024];
let mut expected_ = [b'_'; 1024];
let decoded = decode_finite(v);
@ -118,7 +119,7 @@ where
// 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) {
if let Some(_) = round_up(&mut expected_[..i]) {
expectedk_ += 1;
}
}
@ -193,10 +194,10 @@ impl TestableFloat for f64 {
fn check_exact_one<F, T>(mut f: F, x: i64, e: isize, tstr: &str, expected: &[u8], expectedk: i16)
where
T: TestableFloat,
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
// use a large enough buffer
let mut buf = [b'_'; 1024];
let mut buf = [MaybeUninit::new(b'_'); 1024];
let v: T = TestableFloat::ldexpi(x, e);
let decoded = decode_finite(v);
@ -230,7 +231,7 @@ macro_rules! check_exact_one {
pub fn f32_shortest_sanity_test<F>(mut f: F)
where
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
// 0.0999999940395355224609375
// 0.100000001490116119384765625
@ -277,7 +278,7 @@ where
pub fn f32_exact_sanity_test<F>(mut f: F)
where
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
let minf32 = ldexp_f32(1.0, -149);
@ -321,7 +322,7 @@ where
pub fn f64_shortest_sanity_test<F>(mut f: F)
where
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
// 0.0999999999999999777955395074968691915273...
// 0.1000000000000000055511151231257827021181...
@ -387,7 +388,7 @@ where
pub fn f64_exact_sanity_test<F>(mut f: F)
where
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
let minf64 = ldexp_f64(1.0, -1074);
@ -474,7 +475,7 @@ where
pub fn more_shortest_sanity_test<F>(mut f: F)
where
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
check_shortest!(f{mant: 99_999_999_999_999_999, minus: 1, plus: 1,
exp: 0, inclusive: true} => b"1", 18);
@ -484,10 +485,10 @@ where
fn to_string_with_parts<F>(mut f: F) -> String
where
F: for<'a> FnMut(&'a mut [u8], &'a mut [Part<'a>]) -> Formatted<'a>,
F: for<'a> FnMut(&'a mut [MaybeUninit<u8>], &'a mut [MaybeUninit<Part<'a>>]) -> Formatted<'a>,
{
let mut buf = [0; 1024];
let mut parts = [Part::Zero(0); 16];
let mut buf = [MaybeUninit::new(0); 1024];
let mut parts = [MaybeUninit::new(Part::Zero(0)); 16];
let formatted = f(&mut buf, &mut parts);
let mut ret = vec![0; formatted.len()];
assert_eq!(formatted.write(&mut ret), Some(ret.len()));
@ -496,14 +497,14 @@ where
pub fn to_shortest_str_test<F>(mut f_: F)
where
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
use core::num::flt2dec::Sign::*;
fn to_string<T, F>(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
to_string_with_parts(|buf, parts| {
to_shortest_str(|d, b| f(d, b), v, sign, frac_digits, buf, parts)
@ -597,14 +598,14 @@ where
pub fn to_shortest_exp_str_test<F>(mut f_: F)
where
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
use core::num::flt2dec::Sign::*;
fn to_string<T, F>(f: &mut F, v: T, sign: Sign, exp_bounds: (i16, i16), upper: bool) -> String
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
to_string_with_parts(|buf, parts| {
to_shortest_exp_str(|d, b| f(d, b), v, sign, exp_bounds, upper, buf, parts)
@ -716,14 +717,14 @@ where
pub fn to_exact_exp_str_test<F>(mut f_: F)
where
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
use core::num::flt2dec::Sign::*;
fn to_string<T, F>(f: &mut F, v: T, sign: Sign, ndigits: usize, upper: bool) -> String
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
to_string_with_parts(|buf, parts| {
to_exact_exp_str(|d, b, l| f(d, b, l), v, sign, ndigits, upper, buf, parts)
@ -989,14 +990,14 @@ where
pub fn to_exact_fixed_str_test<F>(mut f_: F)
where
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
use core::num::flt2dec::Sign::*;
fn to_string<T, F>(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String
where
T: DecodableFloat,
F: FnMut(&Decoded, &mut [u8], i16) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
to_string_with_parts(|buf, parts| {
to_exact_fixed_str(|d, b, l| f(d, b, l), v, sign, frac_digits, buf, parts)

View File

@ -1,5 +1,6 @@
#![cfg(not(target_arch = "wasm32"))]
use std::mem::MaybeUninit;
use std::str;
use core::num::flt2dec::strategy::grisu::format_exact_opt;
@ -20,8 +21,8 @@ pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
fn iterate<F, G, V>(func: &str, k: usize, n: usize, mut f: F, mut g: G, mut v: V) -> (usize, usize)
where
F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
V: FnMut(usize) -> Decoded,
{
assert!(k <= 1024);
@ -42,11 +43,11 @@ where
}
let decoded = v(i);
let mut buf1 = [0; 1024];
if let Some((len1, e1)) = f(&decoded, &mut buf1[..k]) {
let mut buf2 = [0; 1024];
let (len2, e2) = g(&decoded, &mut buf2[..k]);
if e1 == e2 && &buf1[..len1] == &buf2[..len2] {
let mut buf1 = [MaybeUninit::new(0); 1024];
if let Some((buf1, e1)) = f(&decoded, &mut buf1[..k]) {
let mut buf2 = [MaybeUninit::new(0); 1024];
let (buf2, e2) = g(&decoded, &mut buf2[..k]);
if e1 == e2 && buf1 == buf2 {
npassed += 1;
} else {
println!(
@ -54,9 +55,9 @@ where
i,
n,
decoded,
str::from_utf8(&buf1[..len1]).unwrap(),
str::from_utf8(buf1).unwrap(),
e1,
str::from_utf8(&buf2[..len2]).unwrap(),
str::from_utf8(buf2).unwrap(),
e2
);
}
@ -85,8 +86,8 @@ where
pub fn f32_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
where
F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
if cfg!(target_os = "emscripten") {
return; // using rng pulls in i128 support, which doesn't work
@ -101,8 +102,8 @@ where
pub fn f64_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
where
F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
if cfg!(target_os = "emscripten") {
return; // using rng pulls in i128 support, which doesn't work
@ -117,8 +118,8 @@ where
pub fn f32_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
where
F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'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?