diff --git a/base/src/expressions/parser/static_analysis.rs b/base/src/expressions/parser/static_analysis.rs index c198a47..a2af299 100644 --- a/base/src/expressions/parser/static_analysis.rs +++ b/base/src/expressions/parser/static_analysis.rs @@ -848,6 +848,20 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec args_signature_scalars(arg_count, 1, 0), Function::Radians => args_signature_scalars(arg_count, 1, 0), Function::Degrees => args_signature_scalars(arg_count, 1, 0), + Function::Int => args_signature_scalars(arg_count, 1, 0), + Function::Even => args_signature_scalars(arg_count, 1, 0), + Function::Odd => args_signature_scalars(arg_count, 1, 0), + Function::Ceiling => args_signature_scalars(arg_count, 2, 0), // (number, significance) + Function::CeilingMath => args_signature_scalars(arg_count, 1, 2), // (number, [significance], [mode]) + Function::CeilingPrecise => args_signature_scalars(arg_count, 1, 1), // (number, [significance]) + Function::Floor => args_signature_scalars(arg_count, 2, 0), // (number, significance) + Function::FloorMath => args_signature_scalars(arg_count, 1, 2), // (number, [significance], [mode]) + Function::FloorPrecise => args_signature_scalars(arg_count, 1, 1), // (number, [significance]) + Function::IsoCeiling => args_signature_scalars(arg_count, 1, 1), // (number, [significance]) + Function::Mod => args_signature_scalars(arg_count, 2, 0), // (number, divisor) + Function::Quotient => args_signature_scalars(arg_count, 2, 0), // (number, denominator) + Function::Mround => args_signature_scalars(arg_count, 2, 0), // (number, multiple) + Function::Trunc => args_signature_scalars(arg_count, 1, 1), // (num, [num_digits]) } } @@ -1084,5 +1098,19 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult { Function::Sign => scalar_arguments(args), Function::Radians => scalar_arguments(args), Function::Degrees => scalar_arguments(args), + Function::Int => scalar_arguments(args), + Function::Even => scalar_arguments(args), + Function::Odd => scalar_arguments(args), + Function::Ceiling => scalar_arguments(args), + Function::CeilingMath => scalar_arguments(args), + Function::CeilingPrecise => scalar_arguments(args), + Function::Floor => scalar_arguments(args), + Function::FloorMath => scalar_arguments(args), + Function::FloorPrecise => scalar_arguments(args), + Function::IsoCeiling => scalar_arguments(args), + Function::Mod => scalar_arguments(args), + Function::Quotient => scalar_arguments(args), + Function::Mround => scalar_arguments(args), + Function::Trunc => scalar_arguments(args), } } diff --git a/base/src/functions/mathematical.rs b/base/src/functions/mathematical.rs index 6fee9c1..af1b066 100644 --- a/base/src/functions/mathematical.rs +++ b/base/src/functions/mathematical.rs @@ -381,6 +381,317 @@ impl Model { } } + // (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(&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; + 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 = match self.get_number(&args[0], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let multiple = match self.get_number(&args[1], 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); + } + CalcResult::Number(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) + }) + } + single_number_fn!(fn_log10, |f| if f <= 0.0 { Err(Error::NUM) } else { @@ -487,6 +798,14 @@ impl Model { single_number_fn!(fn_sign, |f| 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() { diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index 79e3432..f65dadc 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -93,6 +93,21 @@ pub enum Function { Radians, Degrees, + Int, + Even, + Odd, + Ceiling, + CeilingMath, + CeilingPrecise, + Floor, + FloorMath, + FloorPrecise, + IsoCeiling, + Mod, + Quotient, + Mround, + Trunc, + // Information ErrorType, Formulatext, @@ -286,7 +301,7 @@ pub enum Function { } impl Function { - pub fn into_iter() -> IntoIter { + pub fn into_iter() -> IntoIter { [ Function::And, Function::False, @@ -332,6 +347,20 @@ impl Function { Function::Fact, Function::Factdouble, Function::Sign, + Function::Int, + Function::Even, + Function::Odd, + Function::Ceiling, + Function::CeilingMath, + Function::CeilingPrecise, + Function::Floor, + Function::FloorMath, + Function::FloorPrecise, + Function::IsoCeiling, + Function::Mod, + Function::Quotient, + Function::Mround, + Function::Trunc, Function::Max, Function::Min, Function::Product, @@ -559,6 +588,14 @@ impl Function { Function::Sheet => "_xlfn.SHEET".to_string(), Function::Formulatext => "_xlfn.FORMULATEXT".to_string(), Function::Isoweeknum => "_xlfn.ISOWEEKNUM".to_string(), + + Function::Ceiling => "_xlfn.CEILING".to_string(), + Function::CeilingMath => "_xlfn.CEILING.MATH".to_string(), + Function::CeilingPrecise => "_xlfn.CEILING.PRECISE".to_string(), + Function::FloorMath => "_xlfn.FLOOR.MATH".to_string(), + Function::FloorPrecise => "_xlfn.FLOOR.PRECISE".to_string(), + Function::IsoCeiling => "_xlfn.ISO.CEILING".to_string(), + _ => self.to_string(), } } @@ -615,6 +652,21 @@ impl Function { "RADIANS" => Some(Function::Radians), "DEGREES" => Some(Function::Degrees), + "INT" => Some(Function::Int), + "EVEN" => Some(Function::Even), + "ODD" => Some(Function::Odd), + "CEILING" | "_XLFN.CEILING" => Some(Function::Ceiling), + "CEILING.MATH" | "_XLFN.CEILING.MATH" => Some(Function::CeilingMath), + "CEILING.PRECISE" | "_XLFN.CEILING.PRECISE" => Some(Function::CeilingPrecise), + "FLOOR" => Some(Function::Floor), + "FLOOR.MATH" | "_XLFN.FLOOR.MATH" => Some(Function::FloorMath), + "FLOOR.PRECISE" | "_XLFN.FLOOR.PRECISE" => Some(Function::FloorPrecise), + "ISO.CEILING" | "_XLFN.ISO.CEILING" => Some(Function::IsoCeiling), + "MOD" => Some(Function::Mod), + "QUOTIENT" => Some(Function::Quotient), + "MROUND" => Some(Function::Mround), + "TRUNC" => Some(Function::Trunc), + "PI" => Some(Function::Pi), "ABS" => Some(Function::Abs), "SQRT" => Some(Function::Sqrt), @@ -824,6 +876,7 @@ impl Function { "GESTEP" => Some(Function::Gestep), "SUBTOTAL" => Some(Function::Subtotal), + _ => None, } } @@ -1061,6 +1114,20 @@ impl fmt::Display for Function { Function::Sign => write!(f, "SIGN"), Function::Radians => write!(f, "RADIANS"), Function::Degrees => write!(f, "DEGREES"), + Function::Int => write!(f, "INT"), + Function::Even => write!(f, "EVEN"), + Function::Odd => write!(f, "ODD"), + Function::Ceiling => write!(f, "CEILING"), + Function::CeilingMath => write!(f, "CEILING.MATH"), + Function::CeilingPrecise => write!(f, "CEILING.PRECISE"), + Function::Floor => write!(f, "FLOOR"), + Function::FloorMath => write!(f, "FLOOR.MATH"), + Function::FloorPrecise => write!(f, "FLOOR.PRECISE"), + Function::IsoCeiling => write!(f, "ISO.CEILING"), + Function::Mod => write!(f, "MOD"), + Function::Quotient => write!(f, "QUOTIENT"), + Function::Mround => write!(f, "MROUND"), + Function::Trunc => write!(f, "TRUNC"), } } } @@ -1318,6 +1385,20 @@ impl Model { Function::Sign => self.fn_sign(args, cell), Function::Radians => self.fn_radians(args, cell), Function::Degrees => self.fn_degrees(args, cell), + Function::Int => self.fn_int(args, cell), + Function::Even => self.fn_even(args, cell), + Function::Odd => self.fn_odd(args, cell), + Function::Ceiling => self.fn_ceiling(args, cell), + Function::CeilingMath => self.fn_ceiling_math(args, cell), + Function::CeilingPrecise => self.fn_ceiling_precise(args, cell), + Function::Floor => self.fn_floor(args, cell), + Function::FloorMath => self.fn_floor_math(args, cell), + Function::FloorPrecise => self.fn_floor_precise(args, cell), + Function::IsoCeiling => self.fn_iso_ceiling(args, cell), + Function::Mod => self.fn_mod(args, cell), + Function::Quotient => self.fn_quotient(args, cell), + Function::Mround => self.fn_mround(args, cell), + Function::Trunc => self.fn_trunc(args, cell), } } } diff --git a/xlsx/tests/calc_tests/RADIANS_DEGREES.xlsx b/xlsx/tests/calc_tests/RADIANS_DEGREES.xlsx deleted file mode 100644 index 6c8ac84..0000000 Binary files a/xlsx/tests/calc_tests/RADIANS_DEGREES.xlsx and /dev/null differ diff --git a/xlsx/tests/calc_tests/RADIANS_DEGREES_and_mathematical.xlsx b/xlsx/tests/calc_tests/RADIANS_DEGREES_and_mathematical.xlsx new file mode 100644 index 0000000..7c92faa Binary files /dev/null and b/xlsx/tests/calc_tests/RADIANS_DEGREES_and_mathematical.xlsx differ