Uses statrs for numerical functions REFACTOR: Put statistical functions on its own module This might seem counter-intuitive but the wasm build after this refactor is 1528 bytes smaller :)
520 lines
20 KiB
Rust
520 lines
20 KiB
Rust
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
|
use crate::expressions::parser::ArrayNode;
|
|
use crate::expressions::types::CellReferenceIndex;
|
|
use crate::{
|
|
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
|
};
|
|
|
|
impl Model {
|
|
pub(crate) fn fn_stdev_p(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
let mut sum = 0.0;
|
|
let mut sumsq = 0.0;
|
|
let mut count: u64 = 0;
|
|
|
|
#[inline]
|
|
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
|
*sum += value;
|
|
*sumsq += value * value;
|
|
*count += 1;
|
|
}
|
|
|
|
for arg in args {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
|
|
let row1 = left.row;
|
|
let mut row2 = right.row;
|
|
let column1 = left.column;
|
|
let mut column2 = right.column;
|
|
|
|
if row1 == 1 && row2 == LAST_ROW {
|
|
row2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_row,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
if column1 == 1 && column2 == LAST_COLUMN {
|
|
column2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_column,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
for row in row1..row2 + 1 {
|
|
for column in column1..(column2 + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CalcResult::Array(array) => {
|
|
for row in array {
|
|
for value in row {
|
|
match value {
|
|
ArrayNode::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
ArrayNode::Error(error) => {
|
|
return CalcResult::Error {
|
|
error,
|
|
origin: cell,
|
|
message: "Error in array".to_string(),
|
|
}
|
|
}
|
|
_ => {
|
|
// ignore non-numeric
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric
|
|
}
|
|
}
|
|
}
|
|
|
|
if count == 0 {
|
|
return CalcResult::new_error(
|
|
Error::DIV,
|
|
cell,
|
|
"STDEV.P with no numeric data".to_string(),
|
|
);
|
|
}
|
|
|
|
let n = count as f64;
|
|
let mut var = (sumsq - (sum * sum) / n) / n;
|
|
|
|
// clamp tiny negatives from FP noise
|
|
if var < 0.0 && var > -1e-12 {
|
|
var = 0.0;
|
|
}
|
|
|
|
CalcResult::Number(var.sqrt())
|
|
}
|
|
|
|
pub(crate) fn fn_stdev_s(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
let mut sum = 0.0;
|
|
let mut sumsq = 0.0;
|
|
let mut count: u64 = 0;
|
|
|
|
#[inline]
|
|
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
|
*sum += value;
|
|
*sumsq += value * value;
|
|
*count += 1;
|
|
}
|
|
|
|
for arg in args {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
|
|
let row1 = left.row;
|
|
let mut row2 = right.row;
|
|
let column1 = left.column;
|
|
let mut column2 = right.column;
|
|
|
|
if row1 == 1 && row2 == LAST_ROW {
|
|
row2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_row,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
if column1 == 1 && column2 == LAST_COLUMN {
|
|
column2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_column,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
for row in row1..row2 + 1 {
|
|
for column in column1..(column2 + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CalcResult::Array(array) => {
|
|
for row in array {
|
|
for value in row {
|
|
match value {
|
|
ArrayNode::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
ArrayNode::Error(error) => {
|
|
return CalcResult::Error {
|
|
error,
|
|
origin: cell,
|
|
message: "Error in array".to_string(),
|
|
}
|
|
}
|
|
_ => {
|
|
// ignore non-numeric
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric
|
|
}
|
|
}
|
|
}
|
|
|
|
if count <= 1 {
|
|
return CalcResult::new_error(
|
|
Error::DIV,
|
|
cell,
|
|
"STDEV.S requires at least two numeric values".to_string(),
|
|
);
|
|
}
|
|
|
|
let n = count as f64;
|
|
let mut var = (sumsq - (sum * sum) / n) / (n - 1.0);
|
|
|
|
if var < 0.0 && var > -1e-12 {
|
|
var = 0.0;
|
|
}
|
|
|
|
CalcResult::Number(var.sqrt())
|
|
}
|
|
|
|
pub(crate) fn fn_stdeva(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
let mut sum = 0.0;
|
|
let mut sumsq = 0.0;
|
|
let mut count: u64 = 0;
|
|
|
|
#[inline]
|
|
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
|
*sum += value;
|
|
*sumsq += value * value;
|
|
*count += 1;
|
|
}
|
|
|
|
for arg in args {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
|
|
let row1 = left.row;
|
|
let mut row2 = right.row;
|
|
let column1 = left.column;
|
|
let mut column2 = right.column;
|
|
|
|
if row1 == 1 && row2 == LAST_ROW {
|
|
row2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_row,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
if column1 == 1 && column2 == LAST_COLUMN {
|
|
column2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_column,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
for row in row1..row2 + 1 {
|
|
for column in column1..(column2 + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
CalcResult::String(_) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, 0.0);
|
|
}
|
|
CalcResult::Boolean(value) => {
|
|
let val = if value { 1.0 } else { 0.0 };
|
|
accumulate(&mut sum, &mut sumsq, &mut count, val);
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric for now
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CalcResult::Array(array) => {
|
|
for row in array {
|
|
for value in row {
|
|
match value {
|
|
ArrayNode::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
ArrayNode::Error(error) => {
|
|
return CalcResult::Error {
|
|
error,
|
|
origin: cell,
|
|
message: "Error in array".to_string(),
|
|
}
|
|
}
|
|
_ => {
|
|
// ignore non-numeric for now
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric for now
|
|
}
|
|
}
|
|
}
|
|
|
|
if count <= 1 {
|
|
return CalcResult::new_error(
|
|
Error::DIV,
|
|
cell,
|
|
"STDEVA requires at least two numeric values".to_string(),
|
|
);
|
|
}
|
|
|
|
let n = count as f64;
|
|
let mut var = (sumsq - (sum * sum) / n) / (n - 1.0);
|
|
|
|
if var < 0.0 && var > -1e-12 {
|
|
var = 0.0;
|
|
}
|
|
|
|
CalcResult::Number(var.sqrt())
|
|
}
|
|
|
|
pub(crate) fn fn_stdevpa(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
let mut sum = 0.0;
|
|
let mut sumsq = 0.0;
|
|
let mut count: u64 = 0;
|
|
|
|
#[inline]
|
|
fn accumulate(sum: &mut f64, sumsq: &mut f64, count: &mut u64, value: f64) {
|
|
*sum += value;
|
|
*sumsq += value * value;
|
|
*count += 1;
|
|
}
|
|
|
|
for arg in args {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
|
|
let row1 = left.row;
|
|
let mut row2 = right.row;
|
|
let column1 = left.column;
|
|
let mut column2 = right.column;
|
|
|
|
if row1 == 1 && row2 == LAST_ROW {
|
|
row2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_row,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
if column1 == 1 && column2 == LAST_COLUMN {
|
|
column2 = match self.workbook.worksheet(left.sheet) {
|
|
Ok(s) => s.dimension().max_column,
|
|
Err(_) => {
|
|
return CalcResult::new_error(
|
|
Error::ERROR,
|
|
cell,
|
|
format!("Invalid worksheet index: '{}'", left.sheet),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
for row in row1..row2 + 1 {
|
|
for column in column1..(column2 + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
CalcResult::String(_) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, 0.0);
|
|
}
|
|
CalcResult::Boolean(value) => {
|
|
let val = if value { 1.0 } else { 0.0 };
|
|
accumulate(&mut sum, &mut sumsq, &mut count, val);
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric for now
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CalcResult::Array(array) => {
|
|
for row in array {
|
|
for value in row {
|
|
match value {
|
|
ArrayNode::Number(value) => {
|
|
accumulate(&mut sum, &mut sumsq, &mut count, value);
|
|
}
|
|
ArrayNode::Error(error) => {
|
|
return CalcResult::Error {
|
|
error,
|
|
origin: cell,
|
|
message: "Error in array".to_string(),
|
|
}
|
|
}
|
|
_ => {
|
|
// ignore non-numeric for now
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
_ => {
|
|
// ignore non-numeric for now
|
|
}
|
|
}
|
|
}
|
|
|
|
if count == 0 {
|
|
return CalcResult::new_error(
|
|
Error::DIV,
|
|
cell,
|
|
"STDEVPA with no numeric data".to_string(),
|
|
);
|
|
}
|
|
|
|
let n = count as f64;
|
|
let mut var = (sumsq - (sum * sum) / n) / n;
|
|
|
|
if var < 0.0 && var > -1e-12 {
|
|
var = 0.0;
|
|
}
|
|
|
|
CalcResult::Number(var.sqrt())
|
|
}
|
|
}
|