Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions library/core/src/fmt/float.rs
Comment thread
pascaldekloe marked this conversation as resolved.
Comment thread
tgross35 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ where
let mut buf: [MaybeUninit<u8>; 1024] = [MaybeUninit::uninit(); 1024]; // enough for f32 and f64
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 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) }
}

Expand All @@ -73,14 +73,14 @@ where
[MaybeUninit::uninit(); flt2dec::MAX_SIG_DIGITS];
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 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) }
}

Expand Down Expand Up @@ -118,15 +118,15 @@ where
let mut buf: [MaybeUninit<u8>; 1024] = [MaybeUninit::uninit(); 1024]; // enough for f32 and f64
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 6] = [MaybeUninit::uninit(); 6];
let formatted = flt2dec::to_exact_exp_str(
flt2dec::strategy::grisu::format_exact,
flt2dec::format_fixed,
*num,
sign,
precision.into(),
upper,
&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) }
}

Expand All @@ -147,15 +147,15 @@ where
[MaybeUninit::uninit(); flt2dec::MAX_SIG_DIGITS];
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 6] = [MaybeUninit::uninit(); 6];
let formatted = flt2dec::to_shortest_exp_str(
flt2dec::strategy::grisu::format_shortest,
flt2dec::format_short,
*num,
sign,
(0, 0),
upper,
&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) }
}

Expand Down
98 changes: 79 additions & 19 deletions library/core/src/num/imp/flt2dec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -142,6 +130,79 @@ pub mod strategy {
/// The exact formula is `ceil(# bits in mantissa * log_10 2 + 1)`.
pub const MAX_SIG_DIGITS: usize = 17;

/// Formats a finite, non-zero floating-point number in decimal form.
///
/// The return pair `(digits, pow10)` represents:
///
/// v = (0.d₀d₁…dₙ₋₁) × 10ᵏ
///
/// where `digits = [d₀,…,dₙ₋₁]` and `pow10 = k`. The leading digit satisfies
/// `d₀ ≠ 0`, ensuring `0.1 ≤ mantissa < 1`.
///
/// Given an input floating-point value `f`, the produced decimal `v` is the
/// closest such `n`-digit value satisfying:
///
/// |f − v| ≤ ½ × 10^(k−n)
///
/// If two `n`-digit decimals are equally close, a deterministic tie-breaking
/// rule is applied so that parsing the produced decimal recovers `f` exactly.
///
/// In short mode, the minimal `n ≥ 1` satisfying the above property is chosen.
/// The result is therefore the shortest decimal that round-trips back to the
/// original floating-point value. This formatting matches common expectations;
/// for example `0.1f32` prints as `"0.1"`.
pub fn format_short<'a>(d: &Decoded, buf: &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16) {
Comment thread
pascaldekloe marked this conversation as resolved.
// SAFETY: The borrow checker is not smart enough to let us use `buf`
// in the second branch, so we launder the lifetime here. But we only re-use
// `buf` if `format_short` returned `None` so this is okay.
match strategy::grisu::format_short(d, unsafe { &mut *(buf as *mut _) }) {
Some(ret) => ret,
None => strategy::dragon::format_short(d, buf),
}
}

/// Disables `format_fixed` argument explicitly.
pub const UNLIMITED_RESOLUTION: i16 = i16::MIN;

/// Formats a finite, non-zero floating-point number in decimal form.
///
/// The return pair `(digits, pow10)` represents:
///
/// v = (0.d₀d₁…dₙ₋₁) × 10ᵏ
///
/// where `digits = [d₀,…,dₙ₋₁]` and `pow10 = k`. The leading digit satisfies
/// `d₀ ≠ 0`, ensuring `0.1 ≤ mantissa < 1`.
///
/// Given an input floating-point value `f`, the produced decimal `v` is the
/// closest such `n`-digit value satisfying:
///
/// |f − v| ≤ ½ × 10^(k−n)
///
/// If two `n`-digit decimals are equally close, a deterministic tie-breaking
/// rule is applied so that parsing the produced decimal recovers `f` exactly.
///
/// In fixed mode, the number of digits `n` is limited by the buffer size. The
/// `resolution` parameter may further restrict `n` by requiring `v` to be an
/// integer multiple of `10^resolution`. For example, a resolution of `-3`
/// causes rounding to three decimal places, i.e., values are multiples of
/// `0.001`. Use of [`UNLIMITED_RESOLUTION`] can get expensive.
///
/// Note: If the resolution causes the value to round to zero, then the returned
/// digit slice is empty. This preserves the invariant d₀ ≠ 0.
pub fn format_fixed<'a>(
d: &Decoded,
buf: &'a mut [MaybeUninit<u8>],
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)]
Expand Down Expand Up @@ -578,7 +639,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) }
}
}
Expand Down Expand Up @@ -653,13 +714,12 @@ 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
// `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);
Comment thread
pascaldekloe marked this conversation as resolved.
if frac_digits > 0 {
// [0.][0000]
parts[0] = MaybeUninit::new(Part::Copy(b"0."));
Expand Down
26 changes: 13 additions & 13 deletions library/core/src/num/imp/flt2dec/strategy/dragon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>],
) -> (/*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
Expand Down Expand Up @@ -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<u8>],
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);
Expand Down Expand Up @@ -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()
};
Expand Down Expand Up @@ -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;
}
Expand Down
67 changes: 16 additions & 51 deletions library/core/src/num/imp/flt2dec/strategy/grisu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>],
) -> Option<(/*digits*/ &'a [u8], /*exp*/ i16)> {
) -> Option<(/*digits*/ &'a [u8], /*pow10*/ i16)> {
assert!(d.mant > 0);
assert!(d.minus > 0);
assert!(d.plus > 0);
Expand Down Expand Up @@ -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<u8>],
) -> (/*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<u8>],
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());
Expand Down Expand Up @@ -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.
//
Expand All @@ -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()
};
Expand Down Expand Up @@ -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)
};
}

Expand Down Expand Up @@ -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
Expand All @@ -651,7 +634,7 @@ pub fn format_exact_opt<'a>(
buf: &mut [MaybeUninit<u8>],
mut len: usize,
mut exp: i16,
limit: i16,
resolution: i16,
remainder: u64,
ten_kappa: u64,
ulp: u64,
Expand Down Expand Up @@ -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;
}
Expand All @@ -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<u8>],
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),
}
}
Loading
Loading