fix(functions): reject negative fractions in GCD and LCM (#2072)

* M0: reject negative fractional GCD and LCM arguments

Validate the original input before truncation. Add scalar and range regressions, including signed zero, positive fractions and zero-first LCM validation.

* M0: document negative-fraction GCD and LCM regression

Record tested edge-case behavior and update the parity revision history. Function counts and readiness estimates remain unchanged.

* M0: preserve GCD and LCM regression after math test extraction

Move the original negative-fraction regression into the existing extracted math tests. Scalar and range cases, zero-first validation, signed zero and positive truncation coverage are unchanged.
This commit is contained in:
Mex
2026-11-14 16:51:29 +01:00
committed by GitHub
parent 494b830240
commit 10849d5003
3 changed files with 22 additions and 2 deletions
+1 -1
View File
@@ -520,7 +520,7 @@ fn integer_args(a: &[Arg]) -> R<Vec<u64>> {
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()
}
+14
View File
@@ -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);
+7 -1
View File
@@ -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 |