use crate::cast::NumberOrArray; use crate::constants::{LAST_COLUMN, LAST_ROW}; use crate::expressions::parser::ArrayNode; use crate::expressions::types::CellReferenceIndex; use crate::functions::math_util::{from_roman, to_roman_with_form}; use crate::number_format::to_precision; use crate::single_number_fn; use crate::{ calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model, }; use std::f64::consts::PI; #[cfg(not(target_arch = "wasm32"))] pub fn random() -> f64 { rand::random() } // Euclidean gcd for i64 (non-negative inputs expected) fn gcd_i64(mut a: i64, mut b: i64) -> i64 { while b != 0 { let r = a % b; a = b; b = r; } a } // lcm(a, b) = a / gcd(a, b) * b // we do it in i128 to reduce overflow risk, then back to i64/f64 fn lcm_i64(a: i64, b: i64) -> Option { if a == 0 || b == 0 { return Some(0); } let g = gcd_i64(a, b); let a_div_g = (a / g) as i128; let prod = a_div_g * (b as i128); if prod > i64::MAX as i128 { None } else { Some(prod as i64) } } #[cfg(target_arch = "wasm32")] pub fn random() -> f64 { use js_sys::Math; Math::random() } impl Model { pub(crate) fn fn_min(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let mut result = f64::NAN; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => result = value.min(result), CalcResult::Range { left, right } => { if left.sheet != right.sheet { return CalcResult::new_error( Error::VALUE, cell, "Ranges are in different sheets".to_string(), ); } for row in left.row..(right.row + 1) { for column in left.column..(right.column + 1) { match self.evaluate_cell(CellReferenceIndex { sheet: left.sheet, row, column, }) { CalcResult::Number(value) => { result = value.min(result); } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } } } } } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } }; } if result.is_nan() || result.is_infinite() { return CalcResult::Number(0.0); } CalcResult::Number(result) } pub(crate) fn fn_max(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let mut result = f64::NAN; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => result = value.max(result), CalcResult::Range { left, right } => { if left.sheet != right.sheet { return CalcResult::new_error( Error::VALUE, cell, "Ranges are in different sheets".to_string(), ); } for row in left.row..(right.row + 1) { for column in left.column..(right.column + 1) { match self.evaluate_cell(CellReferenceIndex { sheet: left.sheet, row, column, }) { CalcResult::Number(value) => { result = value.max(result); } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } } } } } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } }; } if result.is_nan() || result.is_infinite() { return CalcResult::Number(0.0); } CalcResult::Number(result) } pub(crate) fn fn_base(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let arg_count = args.len(); if !(2..=3).contains(&arg_count) { return CalcResult::new_args_number_error(cell); } // number to convert let mut value = match self.get_number(&args[0], cell) { Ok(f) => f.trunc() as i64, Err(s) => return s, }; // radix let radix = match self.get_number(&args[1], cell) { Ok(f) => f.trunc() as i64, Err(s) => return s, }; // optional min_length let min_length = if arg_count == 3 { match self.get_number(&args[2], cell) { Ok(f) => { if f < 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Minimum length must be non-negative".to_string(), }; } f.trunc() as usize } Err(s) => return s, } } else { 0 }; if !(2..=36).contains(&radix) { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Radix must be between 2 and 36".to_string(), }; } // number must be >= 0 if value < 0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Number must be non-negative".to_string(), }; } let mut buf = String::new(); if value == 0 { buf.push('0'); } else { while value > 0 { let digit = (value % radix) as u8; let ch = match digit { 0..=9 => (b'0' + digit) as char, 10..=35 => (b'A' + (digit - 10)) as char, _ => unreachable!(), }; buf.push(ch); value /= radix; } // we built it in reverse buf = buf.chars().rev().collect(); } // pad with leading zeros if needed if buf.len() < min_length { let mut padded = String::with_capacity(min_length); for _ in 0..(min_length - buf.len()) { padded.push('0'); } padded.push_str(&buf); buf = padded; } CalcResult::String(buf) } pub(crate) fn fn_decimal(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let text = match self.get_string(&args[0], cell) { Ok(s) => s, Err(s) => return s, }; let radix = match self.get_number(&args[1], cell) { Ok(f) => f.trunc() as i32, Err(s) => return s, }; if !(2..=36).contains(&radix) { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Radix must be between 2 and 36".to_string(), }; } match i64::from_str_radix(&text, radix as u32) { Ok(n) => CalcResult::Number(n as f64), Err(_) => CalcResult::Error { error: Error::VALUE, origin: cell, message: format!("'{}' is not a valid number in base {}", text, radix), }, } } pub(crate) fn fn_gcd(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.is_empty() { return CalcResult::new_args_number_error(cell); } let mut acc: Option = None; let mut saw_number = false; let mut has_range = false; // Returns Some(CalcResult) if an error occurred let mut handle_number = |value: f64| -> Option { if !value.is_finite() { return Some(CalcResult::new_error( Error::VALUE, cell, "Non-finite number in GCD".to_string(), )); } let n = value.trunc() as i64; if n < 0 { return Some(CalcResult::new_error( Error::NUM, cell, "GCD only accepts non-negative integers".to_string(), )); } saw_number = true; acc = Some(match acc { Some(cur) => gcd_i64(cur, n), None => n, }); None }; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => { if let Some(res) = handle_number(value) { return res; } } CalcResult::Range { left, right } => { if left.sheet != right.sheet { return CalcResult::new_error( Error::VALUE, cell, "Ranges are in different sheets".to_string(), ); } has_range = true; 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 { for column in column1..=column2 { match self.evaluate_cell(CellReferenceIndex { sheet: left.sheet, row, column, }) { CalcResult::Number(value) => { if let Some(res) = handle_number(value) { return res; } } error @ CalcResult::Error { .. } => return error, _ => { // ignore strings / booleans } } } } } CalcResult::Array(array) => { for row in array { for value in row { match value { ArrayNode::Number(value) => { if let Some(res) = handle_number(value) { return res; } } ArrayNode::Error(error) => { return CalcResult::Error { error, origin: cell, message: "Error in array".to_string(), } } _ => { // ignore strings / booleans } } } } } error @ CalcResult::Error { .. } => return error, _ => { // ignore strings / booleans } } } if !saw_number && !has_range { return CalcResult::Error { error: Error::VALUE, origin: cell, message: "No valid numbers found".to_string(), }; } CalcResult::Number(acc.unwrap_or(0) as f64) } pub(crate) fn fn_lcm(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.is_empty() { return CalcResult::new_args_number_error(cell); } let mut acc: Option = None; let mut saw_number = false; let mut has_range = false; // Returns Some(CalcResult) if an error occurred let mut handle_number = |value: f64| -> Option { if !value.is_finite() { return Some(CalcResult::new_error( Error::VALUE, cell, "Non-finite number in LCM".to_string(), )); } let n = value.trunc() as i64; if n < 0 { return Some(CalcResult::new_error( Error::NUM, cell, "LCM only accepts non-negative integers".to_string(), )); } saw_number = true; acc = Some(match acc { Some(cur) => match lcm_i64(cur, n) { Some(v) => v, None => { return Some(CalcResult::new_error( Error::NUM, cell, "LCM result too large".to_string(), )); } }, None => n, }); None }; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => { if let Some(res) = handle_number(value) { return res; } } CalcResult::Range { left, right } => { if left.sheet != right.sheet { return CalcResult::new_error( Error::VALUE, cell, "Ranges are in different sheets".to_string(), ); } has_range = true; 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 { for column in column1..=column2 { match self.evaluate_cell(CellReferenceIndex { sheet: left.sheet, row, column, }) { CalcResult::Number(value) => { if let Some(res) = handle_number(value) { return res; } } error @ CalcResult::Error { .. } => return error, _ => { // ignore strings / booleans } } } } } CalcResult::Array(array) => { for row in array { for value in row { match value { ArrayNode::Number(value) => { if let Some(res) = handle_number(value) { return res; } } ArrayNode::Error(error) => { return CalcResult::Error { error, origin: cell, message: "Error in array".to_string(), } } _ => { // ignore strings / booleans } } } } } error @ CalcResult::Error { .. } => return error, _ => { // ignore strings / booleans } } } if !saw_number && !has_range { return CalcResult::Error { error: Error::VALUE, origin: cell, message: "No valid numbers found".to_string(), }; } CalcResult::Number(acc.unwrap_or(0) as f64) } pub(crate) fn fn_sum(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.is_empty() { return CalcResult::new_args_number_error(cell); } let mut result = 0.0; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => result += value, CalcResult::Range { left, right } => { if left.sheet != right.sheet { return CalcResult::new_error( Error::VALUE, cell, "Ranges are in different sheets".to_string(), ); } // TODO: We should do this for all functions that run through ranges // Running cargo test for the ironcalc takes around .8 seconds with this speedup // and ~ 3.5 seconds without it. Note that once properly in place sheet.dimension should be almost a noop 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) => { result += value; } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } } } } } CalcResult::Array(array) => { for row in array { for value in row { match value { ArrayNode::Number(value) => { result += value; } ArrayNode::Error(error) => { return CalcResult::Error { error, origin: cell, message: "Error in array".to_string(), } } _ => { // We ignore booleans and strings } } } } } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } }; } CalcResult::Number(result) } pub(crate) fn fn_sumsq(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.is_empty() { return CalcResult::new_args_number_error(cell); } let mut result = 0.0; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => result += value * value, CalcResult::Range { left, right } => { if left.sheet != right.sheet { return CalcResult::new_error( Error::VALUE, cell, "Ranges are in different sheets".to_string(), ); } // TODO: We should do this for all functions that run through ranges // Running cargo test for the ironcalc takes around .8 seconds with this speedup // and ~ 3.5 seconds without it. Note that once properly in place sheet.dimension should be almost a noop 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) => { result += value * value; } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } } } } } CalcResult::Array(array) => { for row in array { for value in row { match value { ArrayNode::Number(value) => { result += value * value; } ArrayNode::Error(error) => { return CalcResult::Error { error, origin: cell, message: "Error in array".to_string(), } } _ => { // We ignore booleans and strings } } } } } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } }; } CalcResult::Number(result) } pub(crate) fn fn_product(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.is_empty() { return CalcResult::new_args_number_error(cell); } let mut result = 1.0; let mut seen_value = false; for arg in args { match self.evaluate_node_in_context(arg, cell) { CalcResult::Number(value) => { seen_value = true; result *= 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) => { seen_value = true; result *= value; } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } } } } } error @ CalcResult::Error { .. } => return error, _ => { // We ignore booleans and strings } }; } if !seen_value { return CalcResult::Number(0.0); } CalcResult::Number(result) } /// SUMIF(criteria_range, criteria, [sum_range]) /// if sum_rage is missing then criteria_range will be used pub(crate) fn fn_sumif(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() == 2 { let arguments = vec![args[0].clone(), args[0].clone(), args[1].clone()]; self.fn_sumifs(&arguments, cell) } else if args.len() == 3 { let arguments = vec![args[2].clone(), args[0].clone(), args[1].clone()]; self.fn_sumifs(&arguments, cell) } else { CalcResult::new_args_number_error(cell) } } /// SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) pub(crate) fn fn_sumifs(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let mut total = 0.0; let sum = |value| total += value; if let Err(e) = self.apply_ifs(args, cell, sum) { return e; } CalcResult::Number(total) } pub(crate) fn fn_round(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { // Incorrect number of arguments return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => to_precision(f, 15), Err(s) => return s, }; let number_of_digits = match self.get_number(&args[1], cell) { Ok(f) => { if f > 0.0 { f.floor() } else { f.ceil() } } Err(s) => return s, }; let scale = 10.0_f64.powf(number_of_digits); CalcResult::Number((value * scale).round() / scale) } pub(crate) fn fn_roundup(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => to_precision(f, 15), Err(s) => return s, }; let number_of_digits = match self.get_number(&args[1], cell) { Ok(f) => { if f > 0.0 { f.floor() } else { f.ceil() } } Err(s) => return s, }; let scale = 10.0_f64.powf(number_of_digits); if value > 0.0 { CalcResult::Number((value * scale).ceil() / scale) } else { CalcResult::Number((value * scale).floor() / scale) } } pub(crate) fn fn_rounddown(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => to_precision(f, 15), Err(s) => return s, }; let number_of_digits = match self.get_number(&args[1], cell) { Ok(f) => { if f > 0.0 { f.floor() } else { f.ceil() } } Err(s) => return s, }; let scale = 10.0_f64.powf(number_of_digits); if value > 0.0 { CalcResult::Number((value * scale).floor() / scale) } else { CalcResult::Number((value * scale).ceil() / scale) } } // (number, divisor) pub(crate) fn fn_mod(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let divisor = match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, }; if divisor == 0.0 { return CalcResult::Error { error: Error::DIV, origin: cell, message: "Divide by 0".to_string(), }; } let result = value - divisor * (value / divisor).floor(); CalcResult::Number(result) } pub(crate) fn fn_quotient(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number_no_bools(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let divisor = match self.get_number_no_bools(&args[1], cell) { Ok(f) => f, Err(s) => return s, }; if divisor == 0.0 { return CalcResult::Error { error: Error::DIV, origin: cell, message: "Divide by 0".to_string(), }; } let result = value / divisor; CalcResult::Number(result.signum() * result.abs().floor()) } pub(crate) fn fn_floor(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let significance = match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, }; if significance == 0.0 { if value == 0.0 { return CalcResult::Number(0.0); } return CalcResult::Error { error: Error::DIV, origin: cell, message: "Divide by 0".to_string(), }; } if significance < 0.0 && value > 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Significance must be positive when value is positive".to_string(), }; } let result = f64::floor(value / significance) * significance; CalcResult::Number(result) } pub(crate) fn fn_ceiling(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let significance = match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, }; if significance == 0.0 { // This behaviour is different from FLOOR where division by zero returns an error return CalcResult::Number(0.0); } if significance < 0.0 && value > 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Significance must be positive when value is positive".to_string(), }; } let result = f64::ceil(value / significance) * significance; CalcResult::Number(result) } pub(crate) fn fn_ceiling_math( &mut self, args: &[Node], cell: CellReferenceIndex, ) -> CalcResult { let arg_count = args.len(); if arg_count > 3 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let significance = if arg_count > 1 { match self.get_number(&args[1], cell) { Ok(f) => f.abs(), Err(s) => return s, } } else { 1.0 }; let mode = if arg_count > 2 { match self.get_number(&args[2], cell) { Ok(f) => f, Err(s) => return s, } } else { 0.0 }; if significance == 0.0 { return CalcResult::Number(0.0); } if value < 0.0 && mode != 0.0 { let result = f64::floor(value / significance) * significance; CalcResult::Number(result) } else { let result = f64::ceil(value / significance) * significance; CalcResult::Number(result) } } pub(crate) fn fn_ceiling_precise( &mut self, args: &[Node], cell: CellReferenceIndex, ) -> CalcResult { let arg_count = args.len(); if arg_count > 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let significance = if arg_count > 1 { match self.get_number(&args[1], cell) { Ok(f) => f.abs(), Err(s) => return s, } } else { 1.0 }; if significance == 0.0 { return CalcResult::Number(0.0); } let result = f64::ceil(value / significance) * significance; CalcResult::Number(result) } pub(crate) fn fn_iso_ceiling(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { // ISO.CEILING is equivalent to CEILING.PRECISE self.fn_ceiling_precise(args, cell) } pub(crate) fn fn_floor_math(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let arg_count = args.len(); if arg_count > 3 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let significance = if arg_count > 1 { match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, } } else { 1.0 }; let mode = if arg_count > 2 { match self.get_number(&args[2], cell) { Ok(f) => f, Err(s) => return s, } } else { 0.0 }; if significance == 0.0 { return CalcResult::Number(0.0); } let significance = significance.abs(); if value < 0.0 && mode != 0.0 { let result = f64::ceil(value / significance) * significance; CalcResult::Number(result) } else { let result = f64::floor(value / significance) * significance; CalcResult::Number(result) } } pub(crate) fn fn_floor_precise( &mut self, args: &[Node], cell: CellReferenceIndex, ) -> CalcResult { let arg_count = args.len(); if arg_count > 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let significance = if arg_count > 1 { match self.get_number(&args[1], cell) { Ok(f) => f.abs(), Err(s) => return s, } } else { 1.0 }; if significance == 0.0 { return CalcResult::Number(0.0); } let result = f64::floor(value / significance) * significance; CalcResult::Number(result) } pub(crate) fn fn_mround(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { // MROUND(number, multiple) if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let value = self.evaluate_node_in_context(&args[0], cell); let multiple = self.evaluate_node_in_context(&args[1], cell); // if either is empty => #N/A if matches!(value, CalcResult::EmptyArg) || matches!(multiple, CalcResult::EmptyArg) { return CalcResult::Error { error: Error::NA, origin: cell, message: "Bad argument for MROUND".to_string(), }; } // Booleans are not cast if matches!(value, CalcResult::Boolean(_)) { return CalcResult::new_error(Error::VALUE, cell, "Expecting number".to_string()); } if matches!(multiple, CalcResult::Boolean(_)) { return CalcResult::new_error(Error::VALUE, cell, "Expecting number".to_string()); } let value = match self.cast_to_number(value, cell) { Ok(f) => f, Err(s) => return s, }; let multiple = match self.cast_to_number(multiple, cell) { Ok(f) => f, Err(s) => return s, }; if multiple == 0.0 { return CalcResult::Number(0.0); } if (value > 0.0 && multiple < 0.0) || (value < 0.0 && multiple > 0.0) { return CalcResult::Error { error: Error::NUM, origin: cell, message: "number and multiple must have the same sign".to_string(), }; } let result = (value / multiple).round() * multiple; CalcResult::Number(result) } pub(crate) fn fn_trunc(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() > 2 { return CalcResult::new_args_number_error(cell); } let value = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let num_digits = if args.len() == 2 { match self.get_number(&args[1], cell) { Ok(f) => { if f > 0.0 { f.floor() } else { f.ceil() } } Err(s) => return s, } } else { 0.0 }; if !(-15.0..=15.0).contains(&num_digits) { return CalcResult::Number(value); } let v = if value >= 0.0 { f64::floor(value * 10f64.powf(num_digits)) / 10f64.powf(num_digits) } else { f64::ceil(value * 10f64.powf(num_digits)) / 10f64.powf(num_digits) }; if value.is_finite() && v.is_infinite() { return CalcResult::Number(value); } CalcResult::Number(v) } single_number_fn!(fn_log10, |f| if f <= 0.0 { Err(Error::NUM) } else { Ok(f64::log10(f)) }); single_number_fn!(fn_ln, |f| if f <= 0.0 { Err(Error::NUM) } else { Ok(f64::ln(f)) }); single_number_fn!(fn_sin, |f| Ok(f64::sin(f))); single_number_fn!(fn_cos, |f| Ok(f64::cos(f))); single_number_fn!(fn_tan, |f| Ok(f64::tan(f))); single_number_fn!(fn_sinh, |f| Ok(f64::sinh(f))); single_number_fn!(fn_cosh, |f| Ok(f64::cosh(f))); single_number_fn!(fn_tanh, |f| Ok(f64::tanh(f))); single_number_fn!(fn_asin, |f| Ok(f64::asin(f))); single_number_fn!(fn_acos, |f| Ok(f64::acos(f))); single_number_fn!(fn_atan, |f| Ok(f64::atan(f))); single_number_fn!(fn_asinh, |f| Ok(f64::asinh(f))); single_number_fn!(fn_acosh, |f| Ok(f64::acosh(f))); single_number_fn!(fn_atanh, |f| Ok(f64::atanh(f))); single_number_fn!(fn_abs, |f| Ok(f64::abs(f))); single_number_fn!(fn_sqrt, |f| if f < 0.0 { Err(Error::NUM) } else { Ok(f64::sqrt(f)) }); single_number_fn!(fn_sqrtpi, |f: f64| if f < 0.0 { Err(Error::NUM) } else { Ok((f * PI).sqrt()) }); single_number_fn!(fn_acot, |f| { let v = f64::atan(1.0 / f); if f >= 0.0 { Ok(v) } else { // To be compatible with Excel we need a different branch // when f < 0 Ok(v + PI) } }); single_number_fn!(fn_acoth, |f: f64| if f.abs() == 1.0 { Err(Error::NUM) } else { Ok(0.5 * (f64::ln((f + 1.0) / (f - 1.0)))) }); single_number_fn!(fn_cot, |f| if f == 0.0 { Err(Error::DIV) } else { Ok(f64::cos(f) / f64::sin(f)) }); single_number_fn!(fn_coth, |f: f64| if f == 0.0 { Err(Error::DIV) } else if f.abs() > 20.0 { // for values > 20.0 this is exact in f64 Ok(f.signum()) } else { Ok(f64::cosh(f) / f64::sinh(f)) }); single_number_fn!(fn_csc, |f| if f == 0.0 { Err(Error::DIV) } else { Ok(1.0 / f64::sin(f)) }); single_number_fn!(fn_csch, |f| if f == 0.0 { Err(Error::DIV) } else { Ok(1.0 / f64::sinh(f)) }); single_number_fn!(fn_sec, |f| Ok(1.0 / f64::cos(f))); single_number_fn!(fn_sech, |f| Ok(1.0 / f64::cosh(f))); single_number_fn!(fn_exp, |f: f64| Ok(f64::exp(f))); single_number_fn!(fn_fact, |x: f64| { let x = x.floor(); if x < 0.0 { return Err(Error::NUM); } let mut acc = 1.0; let mut k = 2.0; while k <= x { acc *= k; k += 1.0; } Ok(acc) }); single_number_fn!(fn_factdouble, |x: f64| { let x = x.floor(); if x < -1.0 { return Err(Error::NUM); } if x < 0.0 { return Ok(1.0); } let mut acc = 1.0; let mut k = if x % 2.0 == 0.0 { 2.0 } else { 1.0 }; while k <= x { acc *= k; k += 2.0; } Ok(acc) }); single_number_fn!(fn_sign, |f| { if f == 0.0 { Ok(0.0) } else { Ok(f64::signum(f)) } }); single_number_fn!(fn_degrees, |f| Ok(f * (180.0 / PI))); single_number_fn!(fn_radians, |f| Ok(f * (PI / 180.0))); single_number_fn!(fn_odd, |f| { let sign = f64::signum(f); Ok(sign * (f64::ceil((f64::abs(f) - 1.0) / 2.0) * 2.0 + 1.0)) }); single_number_fn!(fn_even, |f| Ok(f64::signum(f) * f64::ceil(f64::abs(f) / 2.0) * 2.0)); single_number_fn!(fn_int, |f| Ok(f64::floor(f))); pub(crate) fn fn_pi(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if !args.is_empty() { return CalcResult::new_args_number_error(cell); } CalcResult::Number(PI) } pub(crate) fn fn_atan2(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let x = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let y = match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, }; if x == 0.0 && y == 0.0 { return CalcResult::Error { error: Error::DIV, origin: cell, message: "Arguments can't be both zero".to_string(), }; } CalcResult::Number(f64::atan2(y, x)) } pub(crate) fn fn_log(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let n_args = args.len(); if !(1..=2).contains(&n_args) { return CalcResult::new_args_number_error(cell); } let x = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let y = if n_args == 1 { 10.0 } else { match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, } }; if x <= 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Number must be positive".to_string(), }; } if y == 1.0 { return CalcResult::Error { error: Error::DIV, origin: cell, message: "Logarithm base cannot be 1".to_string(), }; } if y <= 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Logarithm base must be positive".to_string(), }; } CalcResult::Number(f64::log(x, y)) } pub(crate) fn fn_power(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let x = match self.get_number(&args[0], cell) { Ok(f) => f, Err(s) => return s, }; let y = match self.get_number(&args[1], cell) { Ok(f) => f, Err(s) => return s, }; if x == 0.0 && y == 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Arguments can't be both zero".to_string(), }; } if y == 0.0 { return CalcResult::Number(1.0); } let result = x.powf(y); if result.is_infinite() { return CalcResult::Error { error: Error::DIV, origin: cell, message: "POWER returned infinity".to_string(), }; } if result.is_nan() { // This might happen for some combinations of negative base and exponent return CalcResult::Error { error: Error::NUM, origin: cell, message: "Invalid arguments for POWER".to_string(), }; } CalcResult::Number(result) } pub(crate) fn fn_rand(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if !args.is_empty() { return CalcResult::new_args_number_error(cell); } CalcResult::Number(random()) } // TODO: Add tests for RANDBETWEEN pub(crate) fn fn_randbetween(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let x = match self.get_number(&args[0], cell) { Ok(f) => f.floor(), Err(s) => return s, }; let y = match self.get_number(&args[1], cell) { Ok(f) => f.ceil() + 1.0, Err(s) => return s, }; if x > y { return CalcResult::Error { error: Error::NUM, origin: cell, message: format!("{x}>{y}"), }; } CalcResult::Number((x + random() * (y - x)).floor()) } pub(crate) fn fn_roman(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.is_empty() || args.len() > 2 { return CalcResult::new_args_number_error(cell); } let number = match self.get_number(&args[0], cell) { Ok(f) => f.floor(), Err(s) => return s, }; if number == 0.0 { return CalcResult::String(String::new()); } if !(0.0..=3999.0).contains(&number) { return CalcResult::Error { error: Error::VALUE, origin: cell, message: "Number must be between 0 and 3999".to_string(), }; } let form = if args.len() == 2 { let mut t = match self.get_number(&args[1], cell) { Ok(f) => f as i32, Err(s) => return s, }; // If the value is a boolean TRUE/FALSE, convert to 0/4 if t == 0 || t == 1 { if let CalcResult::Boolean(b) = self.evaluate_node_in_context(&args[1], cell) { if b { // classic form t = 0; } else { // simplified form t = 4; } } } t } else { 0 }; if !(0..=4).contains(&form) { return CalcResult::Error { error: Error::VALUE, origin: cell, message: "Form must be between 0 and 4".to_string(), }; } let roman_numeral = match to_roman_with_form(number as u32, form) { Ok(s) => s, Err(e) => { return CalcResult::Error { error: Error::VALUE, origin: cell, message: format!("Could not convert to Roman numeral: {e}"), } } }; CalcResult::String(roman_numeral) } pub(crate) fn fn_arabic(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 1 { return CalcResult::new_args_number_error(cell); } let roman_numeral = match self.evaluate_node_in_context(&args[0], cell) { CalcResult::String(s) => s, error @ CalcResult::Error { .. } => return error, _ => { return CalcResult::Error { error: Error::VALUE, origin: cell, message: "Argument must be a text string".to_string(), } } }; if roman_numeral.is_empty() { return CalcResult::Number(0.0); } match from_roman(&roman_numeral) { Ok(value) => CalcResult::Number(value as f64), Err(e) => CalcResult::Error { error: Error::VALUE, origin: cell, message: format!("Invalid Roman numeral: {e}"), }, } } pub(crate) fn fn_combin(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let n = match self.get_number(&args[0], cell) { Ok(f) => f.floor(), Err(s) => return s, }; let k = match self.get_number(&args[1], cell) { Ok(f) => f.floor(), Err(s) => return s, }; if n < 0.0 || k < 0.0 { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Arguments must be non-negative integers".to_string(), }; } if k > n { return CalcResult::Error { error: Error::NUM, origin: cell, message: "k cannot be greater than n".to_string(), }; } let k = k as usize; let mut result = 1.0; for i in 0..k { let t = i as f64; result *= (n - t) / (t + 1.0); } CalcResult::Number(result) } pub(crate) fn fn_combina(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() != 2 { return CalcResult::new_args_number_error(cell); } let n = match self.get_number(&args[0], cell) { Ok(f) => f.floor(), Err(s) => return s, }; let k = match self.get_number(&args[1], cell) { Ok(f) => f.floor(), Err(s) => return s, }; if n < 0.0 || k < 0.0 || (n == 0.0 && k > 0.0) { return CalcResult::Error { error: Error::NUM, origin: cell, message: "Arguments must be non-negative integers".to_string(), }; } let k = k as usize; let mut result = 1.0; for i in 0..k { let t = i as f64; result *= (n + t) / (t + 1.0); } CalcResult::Number(result) } }