use super::*;
fn eval1(source: &str) -> ResultData {
let sheet = Sheet::new(SheetInit::default());
sheet.eval(source, None).unwrap().0
}
fn assert_float_close(result: &ResultData, expected: f64, tol: f64) {
match result {
ResultData::Float(f) => assert!((f - expected).abs() < tol, "expected {expected}, got {f}"),
ResultData::Integer(i) => assert!(
(*i as f64 - expected).abs() < tol,
"expected {expected}, got {i}"
),
other => panic!("expected numeric result close to {expected}, got {other:?}"),
}
}
#[test]
fn test_statistical_summary_functions() {
let grid = [
["10", "20", "30", "40", "50"],
[
"=AVEDEV(A1:E1)",
"=AVERAGEA(A1:E1)",
"=MEDIAN(A1:E1)",
"=GEOMEAN(A1:E1)",
"=HARMEAN(A1:E1)",
],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r1 = sheet.get_result_data(&CellRef::new(1, 0));
assert!(matches!(r1, ResultData::Float(v) if (v - 12.0).abs() < 1e-6));
let r2 = sheet.get_result_data(&CellRef::new(1, 1));
assert!(matches!(r2, ResultData::Float(v) if (v - 30.0).abs() < 1e-6));
let r3 = sheet.get_result_data(&CellRef::new(1, 2));
assert!(matches!(r3, ResultData::Float(v) if (v - 30.0).abs() < 1e-6));
let r4 = sheet.get_result_data(&CellRef::new(1, 3));
assert!(matches!(r4, ResultData::Float(v) if (v - 26.0517108).abs() < 1e-4));
let r5 = sheet.get_result_data(&CellRef::new(1, 4));
assert!(matches!(r5, ResultData::Float(v) if (v - 21.89781).abs() < 1e-4));
}
#[test]
fn test_variance_and_stdev() {
let grid = [
["10", "20", "30", "40", "50"],
[
"=VAR.S(A1:E1)",
"=VAR.P(A1:E1)",
"=STDEV.S(A1:E1)",
"=STDEV.P(A1:E1)",
"=DEVSQ(A1:E1)",
],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r1 = sheet.get_result_data(&CellRef::new(1, 0));
assert!(matches!(r1, ResultData::Float(v) if (v - 250.0).abs() < 1e-6));
let r2 = sheet.get_result_data(&CellRef::new(1, 1));
assert!(matches!(r2, ResultData::Float(v) if (v - 200.0).abs() < 1e-6));
let r3 = sheet.get_result_data(&CellRef::new(1, 2));
assert!(matches!(r3, ResultData::Float(v) if (v - 250.0_f64.sqrt()).abs() < 1e-6));
let r4 = sheet.get_result_data(&CellRef::new(1, 3));
assert!(matches!(r4, ResultData::Float(v) if (v - 200.0_f64.sqrt()).abs() < 1e-6));
let r5 = sheet.get_result_data(&CellRef::new(1, 4));
assert!(matches!(r5, ResultData::Float(v) if (v - 1000.0).abs() < 1e-6));
}
#[test]
fn test_criteria_and_quantiles() {
let grid = [
["5", "10", "15", "20", "25"],
[
"=AVERAGEIF(A1:E1, \">10\")",
"=LARGE(A1:E1, 2)",
"=SMALL(A1:E1, 1)",
"=PERCENTILE.INC(A1:E1, 0.5)",
"=QUARTILE.INC(A1:E1, 3)",
],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r1 = sheet.get_result_data(&CellRef::new(1, 0));
assert!(matches!(r1, ResultData::Float(v) if (v - 20.0).abs() < 1e-6));
let r2 = sheet.get_result_data(&CellRef::new(1, 1));
assert!(matches!(r2, ResultData::Float(v) if (v - 20.0).abs() < 1e-6));
let r3 = sheet.get_result_data(&CellRef::new(1, 2));
assert!(matches!(r3, ResultData::Float(v) if (v - 5.0).abs() < 1e-6));
let r4 = sheet.get_result_data(&CellRef::new(1, 3));
assert!(matches!(r4, ResultData::Float(v) if (v - 15.0).abs() < 1e-6));
let r5 = sheet.get_result_data(&CellRef::new(1, 4));
assert!(matches!(r5, ResultData::Float(v) if (v - 20.0).abs() < 1e-6));
}
#[test]
fn test_distributions_and_special_math() {
let grid = [[
"=NORM.S.DIST(0, TRUE)",
"=NORM.S.INV(0.5)",
"=EXPON.DIST(1, 1, TRUE)",
"=POISSON.DIST(2, 2, FALSE)",
"=STANDARDIZE(15, 10, 2.5)",
]];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r1 = sheet.get_result_data(&CellRef::new(0, 0));
assert!(matches!(r1, ResultData::Float(v) if (v - 0.5).abs() < 1e-6));
let r2 = sheet.get_result_data(&CellRef::new(0, 1));
assert!(matches!(r2, ResultData::Float(v) if v.abs() < 1e-6));
let r3 = sheet.get_result_data(&CellRef::new(0, 2));
assert!(matches!(r3, ResultData::Float(v) if (v - (1.0 - (-1.0_f64).exp())).abs() < 1e-6));
let r4 = sheet.get_result_data(&CellRef::new(0, 3));
assert!(matches!(r4, ResultData::Float(v) if (v - 0.270670566).abs() < 1e-5));
let r5 = sheet.get_result_data(&CellRef::new(0, 4));
assert!(matches!(r5, ResultData::Float(v) if (v - 2.0).abs() < 1e-6));
}
#[test]
fn test_probability_distributions_match_known_reference_values() {
assert_float_close(&eval1("=NORM.DIST(1,0,1,TRUE)"), 0.8413447461, 1e-6);
assert_float_close(&eval1("=PHI(0)"), 0.3989422804, 1e-8);
assert_float_close(&eval1("=GAUSS(1.96)"), 0.4750021049, 1e-6);
assert_float_close(&eval1("=GAMMA(5)"), 24.0, 1e-9);
assert_float_close(&eval1("=GAMMALN(5)"), 3.1780538303, 1e-8);
assert_float_close(&eval1("=GAMMA.DIST(2,3,1,FALSE)"), 0.2706705665, 1e-8);
assert_float_close(&eval1("=GAMMA.DIST(2,3,1,TRUE)"), 0.3233235838, 1e-8);
assert_float_close(&eval1("=BETA.DIST(0.5,2,2,FALSE,0,1)"), 1.5, 1e-8);
assert_float_close(&eval1("=BETA.DIST(0.5,2,2,TRUE,0,1)"), 0.5, 1e-8);
assert_float_close(&eval1("=BINOM.DIST(3,10,0.5,FALSE)"), 0.1171875, 1e-9);
assert_float_close(&eval1("=BINOM.DIST(3,10,0.5,TRUE)"), 0.171875, 1e-9);
assert_float_close(&eval1("=BINOM.DIST.RANGE(10,0.5,0,3)"), 0.171875, 1e-9);
assert_float_close(&eval1("=BINOM.INV(10,0.5,0.5)"), 5.0, 1e-9);
assert_float_close(&eval1("=POISSON.DIST(3,2,FALSE)"), 0.1804470443, 1e-8);
assert_float_close(&eval1("=POISSON.DIST(3,2,TRUE)"), 0.8571234605, 1e-8);
assert_float_close(&eval1("=WEIBULL.DIST(1,1,1,TRUE)"), 0.6321205588, 1e-8);
assert_float_close(&eval1("=HYPGEOM.DIST(1,2,4,10,FALSE)"), 0.5333333333, 1e-8);
assert_float_close(&eval1("=HYPGEOM.DIST(1,2,4,10,TRUE)"), 0.8666666667, 1e-8);
assert_float_close(&eval1("=NEGBINOM.DIST(2,3,0.5,FALSE)"), 0.1875, 1e-9);
assert_float_close(&eval1("=LOGNORM.DIST(1,0,1,TRUE)"), 0.5, 1e-9);
assert_float_close(&eval1("=CHISQ.DIST(3.841458821,1,TRUE)"), 0.95, 1e-6);
assert_float_close(&eval1("=CHISQ.DIST.RT(3.841458821,1)"), 0.05, 1e-6);
assert_float_close(&eval1("=T.DIST(1.5,10,TRUE)"), 0.9177463367, 1e-6);
assert_float_close(&eval1("=F.DIST(2,5,10,TRUE)"), 0.8358050491, 1e-6);
}
#[test]
fn test_chisq_test_matches_independent_numeric_reference() {
let grid = [["10", "20", "30"], ["15", "15", "30"]];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let (result, _) = sheet.eval("=CHISQ.TEST(A1:C1,A2:C2)", None).unwrap();
assert_float_close(&result, 0.1888756028, 1e-6);
}
#[test]
fn test_rank_percentile_and_bivariate_functions_match_hand_computed_values() {
let grid = [["10", "20", "20", "30", "40"]];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let (rank_eq_desc, _) = sheet.eval("=RANK.EQ(20,A1:E1,0)", None).unwrap();
assert_float_close(&rank_eq_desc, 3.0, 1e-9);
let (rank_eq_asc, _) = sheet.eval("=RANK.EQ(20,A1:E1,1)", None).unwrap();
assert_float_close(&rank_eq_asc, 2.0, 1e-9);
let (rank_avg, _) = sheet.eval("=RANK.AVG(20,A1:E1,0)", None).unwrap();
assert_float_close(&rank_avg, 3.5, 1e-9);
let (pct_exc, _) = sheet.eval("=PERCENTILE.EXC(A1:E1,0.5)", None).unwrap();
assert_float_close(&pct_exc, 20.0, 1e-6);
let (quart_exc, _) = sheet.eval("=QUARTILE.EXC(A1:E1,1)", None).unwrap();
assert_float_close(&quart_exc, 15.0, 1e-6);
let (prank_inc, _) = sheet.eval("=PERCENTRANK.INC(A1:E1,25)", None).unwrap();
assert_float_close(&prank_inc, 0.625, 1e-6);
let (prank_exc, _) = sheet.eval("=PERCENTRANK.EXC(A1:E1,25)", None).unwrap();
assert_float_close(&prank_exc, 0.583, 1e-6);
let bivar_grid = [["1", "2", "3", "4", "5"], ["2", "4", "5", "4", "5"]];
let mut bivar_sheet = create_sheet(&bivar_grid);
bivar_sheet.commit(None).unwrap();
let (cov_p, _) = bivar_sheet
.eval("=COVARIANCE.P(A1:E1,A2:E2)", None)
.unwrap();
assert_float_close(&cov_p, 1.2, 1e-9);
let (cov_s, _) = bivar_sheet
.eval("=COVARIANCE.S(A1:E1,A2:E2)", None)
.unwrap();
assert_float_close(&cov_s, 1.5, 1e-9);
let (steyx, _) = bivar_sheet.eval("=STEYX(A2:E2,A1:E1)", None).unwrap();
assert_float_close(&steyx, 0.8944271910, 1e-8);
let trim_grid = [["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]];
let mut trim_sheet = create_sheet(&trim_grid);
trim_sheet.commit(None).unwrap();
let (trimmean, _) = trim_sheet.eval("=TRIMMEAN(A1:J1,0.2)", None).unwrap();
assert_float_close(&trimmean, 5.5, 1e-9);
let moment_grid = [["2", "4", "4", "4", "5", "5", "7", "9"]];
let mut moment_sheet = create_sheet(&moment_grid);
moment_sheet.commit(None).unwrap();
let (skew, _) = moment_sheet.eval("=SKEW(A1:H1)", None).unwrap();
assert_float_close(&skew, 0.8184875534, 1e-8);
let (skew_p, _) = moment_sheet.eval("=SKEW.P(A1:H1)", None).unwrap();
assert_float_close(&skew_p, 0.65625, 1e-8);
let (kurt, _) = moment_sheet.eval("=KURT(A1:H1)", None).unwrap();
assert_float_close(&kurt, 0.940625, 1e-6);
}
#[test]
fn test_mode_sngl_and_mode_mult() {
let grid = [["1", "2", "2", "3", "3", "4"]];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let (mode_sngl, _) = sheet.eval("=MODE.SNGL(A1:F1)", None).unwrap();
assert_float_close(&mode_sngl, 2.0, 1e-9);
let (mode_mult, _) = sheet.eval("=MODE.MULT(A1:F1)", None).unwrap();
match mode_mult {
ResultData::List(items) => {
let vals: Vec<f64> = items
.iter()
.map(|v| match v {
ResultData::Float(f) => *f,
ResultData::Integer(i) => *i as f64,
other => panic!("expected numeric mode, got {other:?}"),
})
.collect();
assert_eq!(vals, vec![2.0, 3.0]);
}
other => panic!("expected a List of modes, got {other:?}"),
}
}
#[test]
fn test_fuzz_mode_mult_orders_ties_by_first_appearance_not_value() {
let grid = [
["34", "0"],
["0", "479.283"],
["34", "-26"],
["\"b\"", "-50"],
["10", "10"],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let (first_mode, _) = sheet.eval("=INDEX(MODE.MULT(A1:B5), 1)", None).unwrap();
assert_float_close(&first_mode, 34.0, 1e-9);
}
#[test]
fn test_t_test_uses_each_samples_own_length_for_its_mean() {
let grid = [
["10", "12", "14", "16", "18", ""],
["20", "22", "24", "", "", ""],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let (result, _) = sheet.eval("=T.TEST(A1:E1,A2:C2,2,2)", None).unwrap();
assert_float_close(&result, 0.008237263171, 1e-6);
}
#[test]
fn test_f_test_and_confidence_intervals_match_independent_reference() {
let grid = [
["10", "20", "30", "40", "50"],
["5", "10", "15", "20", "25"],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let (f_test, _) = sheet.eval("=F.TEST(A1:E1,A2:E2)", None).unwrap();
assert_float_close(&f_test, 0.208, 1e-4);
assert_float_close(&eval1("=CONFIDENCE.NORM(0.05,10,25)"), 3.919927969, 1e-5);
assert_float_close(&eval1("=CONFIDENCE.T(0.05,10,25)"), 4.127797137, 1e-6);
}
#[test]
fn test_inverse_distributions_round_trip_through_their_forward_dist() {
assert_float_close(&eval1("=NORM.S.DIST(NORM.S.INV(0.9),TRUE)"), 0.9, 1e-6);
assert_float_close(&eval1("=NORM.DIST(NORM.INV(0.3,5,2),5,2,TRUE)"), 0.3, 1e-6);
assert_float_close(
&eval1("=GAMMA.DIST(GAMMA.INV(0.4,3,2),3,2,TRUE)"),
0.4,
1e-5,
);
assert_float_close(
&eval1("=BETA.DIST(BETA.INV(0.4,2,3,0,1),2,3,TRUE,0,1)"),
0.4,
1e-5,
);
assert_float_close(&eval1("=CHISQ.DIST(CHISQ.INV(0.9,5),5,TRUE)"), 0.9, 1e-5);
assert_float_close(&eval1("=CHISQ.DIST.RT(CHISQ.INV.RT(0.1,5),5)"), 0.1, 1e-5);
assert_float_close(&eval1("=T.DIST(T.INV(0.8,10),10,TRUE)"), 0.8, 1e-5);
assert_float_close(
&eval1("=LOGNORM.DIST(LOGNORM.INV(0.4,0,1),0,1,TRUE)"),
0.4,
1e-5,
);
}
#[test]
fn test_regression_and_correlation() {
let grid = [
["1", "2", "3", "4", "5"],
["2", "4", "6", "8", "10"],
[
"=CORREL(A1:E1, A2:E2)",
"=SLOPE(A2:E2, A1:E1)",
"=INTERCEPT(A2:E2, A1:E1)",
"=FORECAST(6, A2:E2, A1:E1)",
"=RSQ(A2:E2, A1:E1)",
],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r1 = sheet.get_result_data(&CellRef::new(2, 0));
assert!(matches!(r1, ResultData::Float(v) if (v - 1.0).abs() < 1e-6));
let r2 = sheet.get_result_data(&CellRef::new(2, 1));
assert!(matches!(r2, ResultData::Float(v) if (v - 2.0).abs() < 1e-6));
let r3 = sheet.get_result_data(&CellRef::new(2, 2));
assert!(matches!(r3, ResultData::Float(v) if v.abs() < 1e-6));
let r4 = sheet.get_result_data(&CellRef::new(2, 3));
assert!(matches!(r4, ResultData::Float(v) if (v - 12.0).abs() < 1e-6));
let r5 = sheet.get_result_data(&CellRef::new(2, 4));
assert!(matches!(r5, ResultData::Float(v) if (v - 1.0).abs() < 1e-6));
}
#[test]
fn test_inv_normal_cdf_matches_real_excel_to_near_double_precision() {
assert_float_close(&eval1("=NORM.S.INV(0.975)"), 1.959963984540054, 1e-9);
}
#[test]
fn test_tdist_honors_tails_argument() {
let one_tailed = eval1("=TDIST(2, 10, 1)");
let rt = eval1("=T.DIST.RT(2, 10)");
assert_float_close(
&one_tailed,
match rt {
ResultData::Float(v) => v,
other => panic!("expected float, got {other:?}"),
},
1e-9,
);
let two_tailed = eval1("=TDIST(2, 10, 2)");
let one_val = match one_tailed {
ResultData::Float(v) => v,
other => panic!("expected float, got {other:?}"),
};
assert_float_close(&two_tailed, one_val * 2.0, 1e-9);
}
#[test]
fn test_percentrank_truncates_to_significance_not_rounds() {
let grid = [
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
[
"=PERCENTRANK(A1:J1, 1.5)",
"",
"",
"",
"",
"",
"",
"",
"",
"",
],
];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r = sheet.get_result_data(&CellRef::new(1, 0));
assert!(
matches!(r, ResultData::Float(v) if (v - 0.055).abs() < 1e-9),
"{r:?}"
);
}
#[test]
fn test_hypgeomdist_legacy_is_pmf_only_and_out_of_support_is_zero() {
let grid = [[
"=HYPGEOMDIST(1, 4, 19, 45)",
"=HYPGEOM.DIST(1, 4, 19, 45, FALSE)",
"=HYPGEOM.DIST(4, 25, 13, 25, FALSE)",
]];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let r1 = sheet.get_result_data(&CellRef::new(0, 0));
let r2 = sheet.get_result_data(&CellRef::new(0, 1));
match (&r1, &r2) {
(ResultData::Float(a), ResultData::Float(b)) => {
assert!(
(a - b).abs() < 1e-9,
"HYPGEOMDIST(1,4,19,45)={a} should equal the non-cumulative HYPGEOM.DIST={b}"
);
}
_ => panic!("expected floats, got {r1:?} / {r2:?}"),
}
let r3 = sheet.get_result_data(&CellRef::new(0, 2));
assert!(
matches!(r3, ResultData::Float(v) if v.abs() < 1e-9),
"{r3:?}"
);
}
#[test]
fn test_inverse_beta_and_f_distributions_converge_to_excel_values() {
for (f, expected) in [
("=BETAINV(0.945, 9.128, 5.585)", 0.8079143872863086),
("=_xlfn.BETA.INV(0.077, 4.347, 1.607)", 0.45886530331058883),
("=_xlfn.F.INV(0.119, 8, 1)", 0.3281233164680227),
("=_xlfn.F.INV(0.883, 1, 8)", 3.0866529196587305),
("=_xlfn.F.INV.RT(0.942, 18, 3)", 0.33370421499396513),
("=_xlfn.F.INV.RT(0.876, 17, 6)", 0.5030517141697566),
("=FINV(0.709, 1, 11)", 0.14670778600563464),
("=FINV(0.868, 10, 9)", 0.4767239715231606),
("=_xlfn.F.INV.RT(0.38, 17, 1)", 3.9202240523326743),
] {
let got = eval1(f);
match got {
ResultData::Float(v) => {
let rel = (v - expected).abs() / expected.abs().max(1e-300);
assert!(rel < 1e-9, "{f}: got {v}, want {expected} (rel {rel:e})");
}
other => panic!("{f}: got {other:?}, want {expected}"),
}
}
}
#[test]
fn test_chitest_single_category_is_not_available() {
let grid = [["5", "7", "=CHITEST(A1:A1, B1:B1)"]];
let mut sheet = create_sheet(&grid);
sheet.commit(None).unwrap();
let got = sheet.get_result_data(&CellRef::new(0, 2));
assert!(
matches!(got, ResultData::Error(ref e) if e == "#N/A"),
"{got:?}"
);
assert!(crate::core::stats::chisq_test(&[10.0, 20.0], &[10.0, 20.0], 2).is_ok());
}
fn assert_err(source: &str, expected: &str) {
match eval1(source) {
ResultData::Error(e) => assert_eq!(e, expected, "for {source}"),
other => panic!("expected {expected} for {source}, got {other:?}"),
}
}
#[test]
fn test_direct_numeric_text_is_coerced_by_stat_family() {
assert_float_close(&eval1("=SUM(\"12\", 3, 4, 5)"), 24.0, 1e-9);
assert_float_close(&eval1("=AVERAGE(\"12\", 3, 4, 5)"), 6.0, 1e-9);
assert_float_close(&eval1("=DEVSQ(\"12\", 3, 4, 5)"), 50.0, 1e-9);
assert_float_close(&eval1("=STDEV(\"12\", 3, 4, 5)"), 4.08248290463863, 1e-12);
assert_float_close(&eval1("=VAR(\"12\", 3, 4, 5)"), 16.666666666666668, 1e-12);
assert_float_close(&eval1("=MEDIAN(\"12\", 3, 4, 5)"), 4.5, 1e-9);
assert_float_close(&eval1("=SUMSQ(\"12\", 3, 4, 5)"), 194.0, 1e-9);
assert_float_close(
&eval1("=GEOMEAN(\"12\", 3, 4, 5)"),
5.180040128222703,
1e-12,
);
assert_float_close(&eval1("=AVEDEV(\"12\", 3, 4, 5)"), 3.0, 1e-9);
assert_float_close(&eval1("=SKEW(\"12\", 3, 4, 5)"), 1.7636326148038874, 1e-12);
assert_float_close(&eval1("=KURT(\"12\", 3, 4, 5)"), 3.2279999999999944, 1e-12);
}
#[test]
fn test_direct_uncoercible_text_is_value_error_in_stat_family() {
for f in [
"SUM", "AVERAGE", "DEVSQ", "STDEV", "VAR", "MEDIAN", "MAX", "MIN", "PRODUCT", "SUMSQ",
"GEOMEAN", "AVEDEV", "SKEW", "KURT",
] {
assert_err(&format!("={f}(\"abc\", 3, 4, 5)"), "#VALUE!");
}
}
#[test]
fn test_count_never_errors_on_text() {
assert_float_close(&eval1("=COUNT(\"12\", 3, 4, 5)"), 4.0, 1e-9);
assert_float_close(&eval1("=COUNT(\"abc\", 3, 4, 5)"), 3.0, 1e-9);
}
#[test]
fn test_averagea_family_direct_vs_referenced_text() {
assert_float_close(&eval1("=AVERAGEA(\"12\", 3)"), 7.5, 1e-9);
assert_float_close(&eval1("=AVERAGEA(TRUE, 3)"), 2.0, 1e-9);
assert_float_close(&eval1("=MAXA(\"12\", 3)"), 12.0, 1e-9);
assert_err("=AVERAGEA(\"abc\", 3)", "#VALUE!");
let mut sheet = create_sheet(&[["=\"12\"", "=AVERAGEA(A1, 3)"]]);
sheet.commit(None).unwrap();
assert_float_close(&sheet.get_result_data(&CellRef::new(0, 1)), 1.5, 1e-9);
}
#[test]
fn test_erf_family_coerces_numeric_text_but_rejects_booleans() {
assert_float_close(&eval1("=ERF(\"1\")"), 0.8427007929497149, 1e-15);
assert_float_close(&eval1("=ERF(\" 1 \")"), 0.8427007929497149, 1e-15);
assert_float_close(&eval1("=ERF(\"-39\")"), -1.0, 1e-15);
assert_float_close(&eval1("=ERFC(\"1\")"), 0.15729920705028513, 1e-15);
assert_err("=ERF(TRUE)", "#VALUE!");
assert_err("=ERF(FALSE)", "#VALUE!");
assert_err("=ERFC(TRUE)", "#VALUE!");
assert_err("=ERF(\"abc\")", "#VALUE!");
}
#[test]
fn test_chitest_rejects_only_a_negative_total_not_negative_expected_values() {
let mut sheet = create_sheet(&[
["1", "2", "3", "=CHITEST(A1:C1, A2:C2)"],
["5", "-4", "3", "=CHITEST(A3:C3, A4:C4)"],
["-478.8", "352.51", "8.5", "=CHITEST(A1:C1, A5:C5)"],
["38", "8.5", "-75", ""],
["5", "0", "3", ""],
]);
sheet.commit(None).unwrap();
match sheet.get_result_data(&CellRef::new(0, 3)) {
ResultData::Error(e) => assert_eq!(e, "#NUM!", "negative statistic is #NUM!"),
other => panic!("expected #NUM!, got {other:?}"),
}
assert_float_close(&sheet.get_result_data(&CellRef::new(1, 3)), 0.0, 1e-12);
match sheet.get_result_data(&CellRef::new(2, 3)) {
ResultData::Error(e) => assert_eq!(e, "#DIV/0!", "zero expected is #DIV/0!"),
other => panic!("expected #DIV/0!, got {other:?}"),
}
}
#[test]
fn test_normal_cdf_keeps_its_left_tail() {
assert_float_close(
&eval1("=NORM.S.DIST(-11, TRUE)"),
1.9106595744986622e-28,
1e-40,
);
assert_float_close(
&eval1("=NORM.S.DIST(-30, TRUE)"),
4.9067139271479094e-198,
1e-210,
);
assert_float_close(&eval1("=SIGN(NORM.S.DIST(-11, TRUE))"), 1.0, 1e-12);
assert_float_close(&eval1("=NORM.S.DIST(0, TRUE)"), 0.5, 1e-15);
assert_float_close(
&eval1("=NORM.S.DIST(1.96, TRUE)"),
0.9750021048517795,
1e-15,
);
}
#[test]
fn test_paired_sums_error_only_when_a_range_holds_no_numbers() {
let mut sheet = create_sheet(&[
["-116.9395", "53", "=TRUE", "=\"I3w\"", "=TRUE", "-10"],
["1", "2", "=\"a\"", "=\"b\"", "=TRUE", "=TRUE"],
[
"=SUMX2PY2(A1:C1, D1:F1)",
"=SUMX2PY2(B1:C1, E1:F1)",
"=SUMX2PY2(A1:B1, E1:F1)",
"=SUMX2PY2(A2:B2, C2:D2)",
"=SUMX2PY2(A2:B2, E2:F2)",
"=SUMXMY2(A1:C1, D1:F1)",
],
]);
sheet.commit(None).unwrap();
assert_float_close(&sheet.get_result_data(&CellRef::new(2, 0)), 0.0, 1e-12);
assert_float_close(&sheet.get_result_data(&CellRef::new(2, 1)), 0.0, 1e-12);
assert_float_close(&sheet.get_result_data(&CellRef::new(2, 5)), 0.0, 1e-12);
assert_float_close(&sheet.get_result_data(&CellRef::new(2, 2)), 2909.0, 1e-9);
for col in [3, 4] {
match sheet.get_result_data(&CellRef::new(2, col)) {
ResultData::Error(e) => assert_eq!(e, "#DIV/0!", "column {col}"),
other => panic!("expected #DIV/0! in column {col}, got {other:?}"),
}
}
assert_err("=CORREL(A1:C1, D1:F1)", "#DIV/0!");
}
#[test]
#[allow(clippy::excessive_precision)]
fn test_f_right_tail_avoids_cancellation_and_fisherinv_saturates() {
assert_float_close(
&eval1("=F.DIST.RT(120.02429320013077, 2, 4)"),
2.6863796553017013481e-4,
1e-18,
);
assert_float_close(
&eval1("=F.DIST.RT(1000000, 2, 4)"),
3.9999840000480035e-12,
1e-24,
);
assert_float_close(&eval1("=F.DIST.RT(2, 3, 7)"), 0.20269364248665092207, 1e-15);
assert_float_close(&eval1("=F.DIST.RT(0.5, 10, 20)"), 0.8701603741696, 1e-12);
assert_float_close(&eval1("=F.DIST.RT(1, 5, 5)"), 0.4999999999999999, 1e-13);
assert_float_close(
&eval1("=FDIST(4.28, 3, 10)"),
0.034670525913903016847,
1e-16,
);
assert_float_close(&eval1("=F.DIST(2, 3, 7, TRUE)"), 0.7973063575133491, 1e-13);
assert_float_close(&eval1("=FISHERINV(1000)"), 1.0, 1e-15);
assert_float_close(&eval1("=FISHERINV(-1000)"), -1.0, 1e-15);
assert_float_close(&eval1("=FISHERINV(0.5)"), 0.46211715726000974, 1e-15);
}
#[test]
fn test_chitest_takes_degrees_of_freedom_from_the_raw_range_size() {
let mut sheet = create_sheet(&[
["-70", "=\"zz\"", "8.6291", "309.431", "3", "4"],
[
"=CHITEST(A1:B1, C1:D1)",
"=CHITEST(A1:C1, D1:F1)",
"=CHITEST(A1:A1, C1:C1)",
"",
"",
"",
],
]);
sheet.commit(None).unwrap();
assert_float_close(
&sheet.get_result_data(&CellRef::new(1, 0)),
7.81883827261815e-158,
1e-170,
);
assert_float_close(
&sheet.get_result_data(&CellRef::new(1, 1)),
6.38808797549415e-103,
1e-115,
);
match sheet.get_result_data(&CellRef::new(1, 2)) {
ResultData::Error(e) => assert_eq!(e, "#N/A"),
other => panic!("expected #N/A, got {other:?}"),
}
}
#[test]
fn test_a_lone_blank_cell_is_a_missing_operand() {
for src in [
"=SUMPRODUCT(Z50:Z50)",
"=SUMPRODUCT(-50, Z50)",
"=MULTINOMIAL(Z50)",
"=MULTINOMIAL(Z50, Z51)",
] {
match eval1(src) {
ResultData::Error(e) => assert_eq!(e, "#VALUE!", "for {src}"),
other => panic!("expected #VALUE! for {src}, got {other:?}"),
}
}
assert_float_close(&eval1("=SUMPRODUCT(Z50:Z51)"), 0.0, 1e-12);
assert_float_close(&eval1("=MULTINOMIAL(3, Z50)"), 1.0, 1e-12);
assert_float_close(&eval1("=MULTINOMIAL(Z50, 3)"), 1.0, 1e-12);
let mut sheet = create_sheet(&[["=\"abc\"", "=SUMPRODUCT(A1:A1)"]]);
sheet.commit(None).unwrap();
assert_float_close(&sheet.get_result_data(&CellRef::new(0, 1)), 0.0, 1e-12);
}
#[test]
fn test_gamma_keeps_full_precision_at_integer_arguments() {
assert_float_close(&eval1("=GAMMA(34)"), 8.68331761881189e36, 1e24);
assert_float_close(&eval1("=GAMMA(5)"), 24.0, 1e-12);
assert_float_close(&eval1("=GAMMA(11)"), 3628800.0, 1e-6);
assert_float_close(&eval1("=GAMMA(0.5)"), 1.7724538509055159, 1e-15);
assert_float_close(&eval1("=GAMMA(-1.5)"), 2.3632718012073544, 1e-14);
}
#[test]
fn test_chitest_with_no_surviving_pair_is_one_not_not_available() {
let mut sheet = create_sheet(&[
["=\"rBN\"", "-323.7702", "=CHITEST(A1:A3, B1:B3)"],
["", "=\"6-323.7702\"", "=ERF(CHITEST(A1:A3, B1:B3))"],
["27", "=\"B\"", ""],
]);
sheet.commit(None).unwrap();
assert_float_close(&sheet.get_result_data(&CellRef::new(0, 2)), 1.0, 1e-12);
assert_float_close(
&sheet.get_result_data(&CellRef::new(1, 2)),
0.8427007929497149,
1e-15,
);
let mut sheet = create_sheet(&[
["=\"cUVCpj\"", "=TRUE", "-60.63", "=\"XDWK\""],
["=\"mpSHAC\"", "", "-331.95", "=TRUE"],
["=CHITEST(A1:A2, B1:B2)", "=CHITEST(C1:C2, D1:D2)", "", ""],
]);
sheet.commit(None).unwrap();
for col in 0..2 {
match sheet.get_result_data(&CellRef::new(2, col)) {
ResultData::Error(e) => assert_eq!(e, "#DIV/0!", "column {col}"),
other => panic!("expected #DIV/0! in column {col}, got {other:?}"),
}
}
}
#[test]
fn test_mode_family_rejects_a_lone_blank_operand() {
for src in [
"=MODE(241.965, Z90)",
"=MODE.SNGL(241.965, Z90)",
"=MODE.MULT(241.965, Z90)",
"=MODE(241.965, 241.965, Z90)",
] {
match eval1(src) {
ResultData::Error(e) => assert_eq!(e, "#VALUE!", "for {src}"),
other => panic!("expected #VALUE! for {src}, got {other:?}"),
}
}
assert_float_close(&eval1("=MEDIAN(241.965, Z90)"), 241.965, 1e-12);
assert_float_close(&eval1("=MODE(241.965, 241.965)"), 241.965, 1e-12);
assert_float_close(&eval1("=MODE(1, 1, 2)"), 1.0, 1e-12);
assert_err("=MODE(1, 2)", "#N/A");
}
#[test]
fn test_shape_mismatch_outranks_the_no_numbers_rule() {
let mut sheet = create_sheet(&[
["1", "=\"x\"", "5", "=SUMXMY2(A1:A3, B1:B2)"],
["2", "=\"y\"", "6", "=SUMXMY2(A1:A3, C1:C2)"],
["3", "", "", "=CHITEST(A1:A1, A1:B5)"],
]);
sheet.commit(None).unwrap();
for row in 0..3 {
match sheet.get_result_data(&CellRef::new(row, 3)) {
ResultData::Error(e) => assert_eq!(e, "#N/A", "row {row}"),
other => panic!("expected #N/A in row {row}, got {other:?}"),
}
}
}
#[test]
fn test_gcd_family_coerces_numeric_text_but_not_booleans() {
assert_float_close(&eval1("=GCD(\"12\", 8)"), 4.0, 1e-12);
assert_float_close(&eval1("=LCM(\"4\", 6)"), 12.0, 1e-12);
assert_float_close(&eval1("=MULTINOMIAL(\"3\", 2)"), 10.0, 1e-9);
assert_float_close(&eval1("=MULTINOMIAL(RIGHT(\"a5\", 1), 2)"), 21.0, 1e-9);
for src in [
"=GCD(\"x\", 8)",
"=GCD(TRUE, 8)",
"=LCM(\"x\", 6)",
"=MULTINOMIAL(\"x\", 2)",
] {
assert_err(src, "#VALUE!");
}
}
#[test]
#[allow(clippy::excessive_precision)]
fn test_incomplete_beta_prefactor_accuracy() {
let mut sheet = create_sheet(&[
["127.95", "127.95"],
["5", "5"],
["28", "28"],
["-24.3108", "92"],
["0", "=FTEST(A1:A11, B1:B4)"],
["-40", ""],
["-45", ""],
["43", ""],
["96", ""],
["-66", ""],
["1", ""],
]);
sheet.commit(None).unwrap();
let got = match sheet.get_result_data(&CellRef::new(4, 1)) {
ResultData::Float(f) => f,
other => panic!("expected a number, got {other:?}"),
};
assert!(
(got - 0.94171633283387507291).abs() < 3e-16,
"FTEST expected 0.94171633283387507291, got {got}"
);
assert_eq!(format!("{got:.15}"), "0.941716332833875");
assert_float_close(
&eval1("=BETA.DIST(0.0378, 5, 50, TRUE)"),
0.052899172535742447319,
3e-17,
);
assert_float_close(&eval1("=F.DIST.RT(0.5, 10, 20)"), 0.8701603741696, 1e-15);
assert_float_close(&eval1("=BETA.DIST(0.5, 2, 3, TRUE)"), 0.6875, 1e-15);
assert_float_close(
&eval1("=T.DIST(1.5, 10, TRUE)"),
0.91774633677727990958,
1e-15,
);
}