Merge PR #1281: N() converts a computed array element by element, so it works inside SUMPRODUCT

crates/calc/src/tests.rs: kept both tests (union).
This commit is contained in:
aiongg
2026-10-22 02:34:42 +00:00
2 changed files with 31 additions and 2 deletions
+17
View File
@@ -1317,3 +1317,20 @@ fn median_large_values_recalculate_dependents() {
assert!((t.num(cell) + 1.3).abs() < 1e-14);
}
}
#[test]
fn n_lifts_over_a_computed_array_but_reads_the_first_cell_of_a_range() {
let mut t = T::new();
t.set("B1", "0");
t.set("B2", "23850");
t.set("D2", "10%");
t.set("D3", "12%");
t.set("F1", "50000");
t.set("H1", "=SUMPRODUCT(D2:D3-D1:D2,F1-B1:B2,N(F1>B1:B2))");
assert!((t.num("H1") - 5523.0).abs() < 1e-9, "{:?}", t.get("H1"));
t.set("H2", "=SUMPRODUCT(N(B1:B2>0))");
assert_eq!(t.num("H2"), 1.0);
// A spill of N(D2:D3) would also show 10% in H3; summing it tells the two apart.
t.set("H3", "=SUM(N(D2:D3))");
assert!((t.num("H3") - 0.1).abs() < 1e-12, "{:?}", t.get("H3"));
}
+14 -2
View File
@@ -89,7 +89,17 @@ fn na(_a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
}
fn n_fn(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
match first(a) {
// N(range) reads the range's first cell, but a computed array (N(A1:A3>0), N({...})) is
// converted element by element, so N can stand in for `--` inside SUMPRODUCT.
if let Some(Arg { value: Value::Array(arr), from_ref: false }) = a.first() {
let data = arr.data.iter().map(|v| n_of(v.clone()).unwrap_or_else(Value::Error)).collect();
return Array::new(arr.rows, arr.cols, data).map(Value::from).ok_or(CellError::Calc);
}
n_of(first(a))
}
fn n_of(v: Value) -> R<Value> {
match v {
Value::Number(n) => Ok(Value::Number(n)),
Value::Bool(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })),
Value::Error(e) => Err(e),
@@ -536,7 +546,9 @@ mod tests {
close(ev("N", vec![b(true)]), 1.0);
close(ev("N", vec![t("7")]), 0.0);
is_err(ev("N", vec![e(CellError::Num)]), CellError::Num);
close(ev("N", vec![av(row(&[3.0, 4.0]))]), 3.0);
close(ev("N", vec![rf(row(&[3.0, 4.0]))]), 3.0);
let lifted = ev("N", vec![av(arr(vec![vec![Value::Bool(true), tv("x")], vec![Value::Error(CellError::Div0), nv(4.0)]]))]);
assert_eq!(rows_of(&lifted), vec![vec![nv(1.0), nv(0.0)], vec![Value::Error(CellError::Div0), nv(4.0)]]);
is_text(ev("INFO", vec![t("RECALC")]), "Automatic");
is_err(ev("INFO", vec![t("bogus")]), CellError::Value);
}