Files
IronCalc/base/src/functions/statistical/beta.rs

214 lines
6.3 KiB
Rust

use statrs::distribution::{Beta, Continuous, ContinuousCDF};
use crate::expressions::types::CellReferenceIndex;
use crate::{
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
};
impl<'a> Model<'a> {
// BETA.DIST(x, alpha, beta, cumulative, [A], [B])
pub(crate) fn fn_beta_dist(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(4..=6).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(e) => return e,
};
let alpha = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(e) => return e,
};
let beta_param = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(e) => return e,
};
// cumulative argument: interpret like Excel
let cumulative = match self.evaluate_node_in_context(&args[3], cell) {
CalcResult::Boolean(b) => b,
CalcResult::Number(n) => n != 0.0,
CalcResult::String(s) => {
let up = s.to_ascii_uppercase();
if up == "TRUE" {
true
} else if up == "FALSE" {
false
} else {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "cumulative must be TRUE/FALSE or numeric".to_string(),
};
}
}
error @ CalcResult::Error { .. } => return error,
_ => {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid cumulative argument".to_string(),
}
}
};
// Optional A, B
let a = if arg_count >= 5 {
match self.get_number_no_bools(&args[4], cell) {
Ok(f) => f,
Err(e) => return e,
}
} else {
0.0
};
let b = if arg_count >= 6 {
match self.get_number_no_bools(&args[5], cell) {
Ok(f) => f,
Err(e) => return e,
}
} else {
1.0
};
// Excel: alpha <= 0 or beta <= 0 → #NUM!
if alpha <= 0.0 || beta_param <= 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"alpha and beta must be > 0 in BETA.DIST".to_string(),
);
}
// Excel: if x < A, x > B, or A = B → #NUM!
if b == a || x < a || x > b {
return CalcResult::new_error(
Error::NUM,
cell,
"x must be between A and B and A < B in BETA.DIST".to_string(),
);
}
// Transform to standard Beta(0,1)
let width = b - a;
let t = (x - a) / width;
let dist = match Beta::new(alpha, beta_param) {
Ok(d) => d,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Invalid parameters for Beta distribution".to_string(),
)
}
};
let result = if cumulative {
dist.cdf(t)
} else {
// general-interval beta pdf: f_X(x) = f_T(t) / (B - A), t=(x-A)/(B-A)
dist.pdf(t) / width
};
if result.is_nan() || result.is_infinite() {
return CalcResult::new_error(
Error::NUM,
cell,
"Invalid result for BETA.DIST".to_string(),
);
}
CalcResult::Number(result)
}
pub(crate) fn fn_beta_inv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(3..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let p = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(e) => return e,
};
let alpha = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(e) => return e,
};
let beta_param = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(e) => return e,
};
let a = if arg_count >= 4 {
match self.get_number_no_bools(&args[3], cell) {
Ok(f) => f,
Err(e) => return e,
}
} else {
0.0
};
let b = if arg_count >= 5 {
match self.get_number_no_bools(&args[4], cell) {
Ok(f) => f,
Err(e) => return e,
}
} else {
1.0
};
if alpha <= 0.0 || beta_param <= 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"alpha and beta must be > 0 in BETA.INV".to_string(),
);
}
// probability <= 0 or probability > 1 → #NUM!
if p <= 0.0 || p > 1.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"probability must be in (0,1] in BETA.INV".to_string(),
);
}
if b <= a {
return CalcResult::new_error(
Error::NUM,
cell,
"A must be < B in BETA.INV".to_string(),
);
}
let dist = match Beta::new(alpha, beta_param) {
Ok(d) => d,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Invalid parameters for Beta distribution".to_string(),
)
}
};
let t = dist.inverse_cdf(p);
if t.is_nan() || t.is_infinite() {
return CalcResult::new_error(
Error::NUM,
cell,
"Invalid result for BETA.INV".to_string(),
);
}
// Map back from [0,1] to [A,B]
let x = a + t * (b - a);
CalcResult::Number(x)
}
}