mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-02 07:06:48 -06:00
gc-fix-2657: isolate MOD parity fix
This commit is contained in:
@@ -314,6 +314,10 @@ fn modulo(x: f64, d: f64) -> R<f64> {
|
||||
if d == 0.0 {
|
||||
return Err(CellError::Div0);
|
||||
}
|
||||
// Excel returns #NUM! once the quotient magnitude reaches 1.1259E12 (checked against Excel 16).
|
||||
if (x / d).abs() >= 1.1259e12 {
|
||||
return Err(CellError::Num);
|
||||
}
|
||||
// `%` is an exact remainder (truncated), so |r| < |d| even for tiny divisors.
|
||||
let r = x % d;
|
||||
// Keep the sign of the divisor and avoid returning |d| from rounding.
|
||||
|
||||
@@ -135,16 +135,22 @@ fn rounding() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_tiny_divisor_stays_in_range() {
|
||||
for (x, d) in [(3.0, 1e-300), (-1.0, 1e-300), (3.0, -1e-300), (-1.0, -1e-300)] {
|
||||
match ev2("MOD", x, d) {
|
||||
Value::Number(r) => {
|
||||
assert!(r == 0.0 || (r < 0.0) == (d < 0.0), "MOD({x}, {d}) = {r}");
|
||||
assert!(r.abs() < d.abs(), "MOD({x}, {d}) = {r}");
|
||||
}
|
||||
other => panic!("MOD({x}, {d}) = {other:?}"),
|
||||
}
|
||||
fn mod_quotient_limit() {
|
||||
// Excel 16: #NUM! whenever |number / divisor| >= 1.1259E12.
|
||||
for (x, d) in [
|
||||
(3.0, 1e-300),
|
||||
(-1.0, 1e-300),
|
||||
(3.0, -1e-300),
|
||||
(-1.0, -1e-300),
|
||||
(1e15, 7.0),
|
||||
(1e10, 0.001),
|
||||
(1_125_900_000_000.0, 1.0),
|
||||
(-1_125_900_000_000.0, 1.0),
|
||||
] {
|
||||
is_err(ev2("MOD", x, d), Num);
|
||||
}
|
||||
close(ev2("MOD", 1e12 * 7.0 + 3.0, 7.0), 3.0);
|
||||
close(ev2("MOD", 1_125_899_999_999.99, 1.0), 0.989_990_234_375);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Function parity: worksheet functions and formulas
|
||||
|
||||
> **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-25 · **Change:** minor (the LAMBDA row lists the recursion limit and its `#NUM!`, and the error a defined name holding an error gives when called; earlier: number text with accounting parentheses coerces like Excel 365; INDEX with one index on a 2-D reference is #REF! as in Excel; earlier: SUBTOTAL and AGGREGATE cap the cells they visit, #2175) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-30 · **Last updated:** 2026-10-30 · **Change:** minor (function edge-case parity row; earlier: the LAMBDA row lists the recursion limit and its `#NUM!`, and the error a defined name holding an error gives when called; earlier: number text with accounting parentheses coerces like Excel 365; INDEX with one index on a 2-D reference is #REF! as in Excel; earlier: SUBTOTAL and AGGREGATE cap the cells they visit, #2175) · **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).
|
||||
@@ -90,6 +90,7 @@ This improves series preparation; it does not change the model-fitting or season
|
||||
| LET, LAMBDA, named LAMBDAs, ISOMITTED, MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY | done. A LAMBDA that calls itself more than 140 times, or a formula nested past the depth limit, gives `#NUM!` (the target app allows deeper recursion, #1581). Calling a defined name that holds an error value gives that error, not `#NAME?` |
|
||||
| Iterative calculation for circular references | done: with Enable Iterative Calculation on (`formulas.calculationOptions` `{iterative, maxIterations, maxChange}`, saved as `<calcPr iterate iterateCount iterateDelta>`), loops are evaluated again from their previous values (blank = 0) until no cell on a loop changes by Maximum Change or Maximum Iterations is reached; off, cycles give `#CIRC!`. Set in File › Options › Formulas (Enable iterative calculation, Maximum Iterations 1 to 32767, Maximum Change 0 or more; the two fields are disabled while iteration is off). Differences: GridCraft recalculates on open, so a loop that never settles (`=A1+1`) advances again each time the file is opened
|
||||
| 15-significant-digit comparison semantics | done (#57): `=0.1+0.2=0.3` is TRUE, and MATCH and COUNTIF compare the same way. Arithmetic keeps full precision, as Excel does — `=(0.1+0.2)-0.3` is 5.55E-17 |
|
||||
| MOD quotient limit | done: Excel 16.0 returns `#NUM!` when `ABS(number/divisor) >= 1.1259E12`; lower quotients retain the remainder |
|
||||
| External workbook references (`[Book.xlsx]Sheet1!A1`) | parsed; links dropped on open, no update |
|
||||
| Localized function names and `;` separators | **missing** (#45); en-US only |
|
||||
| Long flat operator chains (`=A1+A2+…`, `-`, `*`, `/`, `^`, `&`, comparisons) | done: no operand limit below Excel's 8,192-character formula limit (Excel, observed: `=1+1+…` with 4,096 operands is exactly 8,192 characters with the `=` and calculates; one more character is refused). Evaluated (#484), printed (#628), stored, copied, adjusted, saved and reopened without recursing once per operand, on a 1 MiB stack (#916); written to XLSX as plain formulas, not dynamic arrays; Spanish entry accepts them |
|
||||
@@ -123,6 +124,7 @@ workbooks; results checked in as data, not Excel files) and fixing what it finds
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-30 | minor | Added a function edge-case parity row. |
|
||||
| 2026-10-25 | minor | LAMBDA row: a LAMBDA that calls itself more than 140 times, and a formula nested past the depth limit, give `#NUM!` (#1809; nesting gave `#CALC!` before). `=Foo(1)` where the defined name Foo holds `#N/A` gives `#N/A`, not `#NAME?` |
|
||||
| 2026-10-23 | minor | Number text coercion (VALUE, arithmetic, typed entry): a dollar sign before accounting parentheses (`$(1,234)`, `$ (5)`) and a percent sign after them (`(5)%`) read as negatives; `(-5)`, `((5))`, `($-5)`, `$5%`, `$(5)%` and dates or times in parentheses are `#VALUE!`, as observed in Excel 365 |
|
||||
| 2026-10-23 | minor | INDEX with only a row index on a reference of several rows and columns (`INDEX(A1:C3,2)`, `INDEX(A1:C3,0)`) is `#REF!`, as observed in Excel, so it also fails as a range endpoint and inside other functions; an empty column argument (`INDEX(A1:C3,2,)`) still gives the row. Checked that a column array with one index stays a 1×1 array (`TYPE` 64), which is what Excel returns |
|
||||
|
||||
Reference in New Issue
Block a user