mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-31 06:06:48 -06:00
Merge PR #1438: Fix fraction numerators saturating at u64::MAX for large values
This commit is contained in:
@@ -466,20 +466,26 @@ fn render_fraction(sec: &Section, x: f64, pieces: &mut [String], lits: &mut [Opt
|
||||
let has_q = |idx: &[usize]| idx.iter().any(|&i| toks.get(i) == Some(&Tok::Digit('?')));
|
||||
let has_whole = !whole_idx.is_empty();
|
||||
let (mut whole, f) = if has_whole { (x.floor(), x - x.floor()) } else { (0.0, x) };
|
||||
let (mut num, den) = match fixed_den {
|
||||
Some(d) => ((f * d as f64).round() as u64, d),
|
||||
// Without a whole part the numerator carries the whole value and can pass u64::MAX, so it
|
||||
// stays an f64 (a float-to-u64 cast saturates).
|
||||
let (mut num, den): (f64, u64) = match fixed_den {
|
||||
Some(d) => ((f * d as f64).round(), d),
|
||||
None if f >= 1e15 => (f.round(), 1),
|
||||
None => {
|
||||
let digits = den_idx.len().clamp(1, 7) as u32;
|
||||
approximate(f, 10u64.pow(digits) - 1)
|
||||
let (n, d) = approximate(f, 10u64.pow(digits) - 1);
|
||||
(n as f64, d)
|
||||
}
|
||||
};
|
||||
if has_whole && num >= den && den > 0 {
|
||||
whole += (num / den) as f64;
|
||||
num %= den;
|
||||
if has_whole && num >= den as f64 && den > 0 {
|
||||
// The fractional part is below 1, so the numerator is at most the denominator here.
|
||||
let n = num as u64;
|
||||
whole += (n / den) as f64;
|
||||
num = (n % den) as f64;
|
||||
}
|
||||
let whole_s = if whole >= 1.0 { fixed(whole, 0).0 } else { String::new() };
|
||||
let num_zero_ph = num_idx.iter().any(|&i| toks.get(i) == Some(&Tok::Digit('0')));
|
||||
if has_whole && num == 0 && !num_zero_ph {
|
||||
if has_whole && num == 0.0 && !num_zero_ph {
|
||||
fill_int(toks, &whole_idx, &whole_s, sec.thousands, true, pieces);
|
||||
let spaces = has_q(&whole_idx) || has_q(&num_idx) || has_q(&den_idx);
|
||||
blank_range(toks, sep_start..den_end, spaces, pieces, lits);
|
||||
@@ -488,7 +494,7 @@ fn render_fraction(sec: &Section, x: f64, pieces: &mut [String], lits: &mut [Opt
|
||||
if has_whole {
|
||||
// A zero value shows `0` in a `?` or `0` units place, nothing in a `#` one.
|
||||
let units_hash = whole_idx.last().is_some_and(|&i| toks.get(i) == Some(&Tok::Digit('#')));
|
||||
let force = whole_s.is_empty() && num == 0 && !units_hash;
|
||||
let force = whole_s.is_empty() && num == 0.0 && !units_hash;
|
||||
fill_int(toks, &whole_idx, &whole_s, sec.thousands, force, pieces);
|
||||
let whole_shown = whole_idx.iter().any(|&i| pieces.get(i).is_some_and(|p| p.chars().any(|c| c.is_ascii_digit())));
|
||||
if !whole_shown {
|
||||
@@ -496,7 +502,8 @@ fn render_fraction(sec: &Section, x: f64, pieces: &mut [String], lits: &mut [Opt
|
||||
blank_range(toks, sep_start..num_start, spaces, pieces, lits);
|
||||
}
|
||||
}
|
||||
fill_int(toks, &num_idx, &num.to_string(), false, true, pieces);
|
||||
let num_s = if num < 1e15 { (num as u64).to_string() } else { fixed(num, 0).0 };
|
||||
fill_int(toks, &num_idx, &num_s, false, true, pieces);
|
||||
if let Some(p) = pieces.get_mut(slash) {
|
||||
*p = "/".into();
|
||||
}
|
||||
|
||||
@@ -145,6 +145,18 @@ fn fractions() {
|
||||
assert_eq!(f("# ???/???", 0.5), " 1/2 ");
|
||||
}
|
||||
|
||||
/// A fraction without a whole part puts the whole value in the numerator; large values must
|
||||
/// not saturate at u64::MAX.
|
||||
#[test]
|
||||
fn fraction_numerator_beyond_u64() {
|
||||
assert_eq!(f("?/1000000000", 2e10), "20000000000000000000/1000000000");
|
||||
assert_eq!(f("?/?", 1e20), "100000000000000000000/1");
|
||||
assert_eq!(f("?/100", 1e18), "100000000000000000000/100");
|
||||
assert_eq!(f("??/??", 1e25), "10000000000000000000000000/1 ");
|
||||
assert!(!f("?/10", 1e300).contains("18446744073709551615"));
|
||||
assert_eq!(f("?/1000000000", 2.5), "2500000000/1000000000");
|
||||
}
|
||||
|
||||
/// Fraction sections with literal text, as Excel renders them (observed `TEXT()` results).
|
||||
#[test]
|
||||
fn fractions_with_literals() {
|
||||
|
||||
Reference in New Issue
Block a user