diff --git a/crates/functions/src/math.rs b/crates/functions/src/math.rs index 7c84d553b..37c617fc0 100644 --- a/crates/functions/src/math.rs +++ b/crates/functions/src/math.rs @@ -520,7 +520,7 @@ fn integer_args(a: &[Arg]) -> R> { v.iter() .map(|x| { let t = x.trunc(); - if !(0.0..9.007_199_254_740_992e15).contains(&t) { Err(CellError::Num) } else { Ok(t as u64) } + if !(0.0..9.007_199_254_740_992e15).contains(x) { Err(CellError::Num) } else { Ok(t as u64) } }) .collect() } diff --git a/crates/functions/src/math/tests.rs b/crates/functions/src/math/tests.rs index 92449eb26..b6143b4f6 100644 --- a/crates/functions/src/math/tests.rs +++ b/crates/functions/src/math/tests.rs @@ -229,6 +229,20 @@ fn combinatorics() { is_err(ev("MULTINOMIAL", vec![n(-1.0), n(2.0)]), Num); } +#[test] +fn gcd_and_lcm_reject_negative_fractions_before_truncation() { + for name in ["GCD", "LCM"] { + for x in [-0.1, -0.5, -0.999] { + is_err(ev2(name, x, 2.0), Num); + is_err(ev(name, vec![rf(row(&[2.0, x]))]), Num); + // A zero elsewhere must not hide an invalid negative argument. + is_err(ev2(name, 0.0, x), Num); + } + close(ev2(name, -0.0, 2.0), if name == "GCD" { 2.0 } else { 0.0 }); + close(ev2(name, 12.9, 18.9), if name == "GCD" { 6.0 } else { 36.0 }); + } +} + #[test] fn trig() { close(ev1("SIN", PI / 2.0), 1.0); diff --git a/docs/function-parity.md b/docs/function-parity.md index 7dec2c03c..272056909 100644 --- a/docs/function-parity.md +++ b/docs/function-parity.md @@ -1,6 +1,6 @@ # Function parity: worksheet functions and formulas -> **Last reviewed:** 2026-10-31 · **Last updated:** 2026-11-03 · **Change:** minor (GROUPBY total depth 2 or more adds subtotals; earlier: Python cells are edited in the formula bar; earlier: the Python errors `#PYTHON!`, `#BUSY!`, `#TIMEOUT!` and `#BLOCKED!`, and a Python in cells row; earlier: YEARFRAC basis 0 February-end rule, MOD quotient limit, PERCENTRANK significance; earlier: edge cases matched to Excel: base conversions, BITLSHIFT/BITRSHIFT, UNICHAR noncharacters, UPPER/LOWER/PROPER casing, FIXED/DOLLAR output, F/T/GAMMA/WEIBULL/BETA distributions, BINOM trial cap, HARMEAN; earlier: ECMA.CEILING listed with USDOLLAR as not on Microsoft's list; earlier: IMSQRT reproduces the stray real part Excel gives for negative reals) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-31 · **Last updated:** 2026-11-04 · **Change:** minor (GCD and LCM reject negative fractional arguments before truncation; earlier: GROUPBY total depth 2 or more adds subtotals; earlier: Python cells are edited in the formula bar; earlier: the Python errors `#PYTHON!`, `#BUSY!`, `#TIMEOUT!` and `#BLOCKED!`, and a Python in cells row; earlier: YEARFRAC basis 0 February-end rule, MOD quotient limit, PERCENTRANK significance; earlier: edge cases matched to Excel: base conversions, BITLSHIFT/BITRSHIFT, UNICHAR noncharacters, UPPER/LOWER/PROPER casing, FIXED/DOLLAR output, F/T/GAMMA/WEIBULL/BETA distributions, BINOM trial cap, HARMEAN; earlier: ECMA.CEILING listed with USDOLLAR as not on Microsoft's list; earlier: IMSQRT reproduces the stray real part Excel gives for negative reals) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 Excel's worksheet functions, category by category, against GridCraft's function library. Part of [target-app-parity.md](target-app-parity.md); gaps feed [gaps.md](gaps.md). @@ -132,10 +132,16 @@ Work to close (16–26 h in total): the in-scope missing functions (10–16 h wi an oracle corpus of Excel-computed results per function (owner runs Excel on generated workbooks; results checked in as data, not Excel files) and fixing what it finds (6–10 h). +GCD and LCM validate the sign of the original argument before truncating it. Negative +fractions such as `-0.5` return `#NUM!`, including inside ranges and beside a zero argument. +Positive fractional inputs still truncate, and signed zero remains valid. Covered by +`gcd_and_lcm_reject_negative_fractions_before_truncation`; function counts and estimates are unchanged. + ## Revision history | Date | Change | Summary | |---|---|---| +| 2026-11-04 | minor | GCD and LCM reject negative fractional arguments before truncation; scalar and range regressions in the extracted math tests preserve positive truncation and signed-zero behavior; estimates unchanged | | 2026-11-03 | minor | GROUPBY: a total depth of 2 or more adds a subtotal line for each group of the leading key columns (after the group, or before it for a negative depth) and labels the last line Grand Total; a depth past the key columns, or above 1 with `field_relationship` 1, is `#VALUE!`, as in Excel (#1456 item 1) | | 2026-11-01 | minor | Python in cells row: Python mode in the formula bar | | 2026-11-01 | minor | Python in cells (stored, not run): `#PYTHON!`, `#BUSY!`, `#TIMEOUT!` and `#BLOCKED!` done, with their `ERROR.TYPE` numbers; `PY` still counted as missing |