UPDATE: Adds COMBIN, COMBINA and SUMSQ (#511)

This commit is contained in:
Nicolás Hatcher Andrés
2025-11-04 22:16:16 +01:00
committed by GitHub
parent e5854ab3d7
commit 68a33a5f87
5 changed files with 181 additions and 1 deletions

View File

@@ -868,6 +868,9 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec<Signatu
Function::Decimal => args_signature_scalars(arg_count, 2, 0),
Function::Roman => args_signature_scalars(arg_count, 1, 1),
Function::Arabic => args_signature_scalars(arg_count, 1, 0),
Function::Combin => args_signature_scalars(arg_count, 2, 0),
Function::Combina => args_signature_scalars(arg_count, 2, 0),
Function::Sumsq => vec![Signature::Vector; arg_count],
}
}
@@ -1124,5 +1127,8 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult {
Function::Decimal => scalar_arguments(args),
Function::Roman => scalar_arguments(args),
Function::Arabic => scalar_arguments(args),
Function::Combin => scalar_arguments(args),
Function::Combina => scalar_arguments(args),
Function::Sumsq => StaticResult::Scalar,
}
}

View File

@@ -633,6 +633,101 @@ impl Model {
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);
@@ -1465,4 +1560,67 @@ impl Model {
},
}
}
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)
}
}

View File

@@ -111,6 +111,9 @@ pub enum Function {
Decimal,
Roman,
Arabic,
Combin,
Combina,
Sumsq,
// Information
ErrorType,
@@ -305,7 +308,7 @@ pub enum Function {
}
impl Function {
pub fn into_iter() -> IntoIter<Function, 249> {
pub fn into_iter() -> IntoIter<Function, 252> {
[
Function::And,
Function::False,
@@ -556,6 +559,9 @@ impl Function {
Function::Subtotal,
Function::Roman,
Function::Arabic,
Function::Combin,
Function::Combina,
Function::Sumsq,
]
.into_iter()
}
@@ -607,6 +613,7 @@ impl Function {
Function::Base => "_xlfn.BASE".to_string(),
Function::Decimal => "_xlfn.DECIMAL".to_string(),
Function::Arabic => "_xlfn.ARABIC".to_string(),
Function::Combina => "_xlfn.COMBINA".to_string(),
_ => self.to_string(),
}
@@ -696,6 +703,9 @@ impl Function {
"SUM" => Some(Function::Sum),
"SUMIF" => Some(Function::Sumif),
"SUMIFS" => Some(Function::Sumifs),
"COMBIN" => Some(Function::Combin),
"COMBINA" | "_XLFN.COMBINA" => Some(Function::Combina),
"SUMSQ" => Some(Function::Sumsq),
// Lookup and Reference
"CHOOSE" => Some(Function::Choose),
@@ -1141,6 +1151,9 @@ impl fmt::Display for Function {
Function::Decimal => write!(f, "DECIMAL"),
Function::Roman => write!(f, "ROMAN"),
Function::Arabic => write!(f, "ARABIC"),
Function::Combin => write!(f, "COMBIN"),
Function::Combina => write!(f, "COMBINA"),
Function::Sumsq => write!(f, "SUMSQ"),
}
}
}
@@ -1418,6 +1431,9 @@ impl Model {
Function::Decimal => self.fn_decimal(args, cell),
Function::Roman => self.fn_roman(args, cell),
Function::Arabic => self.fn_arabic(args, cell),
Function::Combin => self.fn_combin(args, cell),
Function::Combina => self.fn_combina(args, cell),
Function::Sumsq => self.fn_sumsq(args, cell),
}
}
}

Binary file not shown.

Binary file not shown.