use super::super::super::ast::Expr;
use super::super::super::coerce::to_logical;
use super::super::super::eval::{Engine, EvalContext};
use super::super::super::value::{ErrorKind, Value};
use super::super::array_common::poll_cancellation;
use super::super::moments::{NumericMoments, VarianceKind};
use super::super::special_functions::{
beta_density_exponent, beta_pair, regularized_incomplete_beta_lower,
regularized_incomplete_beta_upper,
};
use super::super::util::required_number;
use super::{finite, quantile_solver_error};
pub(super) fn f_distribution(
engine: &Engine<'_>,
context: EvalContext<'_>,
args: &[Expr],
) -> Value {
if args.len() != 4 {
return Value::Error(ErrorKind::Value);
}
let x = match nonnegative_x(engine, context, &args[0]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let df1 = match degrees_of_freedom(engine, context, &args[1]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let df2 = match degrees_of_freedom(engine, context, &args[2]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let cumulative = match to_logical(&engine.eval_scalar(context, &args[3])) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let on_iteration = || {
poll_cancellation(context)?;
engine.charge_function_iterations(context, 1)
};
if cumulative {
match lower_tail(x, df1, df2, on_iteration) {
Ok(value) => finite(value),
Err(kind) => Value::Error(kind),
}
} else {
match density(x, df1, df2) {
Ok(value) => finite(value),
Err(kind) => Value::Error(kind),
}
}
}
pub(super) fn f_distribution_rt(
engine: &Engine<'_>,
context: EvalContext<'_>,
args: &[Expr],
) -> Value {
if args.len() != 3 {
return Value::Error(ErrorKind::Value);
}
let x = match nonnegative_x(engine, context, &args[0]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let df1 = match degrees_of_freedom(engine, context, &args[1]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let df2 = match degrees_of_freedom(engine, context, &args[2]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
match upper_tail(x, df1, df2, || {
poll_cancellation(context)?;
engine.charge_function_iterations(context, 1)
}) {
Ok(value) => finite(value),
Err(kind) => Value::Error(kind),
}
}
pub(super) fn f_inverse(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr]) -> Value {
if args.len() != 3 {
return Value::Error(ErrorKind::Value);
}
let probability = match required_number(engine, context, &args[0]) {
Ok(value) if (0.0..1.0).contains(&value) => value,
Ok(_) => return Value::Error(ErrorKind::Num),
Err(kind) => return Value::Error(kind),
};
let df1 = match degrees_of_freedom(engine, context, &args[1]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let df2 = match degrees_of_freedom(engine, context, &args[2]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
if probability == 0.0 {
return finite(0.0);
}
match beta_pair(df1 / 2.0, df2 / 2.0, probability, || {
poll_cancellation(context)?;
engine.charge_function_iterations(context, 1)
}) {
Ok((z, w)) => finite(restore_f_coordinate(z, w, df1, df2)),
Err(kind) => Value::Error(quantile_solver_error(kind)),
}
}
pub(super) fn f_inverse_rt(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr]) -> Value {
if args.len() != 3 {
return Value::Error(ErrorKind::Value);
}
let probability = match required_number(engine, context, &args[0]) {
Ok(value) if value > 0.0 && value <= 1.0 => value,
Ok(_) => return Value::Error(ErrorKind::Num),
Err(kind) => return Value::Error(kind),
};
let df1 = match degrees_of_freedom(engine, context, &args[1]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
let df2 = match degrees_of_freedom(engine, context, &args[2]) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
if probability == 1.0 {
return finite(0.0);
}
match beta_pair(df2 / 2.0, df1 / 2.0, probability, || {
poll_cancellation(context)?;
engine.charge_function_iterations(context, 1)
}) {
Ok((w, z)) => finite(restore_f_coordinate(z, w, df1, df2)),
Err(kind) => Value::Error(quantile_solver_error(kind)),
}
}
pub(in crate::calculation::functions) fn f_test(
engine: &Engine<'_>,
context: EvalContext<'_>,
args: &[Expr],
) -> Value {
if args.len() != 2 {
return Value::Error(ErrorKind::Value);
}
let left = match sample_moments(engine, context, &args[0]) {
Ok(moments) => moments,
Err(kind) => return Value::Error(kind),
};
let right = match sample_moments(engine, context, &args[1]) {
Ok(moments) => moments,
Err(kind) => return Value::Error(kind),
};
let left_variance = match left.variance(VarianceKind::Sample) {
Ok(variance) => variance,
Err(kind) => return Value::Error(kind),
};
let right_variance = match right.variance(VarianceKind::Sample) {
Ok(variance) => variance,
Err(kind) => return Value::Error(kind),
};
if left_variance == 0.0 || right_variance == 0.0 {
return Value::Error(ErrorKind::Div0);
}
let (log_ratio, df1, df2) =
variance_ratio_log(left_variance, right_variance, left.count(), right.count());
match upper_tail_from_log_ratio(log_ratio, df1, df2, || {
poll_cancellation(context)?;
engine.charge_function_iterations(context, 1)
}) {
Ok(tail) => finite((2.0 * tail).min(1.0)),
Err(kind) => Value::Error(kind),
}
}
fn variance_ratio_log(
left_variance: f64,
right_variance: f64,
left_count: u64,
right_count: u64,
) -> (f64, f64, f64) {
if left_variance >= right_variance {
(
left_variance.ln() - right_variance.ln(),
(left_count - 1) as f64,
(right_count - 1) as f64,
)
} else {
(
right_variance.ln() - left_variance.ln(),
(right_count - 1) as f64,
(left_count - 1) as f64,
)
}
}
pub(super) fn degrees_of_freedom(
engine: &Engine<'_>,
context: EvalContext<'_>,
argument: &Expr,
) -> Result<f64, ErrorKind> {
let df = required_number(engine, context, argument)?.trunc();
if (1.0..1e10).contains(&df) {
Ok(df)
} else {
Err(ErrorKind::Num)
}
}
pub(super) fn nonnegative_x(
engine: &Engine<'_>,
context: EvalContext<'_>,
argument: &Expr,
) -> Result<f64, ErrorKind> {
match required_number(engine, context, argument)? {
value if value.is_finite() && value >= 0.0 => Ok(value),
_ => Err(ErrorKind::Num),
}
}
fn f_coordinates(x: f64, df1: f64, df2: f64) -> (f64, f64) {
if x == 0.0 {
return (0.0, 1.0);
}
let scaled = df1 * x;
if scaled.is_finite() {
let denominator = scaled + df2;
(scaled / denominator, df2 / denominator)
} else {
let threshold = df2 / df1;
if x <= threshold {
let ratio = x * (df1 / df2);
(ratio / (1.0 + ratio), 1.0 / (1.0 + ratio))
} else {
let ratio = threshold / x;
(1.0 / (1.0 + ratio), ratio / (1.0 + ratio))
}
}
}
fn f_log_coordinates(x: f64, df1: f64, df2: f64) -> (f64, f64) {
if x == 0.0 {
return (f64::NEG_INFINITY, 0.0);
}
f_log_coordinates_from_ratio(x.ln() + df1.ln() - df2.ln())
}
fn f_log_coordinates_from_ratio(log_ratio: f64) -> (f64, f64) {
if log_ratio <= 0.0 {
let log_w = -log_ratio.exp().ln_1p();
(log_ratio + log_w, log_w)
} else {
let log_z = -(-log_ratio).exp().ln_1p();
(log_z, -log_ratio + log_z)
}
}
pub(super) fn coordinate_logs(
x: f64,
z: f64,
w: f64,
log_z: f64,
log_w: f64,
) -> (Option<f64>, Option<f64>) {
if (z == 0.0 || w == 0.0 || z == 1.0) && x > 0.0 {
(Some(log_z), Some(log_w))
} else {
(None, None)
}
}
fn lower_tail(
x: f64,
df1: f64,
df2: f64,
on_iteration: impl FnMut() -> Result<(), ErrorKind>,
) -> Result<f64, ErrorKind> {
let (z, w) = f_coordinates(x, df1, df2);
let (log_z, log_w) = f_log_coordinates(x, df1, df2);
let (log_z, log_w) = coordinate_logs(x, z, w, log_z, log_w);
regularized_incomplete_beta_lower(df1 / 2.0, df2 / 2.0, z, log_z, log_w, on_iteration)
}
fn upper_tail(
x: f64,
df1: f64,
df2: f64,
on_iteration: impl FnMut() -> Result<(), ErrorKind>,
) -> Result<f64, ErrorKind> {
let (z, _) = f_coordinates(x, df1, df2);
let (log_z, log_w) = f_log_coordinates(x, df1, df2);
regularized_incomplete_beta_upper(
df1 / 2.0,
df2 / 2.0,
z,
Some(log_z),
Some(log_w),
on_iteration,
)
}
fn upper_tail_from_log_ratio(
log_ratio: f64,
df1: f64,
df2: f64,
on_iteration: impl FnMut() -> Result<(), ErrorKind>,
) -> Result<f64, ErrorKind> {
let (log_z, log_w) = f_log_coordinates_from_ratio(log_ratio);
regularized_incomplete_beta_lower(
df2 / 2.0,
df1 / 2.0,
log_w.exp(),
Some(log_w),
Some(log_z),
on_iteration,
)
}
fn density(x: f64, df1: f64, df2: f64) -> Result<f64, ErrorKind> {
if x == 0.0 {
return if df1 < 2.0 {
Err(ErrorKind::Num)
} else if df1 == 2.0 {
Ok(1.0)
} else {
Ok(0.0)
};
}
let (z, _) = f_coordinates(x, df1, df2);
let (log_z, log_w) = f_log_coordinates(x, df1, df2);
let exponent = beta_density_exponent(df1 / 2.0, df2 / 2.0, z, log_z, log_w)?;
Ok((exponent + log_z + log_w - x.ln()).exp())
}
fn restore_f_coordinate(z: f64, w: f64, df1: f64, df2: f64) -> f64 {
(df2 / df1) * z / w
}
pub(in crate::calculation::functions) fn sample_moments(
engine: &Engine<'_>,
context: EvalContext<'_>,
argument: &Expr,
) -> Result<NumericMoments, ErrorKind> {
let values = engine.eval_array(context, argument)?;
let mut numbers = Vec::new();
for item in values.data {
poll_cancellation(context)?;
match item {
Value::Error(kind) => return Err(kind),
Value::Number(number) => numbers.push(number),
_ => {}
}
}
NumericMoments::collect_with_work(numbers, || {
poll_cancellation(context)?;
engine.charge_function_iterations(context, 1)
})
}
#[cfg(test)]
mod tests {
use super::{
coordinate_logs, density, f_coordinates, f_log_coordinates, lower_tail,
restore_f_coordinate, upper_tail, upper_tail_from_log_ratio, variance_ratio_log,
};
use crate::calculation::functions::moments::{NumericMoments, VarianceKind};
use crate::calculation::functions::special_functions::beta_pair;
use crate::calculation::value::ErrorKind;
fn assert_tail(actual: f64, expected: f64, what: &str) {
if expected >= 1e-12 {
assert_within(actual, expected, 2e-14, 2e-12, what);
} else {
assert_within(actual, expected, 2.0 * f64::from_bits(1), 5e-9, what);
}
}
fn assert_density(actual: f64, expected: f64, what: &str) {
assert_within(actual, expected, 2e-14, 2e-11, what);
}
fn assert_quantile(actual: f64, expected: f64, what: &str) {
assert_within(actual, expected, 2e-12, 2e-9, what);
}
fn assert_within(actual: f64, expected: f64, abs_tol: f64, rel_tol: f64, what: &str) {
let diff = (actual - expected).abs();
let limit = abs_tol + rel_tol * expected.abs();
assert!(
diff <= limit,
"{what}: {actual} vs {expected} (diff {diff:e} > {limit:e})",
);
}
fn sample_variance(values: &[f64]) -> f64 {
NumericMoments::collect_with_work(values.iter().copied(), || Ok(()))
.expect("fixture samples are finite")
.variance(VarianceKind::Sample)
.expect("fixture samples have at least two values")
}
fn f_inverse(p: f64, df1: f64, df2: f64) -> f64 {
let (z, w) = beta_pair(df1 / 2.0, df2 / 2.0, p, || Ok(())).expect("solver converges");
restore_f_coordinate(z, w, df1, df2)
}
#[allow(clippy::approx_constant)]
const CUMULATIVE_GRID: &[(f64, f64, f64, f64, f64)] = &[
(0.0, 1.0, 1.0, 0.0, 1.0),
(1e-300, 1.0, 1.0, 6.366197723675813e-151, 1.0),
(1e-10, 1.0, 1.0, 6.366197723463607e-06, 0.9999936338022766),
(0.5, 1.0, 1.0, 0.39182655203060723, 0.6081734479693928),
(1.0, 1.0, 1.0, 0.5, 0.5),
(2.0, 1.0, 1.0, 0.6081734479693927, 0.3918265520306073),
(10.0, 1.0, 1.0, 0.8050177709578633, 0.1949822290421367),
(
1000000.0,
1.0,
1.0,
0.999363380439823,
0.0006366195601769944,
),
(1e+300, 1.0, 1.0, 1.0, 6.36619772367589e-151),
(1e+307, 1.0, 1.0, 1.0, 2.013168484179458e-154),
(0.0, 1.0, 2.0, 0.0, 1.0),
(1e-300, 1.0, 2.0, 7.071067811865476e-151, 1.0),
(1e-10, 1.0, 2.0, 7.071067811688699e-06, 0.9999929289321883),
(0.5, 1.0, 2.0, 0.4472135954999579, 0.552786404500042),
(1.0, 1.0, 2.0, 0.5773502691896257, 0.42264973081037427),
(2.0, 1.0, 2.0, 0.7071067811865476, 0.2928932188134525),
(10.0, 1.0, 2.0, 0.9128709291752769, 0.08712907082472313),
(1000000.0, 1.0, 2.0, 0.9999990000015, 9.99998500015988e-07),
(1e13, 1.0, 2.0, 0.9999999999999, 9.9999999999985e-14),
(1e+300, 1.0, 2.0, 1.0, 9.999999999999687e-301),
(1e+307, 1.0, 2.0, 1.0, 9.999999999999218e-308),
(0.0, 2.0, 5.0, 0.0, 1.0),
(1e-300, 2.0, 5.0, 1e-300, 1.0),
(1e-10, 2.0, 5.0, 9.999999999300001e-11, 0.9999999999),
(0.5, 2.0, 5.0, 0.36606185473939107, 0.633938145260609),
(1.0, 2.0, 5.0, 0.5687988496283078, 0.43120115037169215),
(2.0, 2.0, 5.0, 0.7699518541666883, 0.23004814583331173),
(10.0, 2.0, 5.0, 0.9821114561800017, 0.01788854381999831),
(
1000000.0,
2.0,
5.0,
0.9999999999999901,
9.88205592506318e-15,
),
(1e+300, 2.0, 5.0, 1.0, 0.0),
(1e+307, 2.0, 5.0, 1.0, 0.0),
(0.0, 5.0, 30.0, 0.0, 1.0),
(1e-300, 5.0, 30.0, 0.0, 1.0),
(1e-10, 5.0, 30.0, 3.3518801847783487e-25, 1.0),
(0.5, 5.0, 30.0, 0.22626640629640513, 0.7737335937035948),
(1.0, 5.0, 30.0, 0.5653511236601266, 0.43464887633987337),
(2.0, 5.0, 30.0, 0.8926646818950412, 0.10733531810495875),
(10.0, 5.0, 30.0, 0.999989505238318, 1.0494761682015427e-05),
(1000000.0, 5.0, 30.0, 1.0, 2.316014994196991e-77),
(1e+300, 5.0, 30.0, 1.0, 0.0),
(1e+307, 5.0, 30.0, 1.0, 0.0),
(0.0, 30.0, 5.0, 0.0, 1.0),
(1e-300, 30.0, 5.0, 0.0, 1.0),
(1e-10, 30.0, 5.0, 2.3162429647964282e-137, 1.0),
(0.5, 30.0, 5.0, 0.10733531810495875, 0.8926646818950412),
(1.0, 30.0, 5.0, 0.4346488763398731, 0.5653511236601269),
(2.0, 30.0, 5.0, 0.7737335937035952, 0.22626640629640485),
(10.0, 30.0, 5.0, 0.9913657573262192, 0.008634242673780743),
(
1000000.0,
30.0,
5.0,
0.9999999999999967,
3.3518732046886535e-15,
),
(1e+300, 30.0, 5.0, 1.0, 0.0),
(1e+307, 30.0, 5.0, 1.0, 0.0),
(0.0, 1000000.0, 1000000.0, 0.0, 1.0),
(1e-300, 1000000.0, 1000000.0, 0.0, 1.0),
(1e-10, 1000000.0, 1000000.0, 0.0, 1.0),
(0.5, 1000000.0, 1000000.0, 0.0, 1.0),
(1.0, 1000000.0, 1000000.0, 0.5, 0.5),
(2.0, 1000000.0, 1000000.0, 1.0, 0.0),
(10.0, 1000000.0, 1000000.0, 1.0, 0.0),
(1000000.0, 1000000.0, 1000000.0, 1.0, 0.0),
(1e+300, 1000000.0, 1000000.0, 1.0, 0.0),
(1e+307, 1000000.0, 1000000.0, 1.0, 0.0),
(0.0, 1000000.0, 999000000.0, 0.0, 1.0),
(1e-300, 1000000.0, 999000000.0, 0.0, 1.0),
(1e-10, 1000000.0, 999000000.0, 0.0, 1.0),
(0.5, 1000000.0, 999000000.0, 0.0, 1.0),
(
1.0,
1000000.0,
999000000.0,
0.5001877809842447,
0.4998122190157553,
),
(2.0, 1000000.0, 999000000.0, 1.0, 0.0),
(10.0, 1000000.0, 999000000.0, 1.0, 0.0),
(1000000.0, 1000000.0, 999000000.0, 1.0, 0.0),
(1e+300, 1000000.0, 999000000.0, 1.0, 0.0),
(1e+307, 1000000.0, 999000000.0, 1.0, 0.0),
(0.0, 500000000.0, 500000000.0, 0.0, 1.0),
(1e-300, 500000000.0, 500000000.0, 0.0, 1.0),
(1e-10, 500000000.0, 500000000.0, 0.0, 1.0),
(0.5, 500000000.0, 500000000.0, 0.0, 1.0),
(1.0, 500000000.0, 500000000.0, 0.5, 0.5),
(2.0, 500000000.0, 500000000.0, 1.0, 0.0),
(10.0, 500000000.0, 500000000.0, 1.0, 0.0),
(1000000.0, 500000000.0, 500000000.0, 1.0, 0.0),
(1e+300, 500000000.0, 500000000.0, 1.0, 0.0),
(1e+307, 500000000.0, 500000000.0, 1.0, 0.0),
(0.0, 500000000.0, 4500000000.0, 0.0, 1.0),
(1e-300, 500000000.0, 4500000000.0, 0.0, 1.0),
(1e-10, 500000000.0, 4500000000.0, 0.0, 1.0),
(0.5, 500000000.0, 4500000000.0, 0.0, 1.0),
(
1.0,
500000000.0,
4500000000.0,
0.5000070923075768,
0.4999929076924232,
),
(2.0, 500000000.0, 4500000000.0, 1.0, 0.0),
(10.0, 500000000.0, 4500000000.0, 1.0, 0.0),
(1000000.0, 500000000.0, 4500000000.0, 1.0, 0.0),
(1e+300, 500000000.0, 4500000000.0, 1.0, 0.0),
(1e+307, 500000000.0, 4500000000.0, 1.0, 0.0),
(0.0, 9999999999.0, 9999999999.0, 0.0, 1.0),
(1e-300, 9999999999.0, 9999999999.0, 0.0, 1.0),
(1e-10, 9999999999.0, 9999999999.0, 0.0, 1.0),
(0.5, 9999999999.0, 9999999999.0, 0.0, 1.0),
(1.0, 9999999999.0, 9999999999.0, 0.5, 0.5),
(2.0, 9999999999.0, 9999999999.0, 1.0, 0.0),
(10.0, 9999999999.0, 9999999999.0, 1.0, 0.0),
(1000000.0, 9999999999.0, 9999999999.0, 1.0, 0.0),
(1e+300, 9999999999.0, 9999999999.0, 1.0, 0.0),
(1e+307, 9999999999.0, 9999999999.0, 1.0, 0.0),
(0.0, 3.7, 7.2, 0.0, 1.0),
(1e-300, 3.7, 7.2, 0.0, 1.0),
(1e-10, 3.7, 7.2, 6.855029026436743e-19, 1.0),
(0.5, 3.7, 7.2, 0.27390058075169565, 0.7260994192483043),
(1.0, 3.7, 7.2, 0.5393832611844304, 0.4606167388155697),
(2.0, 3.7, 7.2, 0.8023242976920558, 0.1976757023079442),
(10.0, 3.7, 7.2, 0.9950537532436279, 0.004946246756372084),
(1000000.0, 3.7, 7.2, 1.0, 1.05350749731921e-20),
(1e+300, 3.7, 7.2, 1.0, 0.0),
(1e+307, 3.7, 7.2, 1.0, 0.0),
];
const CENTRAL_BAND: &[(f64, f64, f64, f64, f64)] = &[
(1.0, 1000000.0, 1000000.0, 0.5, 0.5),
(
1.0020020009999997,
1000000.0,
1000000.0,
0.841344625083191,
0.158655374916809,
),
(
0.9980019990000002,
1000000.0,
1000000.0,
0.15865537491675527,
0.8413446250832447,
),
(
1.0080321244818662,
1000000.0,
1000000.0,
0.9999683304979299,
3.166950207016052e-05,
),
(
0.9920318764781482,
1000000.0,
1000000.0,
3.166950207014567e-05,
0.9999683304979299,
),
(
1.0161290241285181,
1000000.0,
1000000.0,
0.9999999999999993,
6.214799597083254e-16,
),
(
0.984126992000498,
1000000.0,
1000000.0,
6.214799597083254e-16,
0.9999999999999993,
),
(
1.0242914856824497,
1000000.0,
1000000.0,
1.0,
1.7674252088144044e-33,
),
(
0.9762845966973307,
1000000.0,
1000000.0,
1.7674252088120332e-33,
1.0,
),
(1.0, 500000000.0, 500000000.0, 0.5, 0.5),
(
1.0000894467191894,
500000000.0,
500000000.0,
0.8413447458260245,
0.15865525417397552,
),
(
0.9999105612808106,
500000000.0,
500000000.0,
0.15865525417337484,
0.8413447458266252,
),
(
1.0003578348874929,
500000000.0,
500000000.0,
0.9999683287616465,
3.167123835341704e-05,
),
(
0.9996422931125111,
500000000.0,
500000000.0,
3.167123835341704e-05,
0.9999683287616465,
),
(
1.000715797843706,
500000000.0,
500000000.0,
0.9999999999999993,
6.220948246903931e-16,
),
(
0.9992847141563586,
500000000.0,
500000000.0,
6.220948246778507e-16,
0.9999999999999993,
),
(
1.0010738889374053,
500000000.0,
500000000.0,
1.0,
1.776463953810829e-33,
),
(
0.998927263062924,
500000000.0,
500000000.0,
1.776463953810829e-33,
1.0,
),
(
1.0,
500000000.0,
4500000000.0,
0.5000070923075768,
0.4999929076924232,
),
(
1.0000666671111007,
500000000.0,
4500000000.0,
0.8413447461344663,
0.15865525386553372,
),
(
0.9999333337777881,
500000000.0,
4500000000.0,
0.1586552538654145,
0.8413447461345855,
),
(
1.000266673777914,
500000000.0,
4500000000.0,
0.9999682930564624,
3.170694353758117e-05,
),
(
0.9997333404443082,
500000000.0,
4500000000.0,
3.163556741440673e-05,
0.9999683644325856,
),
(
1.0005333617791883,
500000000.0,
4500000000.0,
0.9999999999999993,
6.277783374421375e-16,
),
(
0.9994666951097008,
500000000.0,
4500000000.0,
6.164611300301199e-16,
0.9999999999999993,
),
(
1.0008000640049604,
500000000.0,
4500000000.0,
1.0,
1.8318638762876417e-33,
),
(
0.9992000639950404,
500000000.0,
4500000000.0,
1.7227168249589655e-33,
1.0,
),
(1.0, 9999999999.0, 9999999999.0, 0.5, 0.5),
(
1.000020000200002,
9999999999.0,
9999999999.0,
0.8413447460580296,
0.1586552539419704,
),
(
0.999980000199998,
9999999999.0,
9999999999.0,
0.1586552539446568,
0.8413447460553432,
),
(
1.000080003200128,
9999999999.0,
9999999999.0,
0.9999683287583414,
3.167124165860526e-05,
),
(
0.999920003199872,
9999999999.0,
9999999999.0,
3.167124165860526e-05,
0.9999683287583414,
),
(
1.0001600128010242,
9999999999.0,
9999999999.0,
0.9999999999999993,
6.220959957490522e-16,
),
(
0.999840012798976,
9999999999.0,
9999999999.0,
6.220959956929608e-16,
0.9999999999999993,
),
(
1.0002400288034563,
9999999999.0,
9999999999.0,
1.0,
1.7764812043765858e-33,
),
(
0.9997600287965445,
9999999999.0,
9999999999.0,
1.7764812043765858e-33,
1.0,
),
];
const TRANSITION_BOUNDARY: &[(f64, f64, f64, f64, f64)] = &[
(
1.024291485679991,
1000000.0,
1000000.0,
1.0,
1.7674252344418338e-33,
),
(
1.0242914856849081,
1000000.0,
1000000.0,
1.0,
1.767425183182233e-33,
),
(
0.9762845966996742,
1000000.0,
1000000.0,
1.7674252344418338e-33,
1.0,
),
(
0.9762845966949873,
1000000.0,
1000000.0,
1.767425183184604e-33,
1.0,
),
(
1.001073888937298,
500000000.0,
500000000.0,
1.0,
1.776463979493804e-33,
),
(
1.0010738889375128,
500000000.0,
500000000.0,
1.0,
1.776463928021286e-33,
),
(
0.9989272630630313,
500000000.0,
500000000.0,
1.7764639795470882e-33,
1.0,
),
(
0.9989272630628168,
500000000.0,
500000000.0,
1.776463928021286e-33,
1.0,
),
];
#[test]
fn cumulative_and_upper_tail_match_the_decimal_reference() {
for &(x, df1, df2, expected_lower, expected_upper) in CUMULATIVE_GRID
.iter()
.chain(CENTRAL_BAND)
.chain(TRANSITION_BOUNDARY)
{
let actual_lower = lower_tail(x, df1, df2, || Ok(())).expect("finite lower tail");
assert_tail(
actual_lower,
expected_lower,
&format!("F.DIST({x}, {df1}, {df2}, TRUE)"),
);
let actual_upper = upper_tail(x, df1, df2, || Ok(())).expect("finite upper tail");
assert_tail(
actual_upper,
expected_upper,
&format!("F.DIST.RT({x}, {df1}, {df2})"),
);
}
}
const DENSITY_GRID: &[(f64, f64, f64, f64)] = &[
(1e-300, 1.0, 1.0, 3.183098861837907e+149),
(1e-10, 1.0, 1.0, 31830.98861519597),
(0.5, 1.0, 1.0, 0.30010543871903533),
(1.0, 1.0, 1.0, 0.15915494309189535),
(2.0, 1.0, 1.0, 0.07502635967975883),
(10.0, 1.0, 1.0, 0.009150765837179461),
(1000000.0, 1.0, 1.0, 3.183095678742228e-10),
(1e-300, 1.0, 2.0, 3.5355339059327374e+149),
(1e-10, 1.0, 2.0, 35355.33905667572),
(0.5, 1.0, 2.0, 0.35777087639996635),
(1.0, 1.0, 2.0, 0.19245008972987526),
(2.0, 1.0, 2.0, 0.08838834764831845),
(10.0, 1.0, 2.0, 0.007607257743127307),
(1000000.0, 1.0, 2.0, 9.999970000075e-13),
(1e-300, 2.0, 5.0, 1.0),
(1e-10, 2.0, 5.0, 0.99999999986),
(0.5, 2.0, 5.0, 0.5282817877171742),
(1.0, 2.0, 5.0, 0.3080008216940658),
(2.0, 2.0, 5.0, 0.12780452546295093),
(10.0, 2.0, 5.0, 0.0035777087639996636),
(1000000.0, 2.0, 5.0, 2.4705078049956996e-20),
(1e-300, 5.0, 30.0, 0.0),
(1e-10, 5.0, 30.0, 8.379700461247563e-15),
(0.5, 5.0, 30.0, 0.7300399754408147),
(1.0, 5.0, 30.0, 0.564494449909015),
(2.0, 5.0, 30.0, 0.15429277772740163),
(10.0, 5.0, 30.0, 9.306270678189991e-06),
(1000000.0, 5.0, 30.0, 3.4739996933709466e-82),
(1e-300, 30.0, 5.0, 0.0),
(1e-10, 30.0, 5.0, 3.4743644449145986e-126),
(0.5, 30.0, 5.0, 0.6171711109096065),
(1.0, 30.0, 5.0, 0.564494449909015),
(2.0, 30.0, 5.0, 0.18250999386020367),
(10.0, 30.0, 5.0, 0.001984281410992701),
(1000000.0, 30.0, 5.0, 8.379676022936302e-21),
(1e-300, 1000000.0, 1000000.0, 0.0),
(1e-10, 1000000.0, 1000000.0, 0.0),
(0.5, 1000000.0, 1000000.0, 0.0),
(1.0, 1000000.0, 1000000.0, 199.47109033293754),
(2.0, 1000000.0, 1000000.0, 0.0),
(10.0, 1000000.0, 1000000.0, 0.0),
(1000000.0, 1000000.0, 1000000.0, 0.0),
(1e-300, 1000000.0, 999000000.0, 0.0),
(1e-10, 1000000.0, 999000000.0, 0.0),
(0.5, 1000000.0, 999000000.0, 0.0),
(1.0, 1000000.0, 999000000.0, 281.9536621061723),
(2.0, 1000000.0, 999000000.0, 0.0),
(10.0, 1000000.0, 999000000.0, 0.0),
(1000000.0, 1000000.0, 999000000.0, 0.0),
(1e-300, 500000000.0, 500000000.0, 0.0),
(1e-10, 500000000.0, 500000000.0, 0.0),
(0.5, 500000000.0, 500000000.0, 0.0),
(1.0, 500000000.0, 500000000.0, 4460.3102881517725),
(2.0, 500000000.0, 500000000.0, 0.0),
(10.0, 500000000.0, 500000000.0, 0.0),
(1000000.0, 500000000.0, 500000000.0, 0.0),
(1e-300, 500000000.0, 4500000000.0, 0.0),
(1e-10, 500000000.0, 4500000000.0, 0.0),
(0.5, 500000000.0, 4500000000.0, 0.0),
(1.0, 500000000.0, 4500000000.0, 5984.134204004616),
(2.0, 500000000.0, 4500000000.0, 0.0),
(10.0, 500000000.0, 4500000000.0, 0.0),
(1000000.0, 500000000.0, 4500000000.0, 0.0),
(1e-300, 9999999999.0, 9999999999.0, 0.0),
(1e-10, 9999999999.0, 9999999999.0, 0.0),
(0.5, 9999999999.0, 9999999999.0, 0.0),
(1.0, 9999999999.0, 9999999999.0, 19947.1140185756),
(2.0, 9999999999.0, 9999999999.0, 0.0),
(10.0, 9999999999.0, 9999999999.0, 0.0),
(1000000.0, 9999999999.0, 9999999999.0, 0.0),
(1e-300, 3.7, 7.2, 4.010338453498581e-255),
(1e-10, 3.7, 7.2, 1.2681803697661737e-08),
(0.5, 3.7, 7.2, 0.639783269709018),
(1.0, 3.7, 7.2, 0.4184742294240687),
(2.0, 3.7, 7.2, 0.1533894761971639),
(10.0, 3.7, 7.2, 0.0014391026209959502),
(1000000.0, 3.7, 7.2, 3.7926182460237685e-26),
];
#[test]
fn density_matches_the_decimal_reference_and_enforces_the_origin_contract() {
for &(x, df1, df2, expected) in DENSITY_GRID {
let actual = density(x, df1, df2).expect("finite density");
assert_density(
actual,
expected,
&format!("F.DIST({x}, {df1}, {df2}, FALSE)"),
);
}
assert!(matches!(density(0.0, 1.0, 30.0), Err(ErrorKind::Num)));
assert_eq!(density(0.0, 2.0, 2.0), Ok(1.0));
assert_eq!(density(0.0, 2.0, 30.0), Ok(1.0));
assert_eq!(density(0.0, 5.0, 2.0), Ok(0.0));
assert_eq!(density(0.0, 5.0, 30.0), Ok(0.0));
}
const QUANTILE_GRID: &[(f64, f64, f64, f64, f64)] = &[
(1e-15, 1.0, 1.0, 2.46740110027234e-30, 4.05284734569351e+29),
(1e-15, 2.0, 5.0, 1.0000000000000009e-15, 2499997.5),
(1e-06, 5.0, 30.0, 0.0024590889317047692, 12.863164945310249),
(0.5, 1.0, 1.0, 1.0, 1.0),
(0.5, 2.0, 5.0, 0.7987697769322356, 0.7987697769322356),
(0.9, 5.0, 30.0, 2.049246080685769, 0.3150514950189801),
(0.9, 30.0, 5.0, 3.1740842872043995, 0.48798434186359674),
(
0.999999999999999,
1.0,
1.0,
4.059333823527321e+29,
2.4634583985287003e-30,
),
(
0.999999999999999,
2.0,
5.0,
2500797.2253150404,
9.992007221626417e-16,
),
(0.5, 1000000.0, 1000000.0, 1.0, 1.0),
(
0.9,
1000000.0,
999000000.0,
1.0018137250507129,
0.9981871371671881,
),
(
0.999999999999999,
500000000.0,
500000000.0,
1.0007105567222643,
0.9992899478100924,
),
(0.5, 9999999999.0, 9999999999.0, 1.0, 1.0),
(
1e-06,
9999999999.0,
9999999999.0,
0.9999049360326638,
1.0000950730053533,
),
];
#[test]
fn quantiles_match_the_decimal_reference() {
for &(p, df1, df2, expected_lower, expected_upper) in QUANTILE_GRID {
let actual_lower = f_inverse(p, df1, df2);
assert_quantile(
actual_lower,
expected_lower,
&format!("F.INV({p}, {df1}, {df2})"),
);
let (w, z) = beta_pair(df2 / 2.0, df1 / 2.0, p, || Ok(())).expect("solver converges");
let actual_upper = restore_f_coordinate(z, w, df1, df2);
assert_quantile(
actual_upper,
expected_upper,
&format!("F.INV.RT({p}, {df1}, {df2})"),
);
}
}
#[test]
fn quantiles_round_trip_through_the_cdf() {
for &(p, df1, df2, _, _) in QUANTILE_GRID {
let quantile = f_inverse(p, df1, df2);
let cdf = lower_tail(quantile, df1, df2, || Ok(())).expect("finite CDF");
let diff = (cdf - p).abs();
let limit = 1e-15 + 1e-9 * p;
assert!(
diff <= limit,
"F.DIST(F.INV({p}, {df1}, {df2})) = {cdf} (diff {diff:e} > {limit:e})",
);
}
}
const F_TEST_GRID: &[(&[f64], &[f64], f64)] = &[
(
&[1.0, 2.0, 3.0, 4.0, 5.0],
&[10.0, 11.0, 12.0, 13.0, 14.0],
1.0,
),
(
&[1.0, 2.0, 3.0, 4.0, 5.0],
&[1.0, 2.0, 3.0, 4.0, 100.0],
1.0324313445360633e-05,
),
(
&[1.0, 2.0, 3.0, 4.0, 100.0],
&[1.0, 2.0, 3.0, 4.0, 5.0],
1.0324313445360633e-05,
),
(&[1.0, 2.0], &[3.0, 4.0], 1.0),
(
&[-7e153, 7e153],
&[-1e-154, 1e-154],
1.8189136353359467e-308,
),
];
#[test]
fn f_test_p_values_match_the_decimal_reference() {
for &(left, right, expected_p) in F_TEST_GRID {
let (log_ratio, df1, df2) = variance_ratio_log(
sample_variance(left),
sample_variance(right),
left.len() as u64,
right.len() as u64,
);
let tail =
upper_tail_from_log_ratio(log_ratio, df1, df2, || Ok(())).expect("finite tail");
let actual_p = (2.0 * tail).min(1.0);
let label = format!("F.TEST({left:?}, {right:?})");
assert_tail(actual_p, expected_p, &label);
let (swapped_log_ratio, swapped_df1, swapped_df2) = variance_ratio_log(
sample_variance(right),
sample_variance(left),
right.len() as u64,
left.len() as u64,
);
assert_eq!(
(swapped_log_ratio, swapped_df1, swapped_df2),
(log_ratio, df1, df2),
);
}
}
#[allow(clippy::approx_constant)]
#[test]
fn coordinates_and_log_coordinates_survive_extreme_x() {
let (z, w) = f_coordinates(1e300, 1.0, 1.0);
assert_eq!(z, 1.0);
assert_eq!(w, 1e-300);
let (log_z, log_w) = f_log_coordinates(1e300, 1.0, 1.0);
assert_within(log_z, -1e-300, 1e-315, 1e-12, "log_z at x = 1e300");
assert_within(
log_w,
-690.775_527_898_213_7,
1e-12,
1e-15,
"log_w at x = 1e300",
);
let (z, w) = f_coordinates(1e-300, 1.0, 1.0);
assert_eq!(z, 1e-300);
assert_eq!(w, 1.0);
let (log_z, log_w) = f_log_coordinates(1e-300, 1.0, 1.0);
assert_within(
log_z,
-690.775_527_898_213_7,
1e-12,
1e-15,
"log_z at x = 1e-300",
);
assert_within(log_w, -1e-300, 1e-315, 1e-12, "log_w at x = 1e-300");
let (z, w) = f_coordinates(1e300, 1.0, 1.0);
let (log_z, log_w) = f_log_coordinates(1e300, 1.0, 1.0);
assert_eq!(
coordinate_logs(1e300, z, w, log_z, log_w),
(Some(log_z), Some(log_w)),
);
assert_eq!(
coordinate_logs(0.0, 0.0, 1.0, f64::NEG_INFINITY, 0.0),
(None, None)
);
assert_eq!(
coordinate_logs(
1.0,
0.5,
0.5,
-0.693_147_180_559_945_3,
-0.693_147_180_559_945_3
),
(None, None)
);
}
#[test]
fn restore_scales_before_dividing_so_subnormal_coordinates_survive() {
let restored = restore_f_coordinate(1.0, 1e-310, 1e10, 1.0);
assert!(restored.is_finite());
assert_within(
restored,
1e300,
1e286,
1e-13,
"restore scales before dividing",
);
let (z, w) = beta_pair(1.0, 2.5, 1e-15, || Ok(())).expect("solver converges");
let quantile = restore_f_coordinate(z, w, 2.0, 5.0);
assert_quantile(quantile, 1.0000000000000009e-15, "F.INV(1e-15, 2, 5)");
}
}