UPDATE: Dump of initial files

This commit is contained in:
Nicolás Hatcher
2023-11-18 21:26:18 +01:00
commit c5b8efd83d
279 changed files with 42654 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
use std::cmp::Ordering;
use crate::{
calc_result::{CalcResult, CellReference},
model::Model,
};
use super::util::compare_values;
// NOTE: We don't know how Excel exactly implements binary search internally.
// This means that if the values on the lookup range are not in order our results and Excel's will differ
// Assumes values are in ascending order, returns matching index or the largest value smaller than target.
// Returns None if target is smaller than the smaller value.
pub(crate) fn binary_search_or_smaller<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
// We apply binary search leftmost for value in the range
let n = array.len();
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
if &array[m] < target {
l = m + 1;
} else {
r = m;
}
}
if l == n {
return Some((l - 1) as i32);
}
// Now l points to the leftmost element
if &array[l] == target {
return Some(l as i32);
}
// If target is less than the minimum return None
if l == 0 {
return None;
}
Some((l - 1) as i32)
}
// Assumes values are in ascending order, returns matching index or the smaller value larger than target.
// Returns None if target is smaller than the smaller value.
pub(crate) fn binary_search_or_greater<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
let mut l = 0;
let mut r = array.len();
while l < r {
let mut m = (l + r) / 2;
match &array[m].cmp(target) {
Ordering::Less => {
l = m + 1;
}
Ordering::Greater => {
r = m;
}
Ordering::Equal => {
while m > 1 {
if &array[m - 1] == target {
m -= 1;
} else {
break;
}
}
return Some(m as i32);
}
}
}
// If target is larger than the maximum return None
if r == array.len() {
return None;
}
// Now r points to the rightmost element
Some(r as i32)
}
// Assumes values are in descending order
pub(crate) fn binary_search_descending_or_smaller<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
let n = array.len();
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let mut index = n - m - 1;
match &array[index].cmp(target) {
Ordering::Less => {
l = m + 1;
}
Ordering::Greater => {
r = m;
}
Ordering::Equal => {
while index < n - 1 {
if &array[index + 1] == target {
index += 1;
} else {
break;
}
}
return Some(index as i32);
}
}
}
if l == 0 {
return None;
}
Some((n - l) as i32)
}
// Assumes values are in descending order, returns matching index or the smaller value larger than target.
// Returns None if target is smaller than the smaller value.
pub(crate) fn binary_search_descending_or_greater<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
let n = array.len();
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let mut index = n - m - 1;
match &array[index].cmp(target) {
Ordering::Less => {
l = m + 1;
}
Ordering::Greater => {
r = m;
}
Ordering::Equal => {
while index < n - 1 {
if &array[index + 1] == target {
index += 1;
} else {
break;
}
}
return Some(index as i32);
}
}
}
if r == n {
return None;
}
Some((n - r - 1) as i32)
}
impl Model {
/// Returns an array with the list of cell values in the range
pub(crate) fn prepare_array(
&mut self,
left: &CellReference,
right: &CellReference,
is_row_vector: bool,
) -> Vec<CalcResult> {
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let mut result = vec![];
for index in 0..n {
let row;
let column;
if is_row_vector {
row = left.row + index;
column = left.column;
} else {
column = left.column + index;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
result.push(value);
}
result
}
/// Old style binary search. Used in HLOOKUP, etc
pub(crate) fn binary_search(
&mut self,
target: &CalcResult,
left: &CellReference,
right: &CellReference,
is_row_vector: bool,
) -> i32 {
let array = self.prepare_array(left, right, is_row_vector);
// We apply binary search leftmost for value in the range
let mut l = 0;
let mut r = array.len();
while l < r {
let m = (l + r) / 2;
match compare_values(&array[m], target) {
-1 => {
l = m + 1;
}
1 => {
r = m;
}
_ => {
return m as i32;
}
}
}
// If target is less than the minimum return #N/A
if l == 0 {
return -2;
}
// Now l points to the leftmost element
(l - 1) as i32
}
}

View File

@@ -0,0 +1,314 @@
use chrono::Datelike;
use chrono::Months;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use chrono::Timelike;
use crate::formatter::dates::date_to_serial_number;
use crate::model::get_milliseconds_since_epoch;
use crate::{
calc_result::{CalcResult, CellReference},
constants::EXCEL_DATE_BASE,
expressions::parser::Node,
expressions::token::Error,
formatter::dates::from_excel_date,
model::Model,
};
impl Model {
pub(crate) fn fn_day(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 1 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function DAY parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let date = from_excel_date(serial_number);
let day = date.day() as f64;
CalcResult::Number(day)
}
pub(crate) fn fn_month(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 1 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function MONTH parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let date = from_excel_date(serial_number);
let month = date.month() as f64;
CalcResult::Number(month)
}
pub(crate) fn fn_eomonth(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 2 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function EOMONTH parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let months = match self.get_number_no_bools(&args[1], cell) {
Ok(c) => {
let t = c.trunc();
t as i32
}
Err(s) => return s,
};
let months_abs = months.unsigned_abs();
let native_date = if months > 0 {
from_excel_date(serial_number) + Months::new(months_abs)
} else {
from_excel_date(serial_number) - Months::new(months_abs)
};
// Instead of calculating the end of month we compute the first day of the following month
// and take one day.
let mut month = native_date.month() + 1;
let mut year = native_date.year();
if month == 13 {
month = 1;
year += 1;
}
match date_to_serial_number(1, month, year) {
Ok(serial_number) => CalcResult::Number(serial_number as f64 - 1.0),
Err(message) => CalcResult::Error {
error: Error::NUM,
origin: cell,
message,
},
}
}
// year, month, day
pub(crate) fn fn_date(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 3 {
return CalcResult::new_args_number_error(cell);
}
let year = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i32;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Out of range parameters for date".to_string(),
};
}
t
}
Err(s) => return s,
};
let month = match self.get_number(&args[1], cell) {
Ok(c) => {
let t = c.floor();
if t < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Out of range parameters for date".to_string(),
};
}
t as u32
}
Err(s) => return s,
};
let day = match self.get_number(&args[2], cell) {
Ok(c) => {
let t = c.floor();
if t < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Out of range parameters for date".to_string(),
};
}
t as u32
}
Err(s) => return s,
};
match date_to_serial_number(day, month, year) {
Ok(serial_number) => CalcResult::Number(serial_number as f64),
Err(message) => CalcResult::Error {
error: Error::NUM,
origin: cell,
message,
},
}
}
pub(crate) fn fn_year(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 1 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function YEAR parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let date = from_excel_date(serial_number);
let year = date.year() as f64;
CalcResult::Number(year)
}
// date, months
pub(crate) fn fn_edate(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 2 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Parameter 1 value is negative. It should be positive or zero."
.to_string(),
};
}
t
}
Err(s) => return s,
};
let months = match self.get_number(&args[1], cell) {
Ok(c) => {
let t = c.trunc();
t as i32
}
Err(s) => return s,
};
let months_abs = months.unsigned_abs();
let native_date = if months > 0 {
from_excel_date(serial_number) + Months::new(months_abs)
} else {
from_excel_date(serial_number) - Months::new(months_abs)
};
let serial_number = native_date.num_days_from_ce() - EXCEL_DATE_BASE;
if serial_number < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "EDATE out of bounds".to_string(),
};
}
CalcResult::Number(serial_number as f64)
}
pub(crate) fn fn_today(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 0 {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Wrong number of arguments".to_string(),
};
}
// milliseconds since January 1, 1970 00:00:00 UTC.
let milliseconds = get_milliseconds_since_epoch();
let seconds = milliseconds / 1000;
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
Some(dt) => dt,
None => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Invalid date".to_string(),
}
}
};
let local_time = self.tz.from_utc_datetime(&dt);
// 693_594 is computed as:
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
// The 2 days offset is because of Excel 1900 bug
let days_from_1900 = local_time.num_days_from_ce() - 693_594;
CalcResult::Number(days_from_1900 as f64)
}
pub(crate) fn fn_now(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 0 {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Wrong number of arguments".to_string(),
};
}
// milliseconds since January 1, 1970 00:00:00 UTC.
let milliseconds = get_milliseconds_since_epoch();
let seconds = milliseconds / 1000;
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
Some(dt) => dt,
None => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Invalid date".to_string(),
}
}
};
let local_time = self.tz.from_utc_datetime(&dt);
// 693_594 is computed as:
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
// The 2 days offset is because of Excel 1900 bug
let days_from_1900 = local_time.num_days_from_ce() - 693_594;
let days = (local_time.num_seconds_from_midnight() as f64) / (60.0 * 60.0 * 24.0);
CalcResult::Number(days_from_1900 as f64 + days.fract())
}
}

View File

@@ -0,0 +1,176 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{parser::Node, token::Error},
model::Model,
};
use super::transcendental::{bessel_i, bessel_j, bessel_k, bessel_y, erf};
// https://root.cern/doc/v610/TMath_8cxx_source.html
// Notice that the parameters for Bessel functions in Excel and here have inverted order
// EXCEL_BESSEL(x, n) => bessel(n, x)
impl Model {
pub(crate) fn fn_besseli(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
let result = bessel_i(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_besselj(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
if n < 0 {
// In Excel this ins #NUM!
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
let result = bessel_j(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_besselk(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
let result = bessel_k(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_bessely(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
if n < 0 {
// In Excel this ins #NUM!
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
let result = bessel_y(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_erf(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
if args.len() == 2 {
let y = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(erf(y) - erf(x))
} else {
CalcResult::Number(erf(x))
}
}
pub(crate) fn fn_erfprecise(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
};
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(erf(x))
}
pub(crate) fn fn_erfc(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
};
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(1.0 - erf(x))
}
pub(crate) fn fn_erfcprecise(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
};
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(1.0 - erf(x))
}
}

View File

@@ -0,0 +1,233 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
// 2^48-1
const MAX: f64 = 281474976710655.0;
impl Model {
// BITAND( number1, number2)
pub(crate) fn fn_bitand(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number2 = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number1.trunc() != number1 || number2.trunc() != number2 {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number1 < 0.0 || number2 < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number1 > MAX || number2 > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
let number1 = number1.trunc() as i64;
let number2 = number2.trunc() as i64;
let result = number1 & number2;
CalcResult::Number(result as f64)
}
// BITOR(number1, number2)
pub(crate) fn fn_bitor(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number2 = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number1.trunc() != number1 || number2.trunc() != number2 {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number1 < 0.0 || number2 < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number1 > MAX || number2 > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
let number1 = number1.trunc() as i64;
let number2 = number2.trunc() as i64;
let result = number1 | number2;
CalcResult::Number(result as f64)
}
// BITXOR(number1, number2)
pub(crate) fn fn_bitxor(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number2 = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number1.trunc() != number1 || number2.trunc() != number2 {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number1 < 0.0 || number2 < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number1 > MAX || number2 > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
let number1 = number1.trunc() as i64;
let number2 = number2.trunc() as i64;
let result = number1 ^ number2;
CalcResult::Number(result as f64)
}
// BITLSHIFT(number, shift_amount)
pub(crate) fn fn_bitlshift(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let shift = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number.trunc() != number {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
if shift.abs() > 53.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"shift amount must be less than 53".to_string(),
);
}
let number = number.trunc() as i64;
let shift = shift.trunc() as i64;
let result = if shift > 0 {
number << shift
} else {
number >> -shift
};
let result = result as f64;
if result.abs() > MAX {
return CalcResult::new_error(Error::NUM, cell, "BITLSHIFT overflow".to_string());
}
CalcResult::Number(result)
}
// BITRSHIFT(number, shift_amount)
pub(crate) fn fn_bitrshift(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let shift = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number.trunc() != number {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
if shift.abs() > 53.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"shift amount must be less than 53".to_string(),
);
}
let number = number.trunc() as i64;
let shift = shift.trunc() as i64;
let result = if shift > 0 {
number >> shift
} else {
number << -shift
};
let result = result as f64;
if result.abs() > MAX {
return CalcResult::new_error(Error::NUM, cell, "BITRSHIFT overflow".to_string());
}
CalcResult::Number(result)
}
}

View File

@@ -0,0 +1,793 @@
use std::fmt;
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{
lexer::util::get_tokens,
parser::Node,
token::{Error, OpSum, TokenType},
},
model::Model,
number_format::to_precision,
};
/// This implements all functions with complex arguments in the standard
/// NOTE: If performance is ever needed we should have a new entry in CalcResult,
/// So this functions will return CalcResult::Complex(x,y, Suffix)
/// and not having to parse it over and over again.
#[derive(PartialEq, Debug)]
enum Suffix {
I,
J,
}
impl fmt::Display for Suffix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Suffix::I => write!(f, "i"),
Suffix::J => write!(f, "j"),
}
}
}
struct Complex {
x: f64,
y: f64,
suffix: Suffix,
}
impl fmt::Display for Complex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let x = to_precision(self.x, 15);
let y = to_precision(self.y, 15);
let suffix = &self.suffix;
// it is a bit weird what Excel does but it seems it uses general notation for
// numbers > 1e-20 and scientific notation for the rest
let y_str = if y.abs() <= 9e-20 {
format!("{:E}", y)
} else if y == 1.0 {
"".to_string()
} else if y == -1.0 {
"-".to_string()
} else {
format!("{}", y)
};
let x_str = if x.abs() <= 9e-20 {
format!("{:E}", x)
} else {
format!("{}", x)
};
if y == 0.0 && x == 0.0 {
write!(f, "0")
} else if y == 0.0 {
write!(f, "{x_str}")
} else if x == 0.0 {
write!(f, "{y_str}{suffix}")
} else if y > 0.0 {
write!(f, "{x_str}+{y_str}{suffix}")
} else {
write!(f, "{x_str}{y_str}{suffix}")
}
}
}
fn parse_complex_number(s: &str) -> Result<(f64, f64, Suffix), String> {
// Check for i, j, -i, -j
let (sign, s) = match s.strip_prefix('-') {
Some(r) => (-1.0, r),
None => (1.0, s),
};
match s {
"i" => return Ok((0.0, sign * 1.0, Suffix::I)),
"j" => return Ok((0.0, sign * 1.0, Suffix::J)),
_ => {
// Let it go
}
};
// TODO: This is an overuse
let tokens = get_tokens(s);
// There has to be 1, 2 3, or 4 tokens
// number
// number suffix
// number1+suffix
// number1+number2 suffix
match tokens.len() {
1 => {
// Real number
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
// i is the default
Ok((sign * number1, 0.0, Suffix::I))
}
2 => {
// number2 i
let number2 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[1].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
Ok((0.0, sign * number2, suffix))
}
3 => {
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let operation = match &tokens[1].token {
TokenType::Addition(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[2].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
let number2 = if matches!(operation, OpSum::Minus) {
-1.0
} else {
1.0
};
Ok((sign * number1, number2, suffix))
}
4 => {
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let operation = match &tokens[1].token {
TokenType::Addition(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let mut number2 = match tokens[2].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[3].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
if matches!(operation, OpSum::Minus) {
number2 = -number2
}
Ok((sign * number1, number2, suffix))
}
_ => Err(format!("Not a complex number: {s}")),
}
}
impl Model {
fn get_complex_number(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<(f64, f64, Suffix), CalcResult> {
let value = match self.get_string(node, cell) {
Ok(s) => s,
Err(s) => return Err(s),
};
if value.is_empty() {
return Ok((0.0, 0.0, Suffix::I));
}
match parse_complex_number(&value) {
Ok(s) => Ok(s),
Err(message) => Err(CalcResult::new_error(Error::NUM, cell, message)),
}
}
// COMPLEX(real_num, i_num, [suffix])
pub(crate) fn fn_complex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(2..=3).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let y = match self.get_number(&args[1], cell) {
Ok(s) => s,
Err(s) => return s,
};
let suffix = if args.len() == 3 {
match self.get_string(&args[2], cell) {
Ok(s) => {
if s == "i" || s.is_empty() {
Suffix::I
} else if s == "j" {
Suffix::J
} else {
return CalcResult::new_error(
Error::VALUE,
cell,
"Invalid suffix".to_string(),
);
}
}
Err(s) => return s,
}
} else {
Suffix::I
};
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imabs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(f64::sqrt(x * x + y * y))
}
pub(crate) fn fn_imaginary(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (_, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(y)
}
pub(crate) fn fn_imargument(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
if x == 0.0 && y == 0.0 {
return CalcResult::new_error(Error::DIV, cell, "Division by zero".to_string());
}
let angle = f64::atan2(y, x);
CalcResult::Number(angle)
}
pub(crate) fn fn_imconjugate(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex { x, y: -y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcos(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let complex = Complex {
x: x.cos() * y.cosh(),
y: -x.sin() * y.sinh(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcosh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let complex = Complex {
x: x.cosh() * y.cos(),
y: x.sinh() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcot(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if x == 0.0 && y != 0.0 {
let complex = Complex {
x: 0.0,
y: -1.0 / y.tanh(),
suffix,
};
return CalcResult::String(complex.to_string());
} else if y == 0.0 {
let complex = Complex {
x: 1.0 / x.tan(),
y: 0.0,
suffix,
};
return CalcResult::String(complex.to_string());
}
let x_cot = 1.0 / x.tan();
let y_coth = 1.0 / y.tanh();
let t = x_cot * x_cot + y_coth * y_coth;
let x = (x_cot * y_coth * y_coth - x_cot) / t;
let y = (-x_cot * x_cot * y_coth - y_coth) / t;
if x.is_infinite() || y.is_infinite() || x.is_nan() || y.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid operation".to_string());
}
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcsc(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let x_cos = x.cos();
let x_sin = x.sin();
let y_cosh = y.cosh();
let y_sinh = y.sinh();
let t = x_sin * x_sin * y_cosh * y_cosh + x_cos * x_cos * y_sinh * y_sinh;
let complex = Complex {
x: x_sin * y_cosh / t,
y: -x_cos * y_sinh / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcsch(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let x_cosh = x.cosh();
let x_sinh = x.sinh();
let y_cos = y.cos();
let y_sin = y.sin();
let t = x_sinh * x_sinh * y_cos * y_cos + x_cosh * x_cosh * y_sin * y_sin;
let complex = Complex {
x: x_sinh * y_cos / t,
y: -x_cosh * y_sin / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imdiv(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let t = x2 * x2 + y2 * y2;
if t == 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid".to_string());
}
let complex = Complex {
x: (x1 * x2 + y1 * y2) / t,
y: (-x1 * y2 + y1 * x2) / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imexp(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.exp() * y.cos(),
y: x.exp() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imln(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.ln(),
y: a,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imlog10(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.log10(),
y: a * f64::log10(f64::exp(1.0)),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imlog2(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.log2(),
y: a * f64::log2(f64::exp(1.0)),
suffix,
};
CalcResult::String(complex.to_string())
}
// IMPOWER(imnumber, power)
// If $(r, \theta)$ is the polar representation the formula is:
// $$ x = r^n*\cos(n\dot\theta), y = r^n*\csin(n\dot\theta) $
pub(crate) fn fn_impower(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
// if n == n.trunc() && n < 10.0 {
// // for small powers we compute manually
// let (mut x0, mut y0) = (x, y);
// for _ in 1..(n.trunc() as i32) {
// (x0, y0) = (x0 * x - y0 * y, x0 * y + y0 * x);
// }
// let complex = Complex {
// x: x0,
// y: y0,
// suffix,
// };
// return CalcResult::String(complex.to_string());
// };
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let x = r.powf(n) * f64::cos(a * n);
let y = r.powf(n) * f64::sin(a * n);
if x.is_infinite() || y.is_infinite() || x.is_nan() || y.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid operation".to_string());
}
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_improduct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 * x2 - y1 * y2,
y: x1 * y2 + y1 * x2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imreal(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, _, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(x)
}
pub(crate) fn fn_imsec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_cos = x.cos();
let x_sin = x.sin();
let y_cosh = y.cosh();
let y_sinh = y.sinh();
let t = x_cos * x_cos * y_cosh * y_cosh + x_sin * x_sin * y_sinh * y_sinh;
let complex = Complex {
x: x_cos * y_cosh / t,
y: x_sin * y_sinh / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsech(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_cosh = x.cosh();
let x_sinh = x.sinh();
let y_cos = y.cos();
let y_sin = y.sin();
let t = x_cosh * x_cosh * y_cos * y_cos + x_sinh * x_sinh * y_sin * y_sin;
let complex = Complex {
x: x_cosh * y_cos / t,
y: -x_sinh * y_sin / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.sin() * y.cosh(),
y: x.cos() * y.sinh(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsinh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.sinh() * y.cos(),
y: x.cosh() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsqrt(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y).sqrt();
let a = f64::atan2(y, x);
let complex = Complex {
x: r * f64::cos(a / 2.0),
y: r * f64::sin(a / 2.0),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsub(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 - x2,
y: y1 - y2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsum(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 + x2,
y: y1 + y2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imtan(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_tan = x.tan();
let y_tanh = y.tanh();
let t = 1.0 + x_tan * x_tan * y_tanh * y_tanh;
let complex = Complex {
x: (x_tan - x_tan * y_tanh * y_tanh) / t,
y: (y_tanh + x_tan * x_tan * y_tanh) / t,
suffix,
};
CalcResult::String(complex.to_string())
}
}
#[cfg(test)]
mod tests {
use crate::functions::engineering::complex::Suffix;
use super::parse_complex_number as parse;
#[test]
fn test_parse_complex() {
assert_eq!(parse("1+2i"), Ok((1.0, 2.0, Suffix::I)));
assert_eq!(parse("2i"), Ok((0.0, 2.0, Suffix::I)));
assert_eq!(parse("7.5"), Ok((7.5, 0.0, Suffix::I)));
assert_eq!(parse("-7.5"), Ok((-7.5, 0.0, Suffix::I)));
assert_eq!(parse("7-5i"), Ok((7.0, -5.0, Suffix::I)));
assert_eq!(parse("i"), Ok((0.0, 1.0, Suffix::I)));
assert_eq!(parse("7+i"), Ok((7.0, 1.0, Suffix::I)));
assert_eq!(parse("7-i"), Ok((7.0, -1.0, Suffix::I)));
assert_eq!(parse("-i"), Ok((0.0, -1.0, Suffix::I)));
assert_eq!(parse("0"), Ok((0.0, 0.0, Suffix::I)));
}
}

View File

@@ -0,0 +1,418 @@
use std::collections::HashMap;
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
enum Temperature {
Kelvin,
Celsius,
Rankine,
Reaumur,
Fahrenheit,
}
// To Kelvin
// T_K = T_C + 273.15
// T_K = 5/9 * T_rank
// T_K = (T_R-273.15)*4/5
// T_K = 5/9 ( T_F + 459.67)
fn convert_temperature(
value: f64,
from_temperature: Temperature,
to_temperature: Temperature,
) -> f64 {
let from_t_kelvin = match from_temperature {
Temperature::Kelvin => value,
Temperature::Celsius => value + 273.15,
Temperature::Rankine => 5.0 * value / 9.0,
Temperature::Reaumur => 5.0 * value / 4.0 + 273.15,
Temperature::Fahrenheit => 5.0 / 9.0 * (value + 459.67),
};
match to_temperature {
Temperature::Kelvin => from_t_kelvin,
Temperature::Celsius => from_t_kelvin - 273.5,
Temperature::Rankine => 9.0 * from_t_kelvin / 5.0,
Temperature::Reaumur => 4.0 * (from_t_kelvin - 273.15) / 5.0,
Temperature::Fahrenheit => 9.0 * from_t_kelvin / 5.0 - 459.67,
}
}
impl Model {
// CONVERT(number, from_unit, to_unit)
pub(crate) fn fn_convert(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 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 from_unit = match self.get_string(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
let to_unit = match self.get_string(&args[2], cell) {
Ok(s) => s,
Err(error) => return error,
};
let prefix = HashMap::from([
("Y", 1E+24),
("Z", 1E+21),
("E", 1000000000000000000.0),
("P", 1000000000000000.0),
("T", 1000000000000.0),
("G", 1000000000.0),
("M", 1000000.0),
("k", 1000.0),
("h", 100.0),
("da", 10.0),
("e", 10.0),
("d", 0.1),
("c", 0.01),
("m", 0.001),
("u", 0.000001),
("n", 0.000000001),
("p", 1E-12),
("f", 1E-15),
("a", 1E-18),
("z", 1E-21),
("y", 1E-24),
("Yi", 2.0_f64.powf(80.0)),
("Ei", 2.0_f64.powf(70.0)),
("Yi", 2.0_f64.powf(80.0)),
("Zi", 2.0_f64.powf(70.0)),
("Ei", 2.0_f64.powf(60.0)),
("Pi", 2.0_f64.powf(50.0)),
("Ti", 2.0_f64.powf(40.0)),
("Gi", 2.0_f64.powf(30.0)),
("Mi", 2.0_f64.powf(20.0)),
("ki", 2.0_f64.powf(10.0)),
]);
let mut units = HashMap::new();
let weight = HashMap::from([
("g", 1.0),
("sg", 14593.9029372064),
("lbm", 453.59237),
("u", 1.660538782E-24),
("ozm", 28.349523125),
("grain", 0.06479891),
("cwt", 45359.237),
("shweight", 45359.237),
("uk_cwt", 50802.34544),
("lcwt", 50802.34544),
("stone", 6350.29318),
("ton", 907184.74),
("brton", 1016046.9088), // g-sheets has a different value for this
("LTON", 1016046.9088),
("uk_ton", 1016046.9088),
]);
units.insert("weight", weight);
let distance = HashMap::from([
("m", 1.0),
("mi", 1609.344),
("Nmi", 1852.0),
("in", 0.0254),
("ft", 0.3048),
("yd", 0.9144),
("ang", 0.0000000001),
("ell", 1.143),
("ly", 9460730472580800.0),
("parsec", 30856775812815500.0),
("pc", 30856775812815500.0),
("Picapt", 0.000352777777777778),
("Pica", 0.000352777777777778),
("pica", 0.00423333333333333),
("survey_mi", 1609.34721869444),
]);
units.insert("distance", distance);
let time = HashMap::from([
("yr", 31557600.0),
("day", 86400.0),
("d", 86400.0),
("hr", 3600.0),
("mn", 60.0),
("min", 60.0),
("sec", 1.0),
("s", 1.0),
]);
units.insert("time", time);
let pressure = HashMap::from([
("Pa", 1.0),
("p", 1.0),
("atm", 101325.0),
("at", 101325.0),
("mmHg", 133.322),
("psi", 6894.75729316836),
("Torr", 133.322368421053),
]);
units.insert("pressure", pressure);
let force = HashMap::from([
("N", 1.0),
("dyn", 0.00001),
("dy", 0.00001),
("lbf", 4.4482216152605),
("pond", 0.00980665),
]);
units.insert("force", force);
let energy = HashMap::from([
("J", 1.0),
("e", 0.0000001),
("c", 4.184),
("cal", 4.1868),
("eV", 1.602176487E-19),
("ev", 1.602176487E-19),
("HPh", 2684519.53769617),
("hh", 2684519.53769617),
("Wh", 3600.0),
("wh", 3600.0),
("flb", 1.3558179483314),
("BTU", 1055.05585262),
("btu", 1055.05585262),
]);
units.insert("energy", energy);
let power = HashMap::from([
("HP", 745.69987158227),
("h", 745.69987158227),
("PS", 735.49875),
("W", 1.0),
("w", 1.0),
]);
units.insert("power", power);
let magnetism = HashMap::from([("T", 1.0), ("ga", 0.0001)]);
units.insert("magnetism", magnetism);
let volume = HashMap::from([
("tsp", 0.00000492892159375),
("tspm", 0.000005),
("tbs", 0.00001478676478125),
("oz", 0.0000295735295625),
("cup", 0.0002365882365),
("pt", 0.000473176473),
("us_pt", 0.000473176473),
("uk_pt", 0.00056826125),
("qt", 0.000946352946),
("uk_qt", 0.0011365225),
("gal", 0.003785411784),
("uk_gal", 0.00454609),
("l", 0.001),
("L", 0.001),
("lt", 0.001),
("ang3", 1E-30),
("ang^3", 1E-30),
("barrel", 0.158987294928),
("bushel", 0.03523907016688),
("ft3", 0.028316846592),
("ft^3", 0.028316846592),
("in3", 0.000016387064),
("in^3", 0.000016387064),
("ly3", 8.46786664623715E+47),
("ly^3", 8.46786664623715E+47),
("m3", 1.0),
("m^3", 1.0),
("mi3", 4168181825.44058),
("mi^3", 4168181825.44058),
("yd3", 0.764554857984),
("yd^3", 0.764554857984),
("Nmi3", 6352182208.0),
("Nmi^3", 6352182208.0),
("Picapt3", 4.39039566186557E-11),
("Picapt^3", 4.39039566186557E-11),
("Pica3", 4.39039566186557E-11),
("Pica^3", 4.39039566186557E-11),
("GRT", 2.8316846592),
("regton", 2.8316846592),
("MTON", 1.13267386368),
]);
units.insert("volume", volume);
let area = HashMap::from([
("uk_acre", 4046.8564224),
("us_acre", 4046.87260987425),
("ang2", 1E-20),
("ang^2", 1E-20),
("ar", 100.0),
("ft2", 0.09290304),
("ft^2", 0.09290304),
("ha", 10000.0),
("in2", 0.00064516),
("in^2", 0.00064516),
("ly2", 8.95054210748189E+31),
("ly^2", 8.95054210748189E+31),
("m2", 1.0),
("m^2", 1.0),
("Morgen", 2500.0),
("mi2", 2589988.110336),
("mi^2", 2589988.110336),
("Nmi2", 3429904.0),
("Nmi^2", 3429904.0),
("Picapt2", 0.000000124452160493827),
("Pica2", 0.000000124452160493827),
("Pica^2", 0.000000124452160493827),
("Picapt^2", 0.000000124452160493827),
("yd2", 0.83612736),
("yd^2", 0.83612736),
]);
units.insert("area", area);
let information = HashMap::from([("bit", 1.0), ("byte", 8.0)]);
units.insert("information", information);
let speed = HashMap::from([
("admkn", 0.514773333333333),
("kn", 0.514444444444444),
("m/h", 0.000277777777777778),
("m/hr", 0.000277777777777778),
("m/s", 1.0),
("m/sec", 1.0),
("mph", 0.44704),
]);
units.insert("speed", speed);
let temperature = HashMap::from([
("C", 1.0),
("cel", 1.0),
("F", 1.0),
("fah", 1.0),
("K", 1.0),
("kel", 1.0),
("Rank", 1.0),
("Reau", 1.0),
]);
units.insert("temperature", temperature);
// only some units admit prefixes (the is no kC, kilo Celsius, for instance)
let mks = [
"Pa", "p", "atm", "at", "mmHg", "g", "u", "m", "ang", "ly", "parsec", "pc", "ang2",
"ang^2", "ar", "m2", "m^2", "N", "dyn", "dy", "pond", "J", "e", "c", "cal", "eV", "ev",
"Wh", "wh", "W", "w", "T", "ga", "uk_pt", "l", "L", "lt", "ang3", "ang^3", "m3", "m^3",
"bit", "byte", "m/h", "m/hr", "m/s", "m/sec", "mph", "K", "kel",
];
let volumes = ["ang3", "ang^3", "m3", "m^3"];
// We need all_units to make sure tha pc is interpreted as parsec and not pico centimeters
// We could have this list hard coded, of course.
let mut all_units = Vec::new();
for unit in units.values() {
for &unit_name in unit.keys() {
all_units.push(unit_name);
}
}
let mut to_unit_prefix = 1.0;
let mut from_unit_prefix = 1.0;
// kind of units (weight, distance, time, ...)
let mut to_unit_kind = "";
let mut from_unit_kind = "";
let mut to_unit_name = "";
let mut from_unit_name = "";
for (&name, unit) in &units {
for (&unit_name, unit_value) in unit {
if let Some(pk) = from_unit.strip_suffix(unit_name) {
if pk.is_empty() {
from_unit_kind = name;
from_unit_prefix = 1.0 * unit_value;
from_unit_name = unit_name;
} else if let Some(modifier) = prefix.get(pk) {
if mks.contains(&unit_name) && !all_units.contains(&from_unit.as_str()) {
// We make sure:
// 1. It is a unit that admits a modifier (like metres or grams)
// 2. from_unit is not itself a unit
let scale = if name == "area" && unit_name != "ar" {
// 1 km2 is actually 10^6 m2
*modifier * modifier
} else if name == "volume" && volumes.contains(&unit_name) {
// don't look at me I don't make the rules!
*modifier * modifier * modifier
} else {
*modifier
};
from_unit_kind = name;
from_unit_prefix = scale * unit_value;
from_unit_name = unit_name;
}
}
}
if let Some(pk) = to_unit.strip_suffix(unit_name) {
if pk.is_empty() {
to_unit_kind = name;
to_unit_prefix = 1.0 * unit_value;
to_unit_name = unit_name;
} else if let Some(modifier) = prefix.get(pk) {
if mks.contains(&unit_name) && !all_units.contains(&to_unit.as_str()) {
let scale = if name == "area" && unit_name != "ar" {
*modifier * modifier
} else if name == "volume" && volumes.contains(&unit_name) {
*modifier * modifier * modifier
} else {
*modifier
};
to_unit_kind = name;
to_unit_prefix = scale * unit_value;
to_unit_name = unit_name;
}
}
}
if !from_unit_kind.is_empty() && !to_unit_kind.is_empty() {
break;
}
}
if !from_unit_kind.is_empty() && !to_unit_kind.is_empty() {
break;
}
}
if from_unit_kind != to_unit_kind {
return CalcResult::new_error(Error::NA, cell, "Different units".to_string());
}
// Let's check if it is temperature;
if from_unit_kind.is_empty() {
return CalcResult::new_error(Error::NA, cell, "Unit not found".to_string());
}
if from_unit_kind == "temperature" {
// Temperature requires formula conversion
// Kelvin (K, k), Celsius (C,cel), Rankine (Rank), Réaumur (Reau)
let to_temperature = match to_unit_name {
"K" | "kel" => Temperature::Kelvin,
"C" | "cel" => Temperature::Celsius,
"Rank" => Temperature::Rankine,
"Reau" => Temperature::Reaumur,
"F" | "fah" => Temperature::Fahrenheit,
_ => {
return CalcResult::new_error(Error::ERROR, cell, "Internal error".to_string());
}
};
let from_temperature = match from_unit_name {
"K" | "kel" => Temperature::Kelvin,
"C" | "cel" => Temperature::Celsius,
"Rank" => Temperature::Rankine,
"Reau" => Temperature::Reaumur,
"F" | "fah" => Temperature::Fahrenheit,
_ => {
return CalcResult::new_error(Error::ERROR, cell, "Internal error".to_string());
}
};
let t = convert_temperature(value * from_unit_prefix, from_temperature, to_temperature)
/ to_unit_prefix;
return CalcResult::Number(t);
}
CalcResult::Number(value * from_unit_prefix / to_unit_prefix)
}
}

View File

@@ -0,0 +1,59 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
model::Model,
number_format::to_precision,
};
impl Model {
// DELTA(number1, [number2])
pub(crate) fn fn_delta(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let arg_count = args.len();
if !(1..=2).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(error) => return error,
};
let number2 = if arg_count > 1 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(error) => return error,
}
} else {
0.0
};
if to_precision(number1, 16) == to_precision(number2, 16) {
CalcResult::Number(1.0)
} else {
CalcResult::Number(0.0)
}
}
// GESTEP(number, [step])
pub(crate) fn fn_gestep(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let arg_count = args.len();
if !(1..=2).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let number = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(error) => return error,
};
let step = if arg_count > 1 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(error) => return error,
}
} else {
0.0
};
if to_precision(number, 16) >= to_precision(step, 16) {
CalcResult::Number(1.0)
} else {
CalcResult::Number(0.0)
}
}
}

View File

@@ -0,0 +1,7 @@
mod bessel;
mod bit_operations;
mod complex;
mod convert;
mod misc;
mod number_basis;
mod transcendental;

View File

@@ -0,0 +1,546 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
// 8_i64.pow(10);
const OCT_MAX: i64 = 1_073_741_824;
const OCT_MAX_HALF: i64 = 536_870_912;
// 16_i64.pow(10)
const HEX_MAX: i64 = 1_099_511_627_776;
const HEX_MAX_HALF: i64 = 549_755_813_888;
// Binary numbers are 10 bits and the most significant bit is the sign
fn from_binary_to_decimal(value: f64) -> Result<i64, String> {
let value = format!("{value}");
let result = match i64::from_str_radix(&value, 2) {
Ok(b) => b,
Err(_) => {
return Err("cannot parse into binary".to_string());
}
};
if !(0..=1023).contains(&result) {
// 2^10
return Err("too large".to_string());
} else if result > 511 {
// 2^9
return Ok(result - 1024);
};
Ok(result)
}
impl Model {
// BIN2DEC(number)
pub(crate) fn fn_bin2dec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
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,
};
match from_binary_to_decimal(value) {
Ok(n) => CalcResult::Number(n as f64),
Err(message) => CalcResult::new_error(Error::NUM, cell, message),
}
}
// BIN2HEX(number, [places])
pub(crate) fn fn_bin2hex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
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 places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let value = match from_binary_to_decimal(value) {
Ok(n) => n,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if value < 0 {
CalcResult::String(format!("{:0width$X}", HEX_MAX + value, width = 9))
} else {
let result = format!("{:X}", value);
if let Some(places) = places {
if places < result.len() as i32 {
return CalcResult::new_error(
Error::NUM,
cell,
"Not enough places".to_string(),
);
}
return CalcResult::String(format!("{:0width$X}", value, width = places as usize));
}
CalcResult::String(result)
}
}
// BIN2OCT(number, [places])
pub(crate) fn fn_bin2oct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
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 places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
let value = match from_binary_to_decimal(value) {
Ok(n) => n,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
if value < 0 {
CalcResult::String(format!("{:0width$o}", OCT_MAX + value, width = 9))
} else {
let result = format!("{:o}", value);
if let Some(places) = places {
if places < result.len() as i32 {
return CalcResult::new_error(
Error::NUM,
cell,
"Not enough places".to_string(),
);
}
return CalcResult::String(format!("{:0width$o}", value, width = places as usize));
}
CalcResult::String(result)
}
}
pub(crate) fn fn_dec2bin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value_raw = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = value_raw.trunc() as i64;
if !(-512..=511).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += 1024;
}
let result = format!("{:b}", value);
if let Some(places) = places {
if value_raw > 0.0 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$b}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_dec2hex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value_raw = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f.trunc(),
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = value_raw.trunc() as i64;
if !(-HEX_MAX_HALF..=HEX_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += HEX_MAX;
}
let result = format!("{:X}", value);
if let Some(places) = places {
if value_raw > 0.0 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$X}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_dec2oct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value_raw = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = value_raw.trunc() as i64;
if !(-OCT_MAX_HALF..=OCT_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += OCT_MAX;
}
let result = format!("{:o}", value);
if let Some(places) = places {
if value_raw > 0.0 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$o}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
// HEX2BIN(number, [places])
pub(crate) fn fn_hex2bin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if value.len() > 10 {
return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
}
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = match i64::from_str_radix(&value, 16) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= HEX_MAX_HALF {
value -= HEX_MAX;
}
if !(-512..=511).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += 1024;
}
let result = format!("{:b}", value);
if let Some(places) = places {
if places <= 0 || (value > 0 && places < result.len() as i32) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$b}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
// HEX2DEC(number)
pub(crate) fn fn_hex2dec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
if value.len() > 10 {
return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
}
let mut value = match i64::from_str_radix(&value, 16) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= HEX_MAX_HALF {
value -= HEX_MAX;
}
CalcResult::Number(value as f64)
}
pub(crate) fn fn_hex2oct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
if value.len() > 10 {
return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
}
let mut value = match i64::from_str_radix(&value, 16) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value > HEX_MAX_HALF {
value -= HEX_MAX;
}
if !(-OCT_MAX_HALF..=OCT_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += OCT_MAX;
}
let result = format!("{:o}", value);
if let Some(places) = places {
if places <= 0 || (value > 0 && places < result.len() as i32) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$o}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_oct2bin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = match i64::from_str_radix(&value, 8) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= OCT_MAX_HALF {
value -= OCT_MAX;
}
if !(-512..=511).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += 1024;
}
let result = format!("{:b}", value);
if let Some(places) = places {
if value < 512 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$b}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_oct2dec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let mut value = match i64::from_str_radix(&value, 8) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= OCT_MAX_HALF {
value -= OCT_MAX
}
CalcResult::Number(value as f64)
}
pub(crate) fn fn_oct2hex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
// There is not a default value for places
// But if there is a value it needs to be positive and less than 11
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = match i64::from_str_radix(&value, 8) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= OCT_MAX_HALF {
value -= OCT_MAX;
}
if !(-HEX_MAX_HALF..=HEX_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += HEX_MAX;
}
let result = format!("{:X}", value);
if let Some(places) = places {
if value < HEX_MAX_HALF && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$X}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
}

View File

@@ -0,0 +1,29 @@
# Creating tests from transcendental functions
Excel supports a number of transcendental functions like the error functions, gamma nad beta functions.
In this folder we have tests for the Bessel functions.
Some other platform's implementations of those functions are remarkably poor (including Excel), sometimes failing on the third decimal digit. We strive to do better.
To properly test you need to compute some known values with established arbitrary precision arithmetic.
I use for this purpose Arb[1], created by the unrivalled Fredrik Johansson[2]. You might find some python bindings, but I use Julia's Nemo[3]:
```julia
julia> using Nemo
julia> CC = AcbField(200)
julia> besseli(CC("17"), CC("5.6"))
```
If you are new to Julia, just install Julia and in the Julia terminal run:
```
julia> using Pkg; Pkg.add("Nemo")
```
You only need to do that once (like the R philosophy)
Will give you any Bessel function of any order (integer or not) of any value real or complex
[1]: https://arblib.org/
[2]: https://fredrikj.net/
[3]: https://nemocas.github.io/Nemo.jl/latest/

View File

@@ -0,0 +1,144 @@
// This are somewhat lower precision than the BesselJ and BesselY
// needed for BesselK
pub(crate) fn bessel_i0(x: f64) -> f64 {
// Parameters of the polynomial approximation
let p1 = 1.0;
let p2 = 3.5156229;
let p3 = 3.0899424;
let p4 = 1.2067492;
let p5 = 0.2659732;
let p6 = 3.60768e-2;
let p7 = 4.5813e-3;
let q1 = 0.39894228;
let q2 = 1.328592e-2;
let q3 = 2.25319e-3;
let q4 = -1.57565e-3;
let q5 = 9.16281e-3;
let q6 = -2.057706e-2;
let q7 = 2.635537e-2;
let q8 = -1.647633e-2;
let q9 = 3.92377e-3;
let k1 = 3.75;
let ax = x.abs();
if x.is_infinite() {
return 0.0;
}
if ax < k1 {
// let xx = x / k1;
// let y = xx * xx;
// let mut result = 1.0;
// let max_iter = 50;
// let mut term = 1.0;
// for i in 1..max_iter {
// term = term * k1*k1*y /(2.0*i as f64).powi(2);
// result += term;
// };
// result
let xx = x / k1;
let y = xx * xx;
p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7)))))
} else {
let y = k1 / ax;
((ax).exp() / (ax).sqrt())
* (q1
+ y * (q2
+ y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * (q7 + y * (q8 + y * q9))))))))
}
}
// needed for BesselK
pub(crate) fn bessel_i1(x: f64) -> f64 {
let p1 = 0.5;
let p2 = 0.87890594;
let p3 = 0.51498869;
let p4 = 0.15084934;
let p5 = 2.658733e-2;
let p6 = 3.01532e-3;
let p7 = 3.2411e-4;
let q1 = 0.39894228;
let q2 = -3.988024e-2;
let q3 = -3.62018e-3;
let q4 = 1.63801e-3;
let q5 = -1.031555e-2;
let q6 = 2.282967e-2;
let q7 = -2.895312e-2;
let q8 = 1.787654e-2;
let q9 = -4.20059e-3;
let k1 = 3.75;
let ax = x.abs();
if ax < k1 {
let xx = x / k1;
let y = xx * xx;
x * (p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7))))))
} else {
let y = k1 / ax;
let result = ((ax).exp() / (ax).sqrt())
* (q1
+ y * (q2
+ y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * (q7 + y * (q8 + y * q9))))))));
if x < 0.0 {
return -result;
}
result
}
}
pub(crate) fn bessel_i(n: i32, x: f64) -> f64 {
let accuracy = 40;
let large_number = 1e10;
let small_number = 1e-10;
if n < 0 {
return f64::NAN;
}
if n == 0 {
return bessel_i0(x);
}
if x == 0.0 {
// I_n(0) = 0 for all n!= 0
return 0.0;
}
if n == 1 {
return bessel_i1(x);
}
if x.abs() > large_number {
return 0.0;
};
let tox = 2.0 / x.abs();
let mut bip = 0.0;
let mut bi = 1.0;
let mut result = 0.0;
let m = 2 * (((accuracy * n) as f64).sqrt().trunc() as i32 + n);
for j in (1..=m).rev() {
(bip, bi) = (bi, bip + (j as f64) * tox * bi);
// Prevent overflow
if bi.abs() > large_number {
result *= small_number;
bi *= small_number;
bip *= small_number;
}
if j == n {
result = bip;
}
}
result *= bessel_i0(x) / bi;
if (x < 0.0) && (n % 2 == 1) {
result = -result;
}
result
}

View File

@@ -0,0 +1,402 @@
/* @(#)e_j0.c 1.3 95/01/18 */
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/* j0(x), y0(x)
* Bessel function of the first and second kinds of order zero.
* Method -- j0(x):
* 1. For tiny x, we use j0(x) = 1 - x^2/4 + x^4/64 - ...
* 2. Reduce x to |x| since j0(x)=j0(-x), and
* for x in (0,2)
* j0(x) = 1-z/4+ z^2*R0/S0, where z = x*x;
* (precision: |j0-1+z/4-z^2R0/S0 |<2**-63.67 )
* for x in (2,inf)
* j0(x) = sqrt(2/(pi*x))*(p0(x)*cos(x0)-q0(x)*sin(x0))
* where x0 = x-pi/4. It is better to compute sin(x0),cos(x0)
* as follow:
* cos(x0) = cos(x)cos(pi/4)+sin(x)sin(pi/4)
* = 1/sqrt(2) * (cos(x) + sin(x))
* sin(x0) = sin(x)cos(pi/4)-cos(x)sin(pi/4)
* = 1/sqrt(2) * (sin(x) - cos(x))
* (To avoid cancellation, use
* sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
* to compute the worse 1.)
*
* 3 Special cases
* j0(nan)= nan
* j0(0) = 1
* j0(inf) = 0
*
* Method -- y0(x):
* 1. For x<2.
* Since
* y0(x) = 2/pi*(j0(x)*(ln(x/2)+Euler) + x^2/4 - ...)
* therefore y0(x)-2/pi*j0(x)*ln(x) is an even function.
* We use the following function to approximate y0,
* y0(x) = U(z)/V(z) + (2/pi)*(j0(x)*ln(x)), z= x^2
* where
* U(z) = u00 + u01*z + ... + u06*z^6
* V(z) = 1 + v01*z + ... + v04*z^4
* with absolute approximation error bounded by 2**-72.
* Note: For tiny x, U/V = u0 and j0(x)~1, hence
* y0(tiny) = u0 + (2/pi)*ln(tiny), (choose tiny<2**-27)
* 2. For x>=2.
* y0(x) = sqrt(2/(pi*x))*(p0(x)*cos(x0)+q0(x)*sin(x0))
* where x0 = x-pi/4. It is better to compute sin(x0),cos(x0)
* by the method menti1d above.
* 3. Special cases: y0(0)=-inf, y0(x<0)=NaN, y0(inf)=0.
*/
use std::f64::consts::FRAC_2_PI;
use super::bessel_util::{high_word, split_words, FRAC_2_SQRT_PI, HUGE};
// R0/S0 on [0, 2.00]
const R02: f64 = 1.562_499_999_999_999_5e-2; // 0x3F8FFFFF, 0xFFFFFFFD
const R03: f64 = -1.899_792_942_388_547_2e-4; // 0xBF28E6A5, 0xB61AC6E9
const R04: f64 = 1.829_540_495_327_006_7e-6; // 0x3EBEB1D1, 0x0C503919
const R05: f64 = -4.618_326_885_321_032e-9; // 0xBE33D5E7, 0x73D63FCE
const S01: f64 = 1.561_910_294_648_900_1e-2; // 0x3F8FFCE8, 0x82C8C2A4
const S02: f64 = 1.169_267_846_633_374_5e-4; // 0x3F1EA6D2, 0xDD57DBF4
const S03: f64 = 5.135_465_502_073_181e-7; // 0x3EA13B54, 0xCE84D5A9
const S04: f64 = 1.166_140_033_337_9e-9; // 0x3E1408BC, 0xF4745D8F
/* The asymptotic expansions of pzero is
* 1 - 9/128 s^2 + 11025/98304 s^4 - ..., where s = 1/x.
* For x >= 2, We approximate pzero by
* pzero(x) = 1 + (R/S)
* where R = pR0 + pR1*s^2 + pR2*s^4 + ... + pR5*s^10
* S = 1 + pS0*s^2 + ... + pS4*s^10
* and
* | pzero(x)-1-R/S | <= 2 ** ( -60.26)
*/
const P_R8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
-7.031_249_999_999_004e-2, /* 0xBFB1FFFF, 0xFFFFFD32 */
-8.081_670_412_753_498, /* 0xC02029D0, 0xB44FA779 */
-2.570_631_056_797_048_5e2, /* 0xC0701102, 0x7B19E863 */
-2.485_216_410_094_288e3, /* 0xC0A36A6E, 0xCD4DCAFC */
-5.253_043_804_907_295e3, /* 0xC0B4850B, 0x36CC643D */
];
const P_S8: [f64; 5] = [
1.165_343_646_196_681_8e2, /* 0x405D2233, 0x07A96751 */
3.833_744_753_641_218_3e3, /* 0x40ADF37D, 0x50596938 */
4.059_785_726_484_725_5e4, /* 0x40E3D2BB, 0x6EB6B05F */
1.167_529_725_643_759_2e5, /* 0x40FC810F, 0x8F9FA9BD */
4.762_772_841_467_309_6e4, /* 0x40E74177, 0x4F2C49DC */
];
const P_R5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
-1.141_254_646_918_945e-11, /* 0xBDA918B1, 0x47E495CC */
-7.031_249_408_735_993e-2, /* 0xBFB1FFFF, 0xE69AFBC6 */
-4.159_610_644_705_878, /* 0xC010A370, 0xF90C6BBF */
-6.767_476_522_651_673e1, /* 0xC050EB2F, 0x5A7D1783 */
-3.312_312_996_491_729_7e2, /* 0xC074B3B3, 0x6742CC63 */
-3.464_333_883_656_049e2, /* 0xC075A6EF, 0x28A38BD7 */
];
const P_S5: [f64; 5] = [
6.075_393_826_923_003_4e1, /* 0x404E6081, 0x0C98C5DE */
1.051_252_305_957_045_8e3, /* 0x40906D02, 0x5C7E2864 */
5.978_970_943_338_558e3, /* 0x40B75AF8, 0x8FBE1D60 */
9.625_445_143_577_745e3, /* 0x40C2CCB8, 0xFA76FA38 */
2.406_058_159_229_391e3, /* 0x40A2CC1D, 0xC70BE864 */
];
const P_R3: [f64; 6] = [
/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */
-2.547_046_017_719_519e-9, /* 0xBE25E103, 0x6FE1AA86 */
-7.031_196_163_814_817e-2, /* 0xBFB1FFF6, 0xF7C0E24B */
-2.409_032_215_495_296, /* 0xC00345B2, 0xAEA48074 */
-2.196_597_747_348_831e1, /* 0xC035F74A, 0x4CB94E14 */
-5.807_917_047_017_376e1, /* 0xC04D0A22, 0x420A1A45 */
-3.144_794_705_948_885e1, /* 0xC03F72AC, 0xA892D80F */
];
const P_S3: [f64; 5] = [
3.585_603_380_552_097e1, /* 0x4041ED92, 0x84077DD3 */
3.615_139_830_503_038_6e2, /* 0x40769839, 0x464A7C0E */
1.193_607_837_921_115_3e3, /* 0x4092A66E, 0x6D1061D6 */
1.127_996_798_569_074_1e3, /* 0x40919FFC, 0xB8C39B7E */
1.735_809_308_133_357_5e2, /* 0x4065B296, 0xFC379081 */
];
const P_R2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
-8.875_343_330_325_264e-8, /* 0xBE77D316, 0xE927026D */
-7.030_309_954_836_247e-2, /* 0xBFB1FF62, 0x495E1E42 */
-1.450_738_467_809_529_9, /* 0xBFF73639, 0x8A24A843 */
-7.635_696_138_235_278, /* 0xC01E8AF3, 0xEDAFA7F3 */
-1.119_316_688_603_567_5e1, /* 0xC02662E6, 0xC5246303 */
-3.233_645_793_513_353_4, /* 0xC009DE81, 0xAF8FE70F */
];
const P_S2: [f64; 5] = [
2.222_029_975_320_888e1, /* 0x40363865, 0x908B5959 */
1.362_067_942_182_152e2, /* 0x4061069E, 0x0EE8878F */
2.704_702_786_580_835e2, /* 0x4070E786, 0x42EA079B */
1.538_753_942_083_203_3e2, /* 0x40633C03, 0x3AB6FAFF */
1.465_761_769_482_562e1, /* 0x402D50B3, 0x44391809 */
];
// Note: This function is only called for ix>=0x40000000 (see above)
fn pzero(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
// ix>=0x40000000 && ix<=0x48000000);
let (p, q) = if ix >= 0x40200000 {
(P_R8, P_S8)
} else if ix >= 0x40122E8B {
(P_R5, P_S5)
} else if ix >= 0x4006DB6D {
(P_R3, P_S3)
} else {
(P_R2, P_S2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * q[4]))));
1.0 + r / s
}
/* For x >= 8, the asymptotic expansions of qzero is
* -1/8 s + 75/1024 s^3 - ..., where s = 1/x.
* We approximate pzero by
* qzero(x) = s*(-1.25 + (R/S))
* where R = qR0 + qR1*s^2 + qR2*s^4 + ... + qR5*s^10
* S = 1 + qS0*s^2 + ... + qS5*s^12
* and
* | qzero(x)/s +1.25-R/S | <= 2 ** ( -61.22)
*/
const Q_R8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
7.324_218_749_999_35e-2, /* 0x3FB2BFFF, 0xFFFFFE2C */
1.176_820_646_822_527e1, /* 0x40278952, 0x5BB334D6 */
5.576_733_802_564_019e2, /* 0x40816D63, 0x15301825 */
8.859_197_207_564_686e3, /* 0x40C14D99, 0x3E18F46D */
3.701_462_677_768_878e4, /* 0x40E212D4, 0x0E901566 */
];
const Q_S8: [f64; 6] = [
1.637_760_268_956_898_2e2, /* 0x406478D5, 0x365B39BC */
8.098_344_946_564_498e3, /* 0x40BFA258, 0x4E6B0563 */
1.425_382_914_191_204_8e5, /* 0x41016652, 0x54D38C3F */
8.033_092_571_195_144e5, /* 0x412883DA, 0x83A52B43 */
8.405_015_798_190_605e5, /* 0x4129A66B, 0x28DE0B3D */
-3.438_992_935_378_666e5, /* 0xC114FD6D, 0x2C9530C5 */
];
const Q_R5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
1.840_859_635_945_155_3e-11, /* 0x3DB43D8F, 0x29CC8CD9 */
7.324_217_666_126_848e-2, /* 0x3FB2BFFF, 0xD172B04C */
5.835_635_089_620_569_5, /* 0x401757B0, 0xB9953DD3 */
1.351_115_772_864_498_3e2, /* 0x4060E392, 0x0A8788E9 */
1.027_243_765_961_641e3, /* 0x40900CF9, 0x9DC8C481 */
1.989_977_858_646_053_8e3, /* 0x409F17E9, 0x53C6E3A6 */
];
const Q_S5: [f64; 6] = [
8.277_661_022_365_378e1, /* 0x4054B1B3, 0xFB5E1543 */
2.077_814_164_213_93e3, /* 0x40A03BA0, 0xDA21C0CE */
1.884_728_877_857_181e4, /* 0x40D267D2, 0x7B591E6D */
5.675_111_228_949_473e4, /* 0x40EBB5E3, 0x97E02372 */
3.597_675_384_251_145e4, /* 0x40E19118, 0x1F7A54A0 */
-5.354_342_756_019_448e3, /* 0xC0B4EA57, 0xBEDBC609 */
];
const Q_R3: [f64; 6] = [
/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */
4.377_410_140_897_386e-9, /* 0x3E32CD03, 0x6ADECB82 */
7.324_111_800_429_114e-2, /* 0x3FB2BFEE, 0x0E8D0842 */
3.344_231_375_161_707, /* 0x400AC0FC, 0x61149CF5 */
4.262_184_407_454_126_5e1, /* 0x40454F98, 0x962DAEDD */
1.708_080_913_405_656e2, /* 0x406559DB, 0xE25EFD1F */
1.667_339_486_966_511_7e2, /* 0x4064D77C, 0x81FA21E0 */
];
const Q_S3: [f64; 6] = [
4.875_887_297_245_872e1, /* 0x40486122, 0xBFE343A6 */
7.096_892_210_566_06e2, /* 0x40862D83, 0x86544EB3 */
3.704_148_226_201_113_6e3, /* 0x40ACF04B, 0xE44DFC63 */
6.460_425_167_525_689e3, /* 0x40B93C6C, 0xD7C76A28 */
2.516_333_689_203_689_6e3, /* 0x40A3A8AA, 0xD94FB1C0 */
-1.492_474_518_361_564e2, /* 0xC062A7EB, 0x201CF40F */
];
const Q_R2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
1.504_444_448_869_832_7e-7, /* 0x3E84313B, 0x54F76BDB */
7.322_342_659_630_793e-2, /* 0x3FB2BEC5, 0x3E883E34 */
1.998_191_740_938_16, /* 0x3FFFF897, 0xE727779C */
1.449_560_293_478_857_4e1, /* 0x402CFDBF, 0xAAF96FE5 */
3.166_623_175_047_815_4e1, /* 0x403FAA8E, 0x29FBDC4A */
1.625_270_757_109_292_7e1, /* 0x403040B1, 0x71814BB4 */
];
const Q_S2: [f64; 6] = [
3.036_558_483_552_192e1, /* 0x403E5D96, 0xF7C07AED */
2.693_481_186_080_498_4e2, /* 0x4070D591, 0xE4D14B40 */
8.447_837_575_953_201e2, /* 0x408A6645, 0x22B3BF22 */
8.829_358_451_124_886e2, /* 0x408B977C, 0x9C5CC214 */
2.126_663_885_117_988_3e2, /* 0x406A9553, 0x0E001365 */
-5.310_954_938_826_669_5, /* 0xC0153E6A, 0xF8B32931 */
];
fn qzero(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
let (p, q) = if ix >= 0x40200000 {
(Q_R8, Q_S8)
} else if ix >= 0x40122E8B {
(Q_R5, Q_S5)
} else if ix >= 0x4006DB6D {
(Q_R3, Q_S3)
} else {
(Q_R2, Q_S2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * (q[4] + z * q[5])))));
(-0.125 + r / s) / x
}
const U00: f64 = -7.380_429_510_868_723e-2; /* 0xBFB2E4D6, 0x99CBD01F */
const U01: f64 = 1.766_664_525_091_811_2e-1; /* 0x3FC69D01, 0x9DE9E3FC */
const U02: f64 = -1.381_856_719_455_969e-2; /* 0xBF8C4CE8, 0xB16CFA97 */
const U03: f64 = 3.474_534_320_936_836_5e-4; /* 0x3F36C54D, 0x20B29B6B */
const U04: f64 = -3.814_070_537_243_641_6e-6; /* 0xBECFFEA7, 0x73D25CAD */
const U05: f64 = 1.955_901_370_350_229_2e-8; /* 0x3E550057, 0x3B4EABD4 */
const U06: f64 = -3.982_051_941_321_034e-11; /* 0xBDC5E43D, 0x693FB3C8 */
const V01: f64 = 1.273_048_348_341_237e-2; /* 0x3F8A1270, 0x91C9C71A */
const V02: f64 = 7.600_686_273_503_533e-5; /* 0x3F13ECBB, 0xF578C6C1 */
const V03: f64 = 2.591_508_518_404_578e-7; /* 0x3E91642D, 0x7FF202FD */
const V04: f64 = 4.411_103_113_326_754_7e-10; /* 0x3DFE5018, 0x3BD6D9EF */
pub(crate) fn y0(x: f64) -> f64 {
let (lx, hx) = split_words(x);
let ix = 0x7fffffff & hx;
// Y0(NaN) is NaN, y0(-inf) is Nan, y0(inf) is 0
if ix >= 0x7ff00000 {
return 1.0 / (x + x * x);
}
if (ix | lx) == 0 {
return f64::NEG_INFINITY;
}
if hx < 0 {
return f64::NAN;
}
if ix >= 0x40000000 {
// |x| >= 2.0
// y0(x) = sqrt(2/(pi*x))*(p0(x)*sin(x0)+q0(x)*cos(x0))
// where x0 = x-pi/4
// Better formula:
// cos(x0) = cos(x)cos(pi/4)+sin(x)sin(pi/4)
// = 1/sqrt(2) * (sin(x) + cos(x))
// sin(x0) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
// = 1/sqrt(2) * (sin(x) - cos(x))
// To avoid cancellation, use
// sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
// to compute the worse 1.
let s = x.sin();
let c = x.cos();
let mut ss = s - c;
let mut cc = s + c;
// j0(x) = 1/sqrt(pi) * (P(0,x)*cc - Q(0,x)*ss) / sqrt(x)
// y0(x) = 1/sqrt(pi) * (P(0,x)*ss + Q(0,x)*cc) / sqrt(x)
if ix < 0x7fe00000 {
// make sure x+x not overflow
let z = -(x + x).cos();
if (s * c) < 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
return if ix > 0x48000000 {
FRAC_2_SQRT_PI * ss / x.sqrt()
} else {
let u = pzero(x);
let v = qzero(x);
FRAC_2_SQRT_PI * (u * ss + v * cc) / x.sqrt()
};
}
if ix <= 0x3e400000 {
// x < 2^(-27)
return U00 + FRAC_2_PI * x.ln();
}
let z = x * x;
let u = U00 + z * (U01 + z * (U02 + z * (U03 + z * (U04 + z * (U05 + z * U06)))));
let v = 1.0 + z * (V01 + z * (V02 + z * (V03 + z * V04)));
u / v + FRAC_2_PI * (j0(x) * x.ln())
}
pub(crate) fn j0(x: f64) -> f64 {
let hx = high_word(x);
let ix = hx & 0x7fffffff;
if x.is_nan() {
return x;
} else if x.is_infinite() {
return 0.0;
}
// the function is even
let x = x.abs();
if ix >= 0x40000000 {
// |x| >= 2.0
// let (s, c) = x.sin_cos()
let s = x.sin();
let c = x.cos();
let mut ss = s - c;
let mut cc = s + c;
// makes sure that x+x does not overflow
if ix < 0x7fe00000 {
// |x| < f64::MAX / 2.0
let z = -(x + x).cos();
if s * c < 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
// j0(x) = 1/sqrt(pi) * (P(0,x)*cc - Q(0,x)*ss) / sqrt(x)
// y0(x) = 1/sqrt(pi) * (P(0,x)*ss + Q(0,x)*cc) / sqrt(x)
return if ix > 0x48000000 {
// x < 5.253807105661922e-287 (2^(-951))
FRAC_2_SQRT_PI * cc / (x.sqrt())
} else {
let u = pzero(x);
let v = qzero(x);
FRAC_2_SQRT_PI * (u * cc - v * ss) / x.sqrt()
};
};
if ix < 0x3f200000 {
// |x| < 2^(-13)
if HUGE + x > 1.0 {
// raise inexact if x != 0
if ix < 0x3e400000 {
return 1.0; // |x|<2^(-27)
} else {
return 1.0 - 0.25 * x * x;
}
}
}
let z = x * x;
let r = z * (R02 + z * (R03 + z * (R04 + z * R05)));
let s = 1.0 + z * (S01 + z * (S02 + z * (S03 + z * S04)));
if ix < 0x3FF00000 {
// |x| < 1.00
1.0 + z * (-0.25 + (r / s))
} else {
let u = 0.5 * x;
(1.0 + u) * (1.0 - u) + z * (r / s)
}
}

View File

@@ -0,0 +1,391 @@
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/* __ieee754_j1(x), __ieee754_y1(x)
* Bessel function of the first and second kinds of order zero.
* Method -- j1(x):
* 1. For tiny x, we use j1(x) = x/2 - x^3/16 + x^5/384 - ...
* 2. Reduce x to |x| since j1(x)=-j1(-x), and
* for x in (0,2)
* j1(x) = x/2 + x*z*R0/S0, where z = x*x;
* (precision: |j1/x - 1/2 - R0/S0 |<2**-61.51 )
* for x in (2,inf)
* j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x1)+q1(x)*cos(x1))
* where x1 = x-3*pi/4. It is better to compute sin(x1),cos(x1)
* as follow:
* cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
* = 1/sqrt(2) * (sin(x) - cos(x))
* sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
* = -1/sqrt(2) * (sin(x) + cos(x))
* (To avoid cancellation, use
* sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
* to compute the worse one.)
*
* 3 Special cases
* j1(nan)= nan
* j1(0) = 0
* j1(inf) = 0
*
* Method -- y1(x):
* 1. screen out x<=0 cases: y1(0)=-inf, y1(x<0)=NaN
* 2. For x<2.
* Since
* y1(x) = 2/pi*(j1(x)*(ln(x/2)+Euler)-1/x-x/2+5/64*x^3-...)
* therefore y1(x)-2/pi*j1(x)*ln(x)-1/x is an odd function.
* We use the following function to approximate y1,
* y1(x) = x*U(z)/V(z) + (2/pi)*(j1(x)*ln(x)-1/x), z= x^2
* where for x in [0,2] (abs err less than 2**-65.89)
* U(z) = U0[0] + U0[1]*z + ... + U0[4]*z^4
* V(z) = 1 + v0[0]*z + ... + v0[4]*z^5
* Note: For tiny x, 1/x dominate y1 and hence
* y1(tiny) = -2/pi/tiny, (choose tiny<2**-54)
* 3. For x>=2.
* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x1)+q1(x)*cos(x1))
* where x1 = x-3*pi/4. It is better to compute sin(x1),cos(x1)
* by method mentioned above.
*/
use std::f64::consts::FRAC_2_PI;
use super::bessel_util::{high_word, split_words, FRAC_2_SQRT_PI, HUGE};
// R0/S0 on [0,2]
const R00: f64 = -6.25e-2; // 0xBFB00000, 0x00000000
const R01: f64 = 1.407_056_669_551_897e-3; // 0x3F570D9F, 0x98472C61
const R02: f64 = -1.599_556_310_840_356e-5; // 0xBEF0C5C6, 0xBA169668
const R03: f64 = 4.967_279_996_095_844_5e-8; // 0x3E6AAAFA, 0x46CA0BD9
const S01: f64 = 1.915_375_995_383_634_6e-2; // 0x3F939D0B, 0x12637E53
const S02: f64 = 1.859_467_855_886_309_2e-4; // 0x3F285F56, 0xB9CDF664
const S03: f64 = 1.177_184_640_426_236_8e-6; // 0x3EB3BFF8, 0x333F8498
const S04: f64 = 5.046_362_570_762_170_4e-9; // 0x3E35AC88, 0xC97DFF2C
const S05: f64 = 1.235_422_744_261_379_1e-11; // 0x3DAB2ACF, 0xCFB97ED8
pub(crate) fn j1(x: f64) -> f64 {
let hx = high_word(x);
let ix = hx & 0x7fffffff;
if ix >= 0x7ff00000 {
return 1.0 / x;
}
let y = x.abs();
if ix >= 0x40000000 {
/* |x| >= 2.0 */
let s = y.sin();
let c = y.cos();
let mut ss = -s - c;
let mut cc = s - c;
if ix < 0x7fe00000 {
/* make sure y+y not overflow */
let z = (y + y).cos();
if s * c > 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
// j1(x) = 1/sqrt(pi) * (P(1,x)*cc - Q(1,x)*ss) / sqrt(x)
// y1(x) = 1/sqrt(pi) * (P(1,x)*ss + Q(1,x)*cc) / sqrt(x)
let z = if ix > 0x48000000 {
FRAC_2_SQRT_PI * cc / y.sqrt()
} else {
let u = pone(y);
let v = qone(y);
FRAC_2_SQRT_PI * (u * cc - v * ss) / y.sqrt()
};
if hx < 0 {
return -z;
} else {
return z;
}
}
if ix < 0x3e400000 {
/* |x|<2**-27 */
if HUGE + x > 1.0 {
return 0.5 * x; /* inexact if x!=0 necessary */
}
}
let z = x * x;
let mut r = z * (R00 + z * (R01 + z * (R02 + z * R03)));
let s = 1.0 + z * (S01 + z * (S02 + z * (S03 + z * (S04 + z * S05))));
r *= x;
x * 0.5 + r / s
}
const U0: [f64; 5] = [
-1.960_570_906_462_389_4e-1, /* 0xBFC91866, 0x143CBC8A */
5.044_387_166_398_113e-2, /* 0x3FA9D3C7, 0x76292CD1 */
-1.912_568_958_757_635_5e-3, /* 0xBF5F55E5, 0x4844F50F */
2.352_526_005_616_105e-5, /* 0x3EF8AB03, 0x8FA6B88E */
-9.190_991_580_398_789e-8, /* 0xBE78AC00, 0x569105B8 */
];
const V0: [f64; 5] = [
1.991_673_182_366_499e-2, /* 0x3F94650D, 0x3F4DA9F0 */
2.025_525_810_251_351_7e-4, /* 0x3F2A8C89, 0x6C257764 */
1.356_088_010_975_162_3e-6, /* 0x3EB6C05A, 0x894E8CA6 */
6.227_414_523_646_215e-9, /* 0x3E3ABF1D, 0x5BA69A86 */
1.665_592_462_079_920_8e-11, /* 0x3DB25039, 0xDACA772A */
];
pub(crate) fn y1(x: f64) -> f64 {
let (lx, hx) = split_words(x);
let ix = 0x7fffffff & hx;
// if Y1(NaN) is NaN, Y1(-inf) is NaN, Y1(inf) is 0
if ix >= 0x7ff00000 {
return 1.0 / (x + x * x);
}
if (ix | lx) == 0 {
return f64::NEG_INFINITY;
}
if hx < 0 {
return f64::NAN;
}
if ix >= 0x40000000 {
// |x| >= 2.0
let s = x.sin();
let c = x.cos();
let mut ss = -s - c;
let mut cc = s - c;
if ix < 0x7fe00000 {
// make sure x+x not overflow
let z = (x + x).cos();
if s * c > 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
/* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x0)+q1(x)*cos(x0))
* where x0 = x-3pi/4
* Better formula:
* cos(x0) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
* = 1/sqrt(2) * (sin(x) - cos(x))
* sin(x0) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
* = -1/sqrt(2) * (cos(x) + sin(x))
* To avoid cancellation, use
* sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
* to compute the worse one.
*/
return if ix > 0x48000000 {
FRAC_2_SQRT_PI * ss / x.sqrt()
} else {
let u = pone(x);
let v = qone(x);
FRAC_2_SQRT_PI * (u * ss + v * cc) / x.sqrt()
};
}
if ix <= 0x3c900000 {
// x < 2^(-54)
return -FRAC_2_PI / x;
}
let z = x * x;
let u = U0[0] + z * (U0[1] + z * (U0[2] + z * (U0[3] + z * U0[4])));
let v = 1.0 + z * (V0[0] + z * (V0[1] + z * (V0[2] + z * (V0[3] + z * V0[4]))));
x * (u / v) + FRAC_2_PI * (j1(x) * x.ln() - 1.0 / x)
}
/* For x >= 8, the asymptotic expansions of pone is
* 1 + 15/128 s^2 - 4725/2^15 s^4 - ..., where s = 1/x.
* We approximate pone by
* pone(x) = 1 + (R/S)
* where R = pr0 + pr1*s^2 + pr2*s^4 + ... + pr5*s^10
* S = 1 + ps0*s^2 + ... + ps4*s^10
* and
* | pone(x)-1-R/S | <= 2 ** ( -60.06)
*/
const PR8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
1.171_874_999_999_886_5e-1, /* 0x3FBDFFFF, 0xFFFFFCCE */
1.323_948_065_930_735_8e1, /* 0x402A7A9D, 0x357F7FCE */
4.120_518_543_073_785_6e2, /* 0x4079C0D4, 0x652EA590 */
3.874_745_389_139_605_3e3, /* 0x40AE457D, 0xA3A532CC */
7.914_479_540_318_917e3, /* 0x40BEEA7A, 0xC32782DD */
];
const PS8: [f64; 5] = [
1.142_073_703_756_784_1e2, /* 0x405C8D45, 0x8E656CAC */
3.650_930_834_208_534_6e3, /* 0x40AC85DC, 0x964D274F */
3.695_620_602_690_334_6e4, /* 0x40E20B86, 0x97C5BB7F */
9.760_279_359_349_508e4, /* 0x40F7D42C, 0xB28F17BB */
3.080_427_206_278_888e4, /* 0x40DE1511, 0x697A0B2D */
];
const PR5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
1.319_905_195_562_435_2e-11, /* 0x3DAD0667, 0xDAE1CA7D */
1.171_874_931_906_141e-1, /* 0x3FBDFFFF, 0xE2C10043 */
6.802_751_278_684_329, /* 0x401B3604, 0x6E6315E3 */
1.083_081_829_901_891_1e2, /* 0x405B13B9, 0x452602ED */
5.176_361_395_331_998e2, /* 0x40802D16, 0xD052D649 */
5.287_152_013_633_375e2, /* 0x408085B8, 0xBB7E0CB7 */
];
const PS5: [f64; 5] = [
5.928_059_872_211_313e1, /* 0x404DA3EA, 0xA8AF633D */
9.914_014_187_336_144e2, /* 0x408EFB36, 0x1B066701 */
5.353_266_952_914_88e3, /* 0x40B4E944, 0x5706B6FB */
7.844_690_317_495_512e3, /* 0x40BEA4B0, 0xB8A5BB15 */
1.504_046_888_103_610_6e3, /* 0x40978030, 0x036F5E51 */
];
const PR3: [f64; 6] = [
3.025_039_161_373_736e-9, /* 0x3E29FC21, 0xA7AD9EDD */
1.171_868_655_672_535_9e-1, /* 0x3FBDFFF5, 0x5B21D17B */
3.932_977_500_333_156_4, /* 0x400F76BC, 0xE85EAD8A */
3.511_940_355_916_369e1, /* 0x40418F48, 0x9DA6D129 */
9.105_501_107_507_813e1, /* 0x4056C385, 0x4D2C1837 */
4.855_906_851_973_649e1, /* 0x4048478F, 0x8EA83EE5 */
];
const PS3: [f64; 5] = [
3.479_130_950_012_515e1, /* 0x40416549, 0xA134069C */
3.367_624_587_478_257_5e2, /* 0x40750C33, 0x07F1A75F */
1.046_871_399_757_751_3e3, /* 0x40905B7C, 0x5037D523 */
8.908_113_463_982_564e2, /* 0x408BD67D, 0xA32E31E9 */
1.037_879_324_396_392_8e2, /* 0x4059F26D, 0x7C2EED53 */
];
const PR2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
1.077_108_301_068_737_4e-7, /* 0x3E7CE9D4, 0xF65544F4 */
1.171_762_194_626_833_5e-1, /* 0x3FBDFF42, 0xBE760D83 */
2.368_514_966_676_088, /* 0x4002F2B7, 0xF98FAEC0 */
1.224_261_091_482_612_3e1, /* 0x40287C37, 0x7F71A964 */
1.769_397_112_716_877_3e1, /* 0x4031B1A8, 0x177F8EE2 */
5.073_523_125_888_185, /* 0x40144B49, 0xA574C1FE */
];
const PS2: [f64; 5] = [
2.143_648_593_638_214e1, /* 0x40356FBD, 0x8AD5ECDC */
1.252_902_271_684_027_5e2, /* 0x405F5293, 0x14F92CD5 */
2.322_764_690_571_628e2, /* 0x406D08D8, 0xD5A2DBD9 */
1.176_793_732_871_471e2, /* 0x405D6B7A, 0xDA1884A9 */
8.364_638_933_716_183, /* 0x4020BAB1, 0xF44E5192 */
];
/* Note: This function is only called for ix>=0x40000000 (see above) */
fn pone(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
// ix>=0x40000000 && ix<=0x48000000)
let (p, q) = if ix >= 0x40200000 {
(PR8, PS8)
} else if ix >= 0x40122E8B {
(PR5, PS5)
} else if ix >= 0x4006DB6D {
(PR3, PS3)
} else {
(PR2, PS2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * q[4]))));
1.0 + r / s
}
/* For x >= 8, the asymptotic expansions of qone is
* 3/8 s - 105/1024 s^3 - ..., where s = 1/x.
* We approximate pone by
* qone(x) = s*(0.375 + (R/S))
* where R = qr1*s^2 + qr2*s^4 + ... + qr5*s^10
* S = 1 + qs1*s^2 + ... + qs6*s^12
* and
* | qone(x)/s -0.375-R/S | <= 2 ** ( -61.13)
*/
const QR8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
-1.025_390_624_999_927_1e-1, /* 0xBFBA3FFF, 0xFFFFFDF3 */
-1.627_175_345_445_9e1, /* 0xC0304591, 0xA26779F7 */
-7.596_017_225_139_501e2, /* 0xC087BCD0, 0x53E4B576 */
-1.184_980_667_024_295_9e4, /* 0xC0C724E7, 0x40F87415 */
-4.843_851_242_857_503_5e4, /* 0xC0E7A6D0, 0x65D09C6A */
];
const QS8: [f64; 6] = [
1.613_953_697_007_229e2, /* 0x40642CA6, 0xDE5BCDE5 */
7.825_385_999_233_485e3, /* 0x40BE9162, 0xD0D88419 */
1.338_753_362_872_495_8e5, /* 0x4100579A, 0xB0B75E98 */
7.196_577_236_832_409e5, /* 0x4125F653, 0x72869C19 */
6.666_012_326_177_764e5, /* 0x412457D2, 0x7719AD5C */
-2.944_902_643_038_346_4e5, /* 0xC111F969, 0x0EA5AA18 */
];
const QR5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
-2.089_799_311_417_641e-11, /* 0xBDB6FA43, 0x1AA1A098 */
-1.025_390_502_413_754_3e-1, /* 0xBFBA3FFF, 0xCB597FEF */
-8.056_448_281_239_36, /* 0xC0201CE6, 0xCA03AD4B */
-1.836_696_074_748_883_8e2, /* 0xC066F56D, 0x6CA7B9B0 */
-1.373_193_760_655_081_6e3, /* 0xC09574C6, 0x6931734F */
-2.612_444_404_532_156_6e3, /* 0xC0A468E3, 0x88FDA79D */
];
const QS5: [f64; 6] = [
8.127_655_013_843_358e1, /* 0x405451B2, 0xFF5A11B2 */
1.991_798_734_604_859_6e3, /* 0x409F1F31, 0xE77BF839 */
1.746_848_519_249_089e4, /* 0x40D10F1F, 0x0D64CE29 */
4.985_142_709_103_523e4, /* 0x40E8576D, 0xAABAD197 */
2.794_807_516_389_181_2e4, /* 0x40DB4B04, 0xCF7C364B */
-4.719_183_547_951_285e3, /* 0xC0B26F2E, 0xFCFFA004 */
];
const QR3: [f64; 6] = [
-5.078_312_264_617_666e-9, /* 0xBE35CFA9, 0xD38FC84F */
-1.025_378_298_208_370_9e-1, /* 0xBFBA3FEB, 0x51AEED54 */
-4.610_115_811_394_734, /* 0xC01270C2, 0x3302D9FF */
-5.784_722_165_627_836_4e1, /* 0xC04CEC71, 0xC25D16DA */
-2.282_445_407_376_317e2, /* 0xC06C87D3, 0x4718D55F */
-2.192_101_284_789_093_3e2, /* 0xC06B66B9, 0x5F5C1BF6 */
];
const QS3: [f64; 6] = [
4.766_515_503_237_295e1, /* 0x4047D523, 0xCCD367E4 */
6.738_651_126_766_997e2, /* 0x40850EEB, 0xC031EE3E */
3.380_152_866_795_263_4e3, /* 0x40AA684E, 0x448E7C9A */
5.547_729_097_207_228e3, /* 0x40B5ABBA, 0xA61D54A6 */
1.903_119_193_388_108e3, /* 0x409DBC7A, 0x0DD4DF4B */
-1.352_011_914_443_073_4e2, /* 0xC060E670, 0x290A311F */
];
const QR2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
-1.783_817_275_109_588_7e-7, /* 0xBE87F126, 0x44C626D2 */
-1.025_170_426_079_855_5e-1, /* 0xBFBA3E8E, 0x9148B010 */
-2.752_205_682_781_874_6, /* 0xC0060484, 0x69BB4EDA */
-1.966_361_626_437_037_2e1, /* 0xC033A9E2, 0xC168907F */
-4.232_531_333_728_305e1, /* 0xC04529A3, 0xDE104AAA */
-2.137_192_117_037_040_6e1, /* 0xC0355F36, 0x39CF6E52 */
];
const QS2: [f64; 6] = [
2.953_336_290_605_238_5e1, /* 0x403D888A, 0x78AE64FF */
2.529_815_499_821_905_3e2, /* 0x406F9F68, 0xDB821CBA */
7.575_028_348_686_454e2, /* 0x4087AC05, 0xCE49A0F7 */
7.393_932_053_204_672e2, /* 0x40871B25, 0x48D4C029 */
1.559_490_033_366_661_2e2, /* 0x40637E5E, 0x3C3ED8D4 */
-4.959_498_988_226_282, /* 0xC013D686, 0xE71BE86B */
];
// Note: This function is only called for ix>=0x40000000 (see above)
fn qone(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
// ix>=0x40000000 && ix<=0x48000000
let (p, q) = if ix >= 0x40200000 {
(QR8, QS8)
} else if ix >= 0x40122E8B {
(QR5, QS5)
} else if ix >= 0x4006DB6D {
(QR3, QS3)
} else {
(QR2, QS2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * (q[4] + z * q[5])))));
(0.375 + r / s) / x
}

View File

@@ -0,0 +1,329 @@
// https://github.com/JuliaLang/openlibm/blob/master/src/e_jn.c
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/*
* __ieee754_jn(n, x), __ieee754_yn(n, x)
* floating point Bessel's function of the 1st and 2nd kind
* of order n
*
* Special cases:
* y0(0)=y1(0)=yn(n,0) = -inf with division by 0 signal;
* y0(-ve)=y1(-ve)=yn(n,-ve) are NaN with invalid signal.
* Note 2. About jn(n,x), yn(n,x)
* For n=0, j0(x) is called,
* for n=1, j1(x) is called,
* for n<x, forward recursion us used starting
* from values of j0(x) and j1(x).
* for n>x, a continued fraction approximation to
* j(n,x)/j(n-1,x) is evaluated and then backward
* recursion is used starting from a supposed value
* for j(n,x). The resulting value of j(0,x) is
* compared with the actual value to correct the
* supposed value of j(n,x).
*
* yn(n,x) is similar in all respects, except
* that forward recursion is used for all
* values of n>1.
*
*/
use super::{
bessel_j0_y0::{j0, y0},
bessel_j1_y1::{j1, y1},
bessel_util::{split_words, FRAC_2_SQRT_PI},
};
// Special cases are:
//
// $ J_n(n, ±\Infinity) = 0$
// $ J_n(n, NaN} = NaN $
// $ J_n(n, 0) = 0 $
pub(crate) fn jn(n: i32, x: f64) -> f64 {
let (lx, mut hx) = split_words(x);
let ix = 0x7fffffff & hx;
// if J(n,NaN) is NaN
if x.is_nan() {
return x;
}
// if (ix | (/*(u_int32_t)*/(lx | -lx)) >> 31) > 0x7ff00000 {
// return x + x;
// }
let (n, x) = if n < 0 {
// hx ^= 0x80000000;
hx = -hx;
(-n, -x)
} else {
(n, x)
};
if n == 0 {
return j0(x);
}
if n == 1 {
return j1(x);
}
let sign = (n & 1) & (hx >> 31); /* even n -- 0, odd n -- sign(x) */
// let sign = if x < 0.0 { -1 } else { 1 };
let x = x.abs();
let b = if (ix | lx) == 0 || ix >= 0x7ff00000 {
// if x is 0 or inf
0.0
} else if n as f64 <= x {
/* Safe to use J(n+1,x)=2n/x *J(n,x)-J(n-1,x) */
if ix >= 0x52D00000 {
/* x > 2**302 */
/* (x >> n**2)
* Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Let s=x.sin(), c=x.cos(),
* xn=x-(2n+1)*pi/4, sqt2 = sqrt(2),then
*
* n sin(xn)*sqt2 cos(xn)*sqt2
* ----------------------------------
* 0 s-c c+s
* 1 -s-c -c+s
* 2 -s+c -c-s
* 3 s+c c-s
*/
let temp = match n & 3 {
0 => x.cos() + x.sin(),
1 => -x.cos() + x.sin(),
2 => -x.cos() - x.sin(),
3 => x.cos() - x.sin(),
_ => {
// Impossible: FIXME!
// panic!("")
0.0
}
};
FRAC_2_SQRT_PI * temp / x.sqrt()
} else {
let mut a = j0(x);
let mut b = j1(x);
for i in 1..n {
let temp = b;
b = b * (((i + i) as f64) / x) - a; /* avoid underflow */
a = temp;
}
b
}
} else {
// x < 2^(-29)
if ix < 0x3e100000 {
// x is tiny, return the first Taylor expansion of J(n,x)
// J(n,x) = 1/n!*(x/2)^n - ...
if n > 33 {
// underflow
0.0
} else {
let temp = x * 0.5;
let mut b = temp;
let mut a = 1;
for i in 2..=n {
a *= i; /* a = n! */
b *= temp; /* b = (x/2)^n */
}
b / (a as f64)
}
} else {
/* use backward recurrence */
/* x x^2 x^2
* J(n,x)/J(n-1,x) = ---- ------ ------ .....
* 2n - 2(n+1) - 2(n+2)
*
* 1 1 1
* (for large x) = ---- ------ ------ .....
* 2n 2(n+1) 2(n+2)
* -- - ------ - ------ -
* x x x
*
* Let w = 2n/x and h=2/x, then the above quotient
* is equal to the continued fraction:
* 1
* = -----------------------
* 1
* w - -----------------
* 1
* w+h - ---------
* w+2h - ...
*
* To determine how many terms needed, let
* Q(0) = w, Q(1) = w(w+h) - 1,
* Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
* When Q(k) > 1e4 good for single
* When Q(k) > 1e9 good for double
* When Q(k) > 1e17 good for quadruple
*/
let w = ((n + n) as f64) / x;
let h = 2.0 / x;
let mut q0 = w;
let mut z = w + h;
let mut q1 = w * z - 1.0;
let mut k = 1;
while q1 < 1.0e9 {
k += 1;
z += h;
let tmp = z * q1 - q0;
q0 = q1;
q1 = tmp;
}
let m = n + n;
let mut t = 0.0;
for i in (m..2 * (n + k)).step_by(2).rev() {
t = 1.0 / ((i as f64) / x - t);
}
// for (t=0, i = 2*(n+k); i>=m; i -= 2) t = 1/(i/x-t);
let mut a = t;
let mut b = 1.0;
/* estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
* Hence, if n*(log(2n/x)) > ...
* single 8.8722839355e+01
* double 7.09782712893383973096e+02
* long double 1.1356523406294143949491931077970765006170e+04
* then recurrent value may overflow and the result is
* likely underflow to 0
*/
let mut tmp = n as f64;
let v = 2.0 / x;
tmp = tmp * f64::ln(f64::abs(v * tmp));
if tmp < 7.097_827_128_933_84e2 {
// for(i=n-1, di=(i+i); i>0; i--){
let mut di = 2.0 * ((n - 1) as f64);
for _ in (1..=n - 1).rev() {
let temp = b;
b *= di;
b = b / x - a;
a = temp;
di -= 2.0;
}
} else {
// for(i=n-1, di=(i+i); i>0; i--) {
let mut di = 2.0 * ((n - 1) as f64);
for _ in (1..=n - 1).rev() {
let temp = b;
b *= di;
b = b / x - a;
a = temp;
di -= 2.0;
/* scale b to avoid spurious overflow */
if b > 1e100 {
a /= b;
t /= b;
b = 1.0;
}
}
}
let z = j0(x);
let w = j1(x);
if z.abs() >= w.abs() {
t * z / b
} else {
t * w / a
}
}
};
if sign == 1 {
-b
} else {
b
}
}
// Yn returns the order-n Bessel function of the second kind.
//
// Special cases are:
//
// Y(n, +Inf) = 0
// Y(n ≥ 0, 0) = -Inf
// Y(n < 0, 0) = +Inf if n is odd, -Inf if n is even
// Y(n, x < 0) = NaN
// Y(n, NaN) = NaN
pub(crate) fn yn(n: i32, x: f64) -> f64 {
let (lx, hx) = split_words(x);
let ix = 0x7fffffff & hx;
// if Y(n, NaN) is NaN
if x.is_nan() {
return x;
}
// if (ix | (/*(u_int32_t)*/(lx | -lx)) >> 31) > 0x7ff00000 {
// return x + x;
// }
if (ix | lx) == 0 {
return f64::NEG_INFINITY;
}
if hx < 0 {
return f64::NAN;
}
let (n, sign) = if n < 0 {
(-n, 1 - ((n & 1) << 1))
} else {
(n, 1)
};
if n == 0 {
return y0(x);
}
if n == 1 {
return (sign as f64) * y1(x);
}
if ix == 0x7ff00000 {
return 0.0;
}
let b = if ix >= 0x52D00000 {
// x > 2^302
/* (x >> n**2)
* Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Let s=x.sin(), c=x.cos(),
* xn=x-(2n+1)*pi/4, sqt2 = sqrt(2),then
*
* n sin(xn)*sqt2 cos(xn)*sqt2
* ----------------------------------
* 0 s-c c+s
* 1 -s-c -c+s
* 2 -s+c -c-s
* 3 s+c c-s
*/
let temp = match n & 3 {
0 => x.sin() - x.cos(),
1 => -x.sin() - x.cos(),
2 => -x.sin() + x.cos(),
3 => x.sin() + x.cos(),
_ => {
// unreachable
0.0
}
};
FRAC_2_SQRT_PI * temp / x.sqrt()
} else {
let mut a = y0(x);
let mut b = y1(x);
for i in 1..n {
if b.is_infinite() {
break;
}
// if high_word(b) != 0xfff00000 {
// break;
// }
(a, b) = (b, ((2.0 * i as f64) / x) * b - a);
}
b
};
if sign > 0 {
b
} else {
-b
}
}

View File

@@ -0,0 +1,90 @@
// This are somewhat lower precision than the BesselJ and BesselY
use super::bessel_i::bessel_i0;
use super::bessel_i::bessel_i1;
fn bessel_k0(x: f64) -> f64 {
let p1 = -0.57721566;
let p2 = 0.42278420;
let p3 = 0.23069756;
let p4 = 3.488590e-2;
let p5 = 2.62698e-3;
let p6 = 1.0750e-4;
let p7 = 7.4e-6;
let q1 = 1.25331414;
let q2 = -7.832358e-2;
let q3 = 2.189568e-2;
let q4 = -1.062446e-2;
let q5 = 5.87872e-3;
let q6 = -2.51540e-3;
let q7 = 5.3208e-4;
if x <= 0.0 {
return 0.0;
}
if x <= 2.0 {
let y = x * x / 4.0;
(-(x / 2.0).ln() * bessel_i0(x))
+ (p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7))))))
} else {
let y = 2.0 / x;
((-x).exp() / x.sqrt())
* (q1 + y * (q2 + y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * q7))))))
}
}
fn bessel_k1(x: f64) -> f64 {
let p1 = 1.0;
let p2 = 0.15443144;
let p3 = -0.67278579;
let p4 = -0.18156897;
let p5 = -1.919402e-2;
let p6 = -1.10404e-3;
let p7 = -4.686e-5;
let q1 = 1.25331414;
let q2 = 0.23498619;
let q3 = -3.655620e-2;
let q4 = 1.504268e-2;
let q5 = -7.80353e-3;
let q6 = 3.25614e-3;
let q7 = -6.8245e-4;
if x <= 0.0 {
return f64::NAN;
}
if x <= 2.0 {
let y = x * x / 4.0;
((x / 2.0).ln() * bessel_i1(x))
+ (1. / x) * (p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7))))))
} else {
let y = 2.0 / x;
((-x).exp() / x.sqrt())
* (q1 + y * (q2 + y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * q7))))))
}
}
pub(crate) fn bessel_k(n: i32, x: f64) -> f64 {
if x <= 0.0 || n < 0 {
return f64::NAN;
}
if n == 0 {
return bessel_k0(x);
}
if n == 1 {
return bessel_k1(x);
}
// Perform upward recurrence for all x
let tox = 2.0 / x;
let mut bkm = bessel_k0(x);
let mut bk = bessel_k1(x);
for j in 1..n {
(bkm, bk) = (bk, bkm + (j as f64) * tox * bk);
}
bk
}

View File

@@ -0,0 +1,19 @@
pub(crate) const HUGE: f64 = 1e300;
pub(crate) const FRAC_2_SQRT_PI: f64 = 5.641_895_835_477_563e-1;
pub(crate) fn high_word(x: f64) -> i32 {
let [_, _, _, _, a1, a2, a3, a4] = x.to_ne_bytes();
// let binding = x.to_ne_bytes();
// let high = <&[u8; 4]>::try_from(&binding[4..8]).expect("");
i32::from_ne_bytes([a1, a2, a3, a4])
}
pub(crate) fn split_words(x: f64) -> (i32, i32) {
let [a1, a2, a3, a4, b1, b2, b3, b4] = x.to_ne_bytes();
// let binding = x.to_ne_bytes();
// let high = <&[u8; 4]>::try_from(&binding[4..8]).expect("");
(
i32::from_ne_bytes([a1, a2, a3, a4]),
i32::from_ne_bytes([b1, b2, b3, b4]),
)
}

View File

@@ -0,0 +1,14 @@
# Example file creating testing cases for BesselI
using Nemo
CC = AcbField(100)
values = [1, 2, 3, -2, 5, 30, 2e-8]
for value in values
y_acb = besseli(CC(1), CC(value))
real64 = convert(Float64, real(y_acb))
im64 = convert(Float64, real(y_acb))
println("(", value, ", ", real64, "),")
end

View File

@@ -0,0 +1,53 @@
pub(crate) fn erf(x: f64) -> f64 {
let cof = vec![
-1.3026537197817094,
6.419_697_923_564_902e-1,
1.9476473204185836e-2,
-9.561_514_786_808_63e-3,
-9.46595344482036e-4,
3.66839497852761e-4,
4.2523324806907e-5,
-2.0278578112534e-5,
-1.624290004647e-6,
1.303655835580e-6,
1.5626441722e-8,
-8.5238095915e-8,
6.529054439e-9,
5.059343495e-9,
-9.91364156e-10,
-2.27365122e-10,
9.6467911e-11,
2.394038e-12,
-6.886027e-12,
8.94487e-13,
3.13092e-13,
-1.12708e-13,
3.81e-16,
7.106e-15,
-1.523e-15,
-9.4e-17,
1.21e-16,
-2.8e-17,
];
let mut d = 0.0;
let mut dd = 0.0;
let x_abs = x.abs();
let t = 2.0 / (2.0 + x_abs);
let ty = 4.0 * t - 2.0;
for j in (1..=cof.len() - 1).rev() {
let tmp = d;
d = ty * d - dd + cof[j];
dd = tmp;
}
let res = t * f64::exp(-x_abs * x_abs + 0.5 * (cof[0] + ty * d) - dd);
if x < 0.0 {
res - 1.0
} else {
1.0 - res
}
}

View File

@@ -0,0 +1,16 @@
mod bessel_i;
mod bessel_j0_y0;
mod bessel_j1_y1;
mod bessel_jn_yn;
mod bessel_k;
mod bessel_util;
mod erf;
#[cfg(test)]
mod test_bessel;
pub(crate) use bessel_i::bessel_i;
pub(crate) use bessel_jn_yn::jn as bessel_j;
pub(crate) use bessel_jn_yn::yn as bessel_y;
pub(crate) use bessel_k::bessel_k;
pub(crate) use erf::erf;

View File

@@ -0,0 +1,183 @@
use crate::functions::engineering::transcendental::bessel_k;
use super::{
bessel_i::bessel_i,
bessel_j0_y0::{j0, y0},
bessel_j1_y1::j1,
bessel_jn_yn::{jn, yn},
};
const EPS: f64 = 1e-13;
const EPS_LOW: f64 = 1e-6;
// Known values computed with Arb via Nemo.jl in Julia
// You can also use Mathematica
/// But please do not use Excel or any other software without arbitrary precision
fn numbers_are_close(a: f64, b: f64) -> bool {
if a == b {
// avoid underflow if a = b = 0.0
return true;
}
(a - b).abs() / ((a * a + b * b).sqrt()) < EPS
}
fn numbers_are_somewhat_close(a: f64, b: f64) -> bool {
if a == b {
// avoid underflow if a = b = 0.0
return true;
}
(a - b).abs() / ((a * a + b * b).sqrt()) < EPS_LOW
}
#[test]
fn bessel_j0_known_values() {
let cases = [
(2.4, 0.002507683297243813),
(0.5, 0.9384698072408129),
(1.0, 0.7651976865579666),
(1.12345, 0.7084999488947348),
(27.0, 0.07274191800588709),
(33.0, 0.09727067223550946),
(2e-4, 0.9999999900000001),
(0.0, 1.0),
(1e10, 2.175591750246892e-6),
];
for (value, known) in cases {
let f = j0(value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for j0({value})"
);
}
}
#[test]
fn bessel_y0_known_values() {
let cases = [
(2.4, 0.5104147486657438),
(0.5, -0.4445187335067065),
(1.0, 0.08825696421567692),
(1.12345, 0.1783162909790613),
(27.0, 0.1352149762078722),
(33.0, 0.0991348255208796),
(2e-4, -5.496017824512429),
(1e10, -7.676508175792937e-6),
(1e-300, -439.8351636227653),
];
for (value, known) in cases {
let f = y0(value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for y0({value})"
);
}
assert!(y0(0.0).is_infinite());
}
#[test]
fn bessel_j1_known_values() {
// Values computed with Maxima, the computer algebra system
// TODO: Recompute
let cases = [
(2.4, 0.5201852681819311),
(0.5, 0.2422684576748738),
(1.0, 0.4400505857449335),
(1.17232, 0.4910665691824317),
(27.5, 0.1521418932046569),
(42.0, -0.04599388822188721),
(3e-5, 1.499999999831249E-5),
(350.0, -0.02040531295214455),
(0.0, 0.0),
(1e12, -7.913802683850441e-7),
];
for (value, known) in cases {
let f = j1(value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for j1({value})"
);
}
}
#[test]
fn bessel_jn_known_values() {
// Values computed with Maxima, the computer algebra system
// TODO: Recompute
let cases = [
(3, 0.5, 0.002_563_729_994_587_244),
(4, 0.5, 0.000_160_736_476_364_287_6),
(-3, 0.5, -0.002_563_729_994_587_244),
(-4, 0.5, 0.000_160_736_476_364_287_6),
(3, 30.0, 0.129211228759725),
(-3, 30.0, -0.129211228759725),
(4, 30.0, -0.052609000321320355),
(20, 30.0, 0.0048310199934040645),
(7, 0.0, 0.0),
];
for (n, value, known) in cases {
let f = jn(n, value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for jn({n}, {value})"
);
}
}
#[test]
fn bessel_yn_known_values() {
let cases = [
(3, 0.5, -42.059494304723883),
(4, 0.5, -499.272_560_819_512_3),
(-3, 0.5, 42.059494304723883),
(-4, 0.5, -499.272_560_819_512_3),
(3, 35.0, -0.13191405300596323),
(-12, 12.2, -0.310438011314211),
(7, 1e12, 1.016_712_505_197_956_3e-7),
(35, 3.0, -6.895_879_073_343_495e31),
];
for (n, value, known) in cases {
let f = yn(n, value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for yn({n}, {value})"
);
}
}
#[test]
fn bessel_in_known_values() {
let cases = [
(1, 0.5, 0.2578943053908963),
(3, 0.5, 0.002645111968990286),
(7, 0.2, 1.986608521182497e-11),
(7, 0.0, 0.0),
(0, -0.5, 1.0634833707413236),
// worse case scenario
(0, 3.7499, 9.118167894541882),
(0, 3.7501, 9.119723897590003),
];
for (n, value, known) in cases {
let f = bessel_i(n, value);
assert!(
numbers_are_somewhat_close(f, known),
"Got: {f}, expected: {known} for in({n}, {value})"
);
}
}
#[test]
fn bessel_kn_known_values() {
let cases = [
(1, 0.5, 1.656441120003301),
(0, 0.5, 0.9244190712276659),
(3, 0.5, 62.05790952993026),
];
for (n, value, known) in cases {
let f = bessel_k(n, value);
assert!(
numbers_are_somewhat_close(f, known),
"Got: {f}, expected: {known} for kn({n}, {value})"
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,255 @@
use crate::expressions::token::Error;
// Here we use some numerical routines to solve for some functions:
// RATE, IRR, XIRR
// We use a combination of heuristics, bisection and Newton-Raphson
// If you want to improve on this methods you probably would want to find failing tests first
// From Microsoft docs:
// https://support.microsoft.com/en-us/office/rate-function-9f665657-4a7e-4bb7-a030-83fc59e748ce
// Returns the interest rate per period of an annuity.
// RATE is calculated by iteration (using Newton-Raphson) and can have zero or more solutions.
// If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
// RATE returns the #NUM! error value.
// NOTE: We need a better algorithm here
pub(crate) fn compute_rate(
pv: f64,
fv: f64,
nper: f64,
pmt: f64,
annuity_type: i32,
guess: f64,
) -> Result<f64, (Error, String)> {
let mut rate = guess;
// Excel _claims_ to do 20 iterations, but that will have tests failing
let max_iterations = 50;
let eps = 0.0000001;
let annuity_type = annuity_type as f64;
if guess <= -1.0 {
return Err((Error::VALUE, "Rate initial guess must be > -1".to_string()));
}
for _ in 1..=max_iterations {
let t = (1.0 + rate).powf(nper - 1.0);
let tt = t * (1.0 + rate);
let f = pv * tt + pmt * (1.0 + rate * annuity_type) * (tt - 1.0) / rate + fv;
let f_prime = pv * nper * t - pmt * (tt - 1.0) / (rate * rate)
+ pmt * (1.0 + rate * annuity_type) * t * nper / rate;
let new_rate = rate - f / f_prime;
if new_rate <= -1.0 {
return Err((Error::NUM, "Failed to converge".to_string()));
}
if (new_rate - rate).abs() < eps {
return Ok(new_rate);
}
rate = new_rate;
}
Err((Error::NUM, "Failed to converge".to_string()))
}
pub(crate) fn compute_npv(rate: f64, values: &[f64]) -> Result<f64, (Error, String)> {
let mut npv = 0.0;
for (i, item) in values.iter().enumerate() {
npv += item / (1.0 + rate).powi(i as i32 + 1)
}
Ok(npv)
}
// Tries to solve npv(r, values) = 0 for r given values
// Uses a bit of heuristics:
// * First tries Newton-Raphson around the guess
// * Failing that uses bisection and bracketing
// * If that fails (no root found of the interval) uses Newton-Raphson around the edges
// Values for x1, x2 and the guess for N-R are fine tuned using heuristics
pub(crate) fn compute_irr(values: &[f64], guess: f64) -> Result<f64, (Error, String)> {
if guess <= -1.0 {
return Err((Error::VALUE, "Rate initial guess must be > -1".to_string()));
}
// The values cannot be all positive or all negative
if values.iter().all(|&x| x >= 0.0) || values.iter().all(|&x| x <= 0.0) {
return Err((Error::NUM, "Failed to converge".to_string()));
}
if let Ok(f) = compute_irr_newton_raphson(values, guess) {
return Ok(f);
};
// We try bisection
let max_iterations = 50;
let eps = 1e-10;
let x1 = -0.99999;
let x2 = 100.0;
let f1 = compute_npv(x1, values)?;
let f2 = compute_npv(x2, values)?;
if f1 * f2 > 0.0 {
// The root is not within the limits or there are two roots
// We try Newton-Raphson a bit above the upper limit and a bit below the lower limit
if let Ok(f) = compute_irr_newton_raphson(values, 200.0) {
return Ok(f);
};
if let Ok(f) = compute_irr_newton_raphson(values, -2.0) {
return Ok(f);
};
return Err((Error::NUM, "Failed to converge".to_string()));
}
let (mut rtb, mut dx) = if f1 < 0.0 {
(x1, x2 - x1)
} else {
(x2, x1 - x2)
};
for _ in 1..max_iterations {
dx *= 0.5;
let x_mid = rtb + dx;
let f_mid = compute_npv(x_mid, values)?;
if f_mid <= 0.0 {
rtb = x_mid;
}
if f_mid.abs() < eps || dx.abs() < eps {
return Ok(x_mid);
}
}
Err((Error::NUM, "Failed to converge".to_string()))
}
fn compute_npv_prime(rate: f64, values: &[f64]) -> Result<f64, (Error, String)> {
let mut npv = 0.0;
for (i, item) in values.iter().enumerate() {
npv += -item * (i as f64 + 1.0) / (1.0 + rate).powi(i as i32 + 2)
}
if npv.is_infinite() || npv.is_nan() {
return Err((Error::NUM, "NaN".to_string()));
}
Ok(npv)
}
fn compute_irr_newton_raphson(values: &[f64], guess: f64) -> Result<f64, (Error, String)> {
let mut irr = guess;
let max_iterations = 50;
let eps = 1e-8;
for _ in 1..=max_iterations {
let f = compute_npv(irr, values)?;
let f_prime = compute_npv_prime(irr, values)?;
let new_irr = irr - f / f_prime;
if (new_irr - irr).abs() < eps {
return Ok(new_irr);
}
irr = new_irr;
}
Err((Error::NUM, "Failed to converge".to_string()))
}
// Formula is:
// $$\sum_{i=1}^n\frac{v_i}{(1+r)^{\frac{(d_j-d_1)}{365}}}$$
// where $v_i$ is the ith-1 value and $d_i$ is the ith-1 date
pub(crate) fn compute_xnpv(
rate: f64,
values: &[f64],
dates: &[f64],
) -> Result<f64, (Error, String)> {
let mut xnpv = values[0];
let d0 = dates[0];
let n = values.len();
for i in 1..n {
let vi = values[i];
let di = dates[i];
xnpv += vi / ((1.0 + rate).powf((di - d0) / 365.0))
}
if xnpv.is_infinite() || xnpv.is_nan() {
return Err((Error::NUM, "NaN".to_string()));
}
Ok(xnpv)
}
fn compute_xnpv_prime(rate: f64, values: &[f64], dates: &[f64]) -> Result<f64, (Error, String)> {
let mut xnpv = 0.0;
let d0 = dates[0];
let n = values.len();
for i in 1..n {
let vi = values[i];
let di = dates[i];
let ratio = (di - d0) / 365.0;
let power = (1.0 + rate).powf(ratio + 1.0);
xnpv -= vi * ratio / power;
}
if xnpv.is_infinite() || xnpv.is_nan() {
return Err((Error::NUM, "NaN".to_string()));
}
Ok(xnpv)
}
fn compute_xirr_newton_raphson(
values: &[f64],
dates: &[f64],
guess: f64,
) -> Result<f64, (Error, String)> {
let mut xirr = guess;
let max_iterations = 100;
let eps = 1e-7;
for _ in 1..=max_iterations {
let f = compute_xnpv(xirr, values, dates)?;
let f_prime = compute_xnpv_prime(xirr, values, dates)?;
let new_xirr = xirr - f / f_prime;
if (new_xirr - xirr).abs() < eps {
return Ok(new_xirr);
}
xirr = new_xirr;
}
Err((Error::NUM, "Failed to converge".to_string()))
}
// NOTES:
// 1. If the cash-flows (value[i] for i > 0) are always of the same sign, the function is monotonous
// 3. Say (d_max, v_max) are the pair where d_max is the largest date,
// then if v_max and v_0 have different signs, it's guaranteed there is a zero
// The algorithm needs to be improved but it works so far in all practical cases
pub(crate) fn compute_xirr(
values: &[f64],
dates: &[f64],
guess: f64,
) -> Result<f64, (Error, String)> {
if guess <= -1.0 {
return Err((Error::VALUE, "Rate initial guess must be > -1".to_string()));
}
// The values cannot be all positive or all negative
if values.iter().all(|&x| x >= 0.0) || values.iter().all(|&x| x <= 0.0) {
return Err((Error::NUM, "Failed to converge".to_string()));
}
if let Ok(f) = compute_xirr_newton_raphson(values, dates, guess) {
return Ok(f);
};
// We try bisection
let max_iterations = 50;
let eps = 1e-8;
// This will miss 0's very close to -1
let x1 = -0.9999;
let x2 = 100.0;
let f1 = compute_xnpv(x1, values, dates)?;
let f2 = compute_xnpv(x2, values, dates)?;
if f1 * f2 > 0.0 {
// The root is not within the limits (or there are two roots)
// We try Newton-Raphson above the upper limit
// (we cannot go to the left of -1)
if let Ok(f) = compute_xirr_newton_raphson(values, dates, 200.0) {
return Ok(f);
};
return Err((Error::NUM, "Failed to converge".to_string()));
}
let (mut rtb, mut dx) = if f1 < 0.0 {
(x1, x2 - x1)
} else {
(x2, x1 - x2)
};
for _ in 1..max_iterations {
dx *= 0.5;
let x_mid = rtb + dx;
let f_mid = compute_xnpv(x_mid, values, dates)?;
if f_mid <= 0.0 {
rtb = x_mid;
}
if f_mid.abs() < eps || dx.abs() < eps {
return Ok(x_mid);
}
}
Err((Error::NUM, "Failed to converge".to_string()))
}

View File

@@ -0,0 +1,296 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::{Model, ParsedDefinedName},
};
impl Model {
pub(crate) fn fn_isnumber(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Number(_) => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_istext(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::String(_) => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_isnontext(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::String(_) => return CalcResult::Boolean(false),
_ => {
return CalcResult::Boolean(true);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_islogical(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Boolean(_) => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_isblank(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::EmptyCell => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_iserror(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { .. } => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_iserr(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { error, .. } => {
if Error::NA == error {
return CalcResult::Boolean(false);
} else {
return CalcResult::Boolean(true);
}
}
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_isna(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { error, .. } => {
if error == Error::NA {
return CalcResult::Boolean(true);
} else {
return CalcResult::Boolean(false);
}
}
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
// Returns true if it is a reference or evaluates to a reference
// But DOES NOT evaluate
pub(crate) fn fn_isref(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match &args[0] {
Node::ReferenceKind { .. } | Node::RangeKind { .. } | Node::OpRangeKind { .. } => {
CalcResult::Boolean(true)
}
Node::FunctionKind { kind, args: _ } => CalcResult::Boolean(kind.returns_reference()),
_ => CalcResult::Boolean(false),
}
}
pub(crate) fn fn_isodd(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f.abs().trunc() as i64,
Err(s) => return s,
};
CalcResult::Boolean(value % 2 == 1)
}
pub(crate) fn fn_iseven(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f.abs().trunc() as i64,
Err(s) => return s,
};
CalcResult::Boolean(value % 2 == 0)
}
// ISFORMULA arg needs to be a reference or something that evaluates to a reference
pub(crate) fn fn_isformula(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
if let CalcResult::Range { left, right } = self.evaluate_node_with_reference(&args[0], cell)
{
if left.sheet != right.sheet {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "3D ranges not supported".to_string(),
};
}
if left.row != right.row && left.column != right.column {
// FIXME: Implicit intersection or dynamic arrays
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "argument must be a reference to a single cell".to_string(),
};
}
let is_formula = if let Ok(f) = self.cell_formula(left.sheet, left.row, left.column) {
f.is_some()
} else {
false
};
CalcResult::Boolean(is_formula)
} else {
CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a reference".to_string(),
}
}
}
pub(crate) fn fn_errortype(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { error, .. } => {
match error {
Error::NULL => CalcResult::Number(1.0),
Error::DIV => CalcResult::Number(2.0),
Error::VALUE => CalcResult::Number(3.0),
Error::REF => CalcResult::Number(4.0),
Error::NAME => CalcResult::Number(5.0),
Error::NUM => CalcResult::Number(6.0),
Error::NA => CalcResult::Number(7.0),
Error::SPILL => CalcResult::Number(9.0),
Error::CALC => CalcResult::Number(14.0),
// IronCalc specific
Error::ERROR => CalcResult::Number(101.0),
Error::NIMPL => CalcResult::Number(102.0),
Error::CIRC => CalcResult::Number(104.0),
// Missing from Excel
// #GETTING_DATA => 8
// #CONNECT => 10
// #BLOCKED => 11
// #UNKNOWN => 12
// #FIELD => 13
// #EXTERNAL => 19
}
}
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not an error".to_string(),
},
}
}
// Excel believes for some reason that TYPE(A1:A7) is an array formula
// Although we evaluate the same as Excel we cannot, ATM import this from excel
pub(crate) fn fn_type(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::String(_) => CalcResult::Number(2.0),
CalcResult::Number(_) => CalcResult::Number(1.0),
CalcResult::Boolean(_) => CalcResult::Number(4.0),
CalcResult::Error { .. } => CalcResult::Number(16.0),
CalcResult::Range { .. } => CalcResult::Number(64.0),
CalcResult::EmptyCell => CalcResult::Number(1.0),
CalcResult::EmptyArg => {
// This cannot happen
CalcResult::Number(1.0)
}
}
}
pub(crate) fn fn_sheet(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let arg_count = args.len();
if arg_count > 1 {
return CalcResult::new_args_number_error(cell);
}
if arg_count == 0 {
// Sheets are 0-indexed`
return CalcResult::Number(cell.sheet as f64 + 1.0);
}
// The arg could be a defined name or a table
let arg = &args[0];
if let Node::VariableKind(name) = arg {
// Let's see if it is a defined name
if let Some(defined_name) = self.parsed_defined_names.get(&(None, name.to_lowercase()))
{
match defined_name {
ParsedDefinedName::CellReference(reference) => {
return CalcResult::Number(reference.sheet as f64 + 1.0)
}
ParsedDefinedName::RangeReference(range) => {
return CalcResult::Number(range.left.sheet as f64 + 1.0)
}
ParsedDefinedName::InvalidDefinedNameFormula => {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid name".to_string(),
};
}
}
}
// Now let's see if it is a table
for (table_name, table) in &self.workbook.tables {
if table_name == name {
if let Some(sheet_index) = self.get_sheet_index_by_name(&table.sheet_name) {
return CalcResult::Number(sheet_index as f64 + 1.0);
} else {
break;
}
}
}
}
// Now it should be the name of a sheet
let sheet_name = match self.get_string(arg, cell) {
Ok(s) => s,
Err(e) => return e,
};
if let Some(sheet_index) = self.get_sheet_index_by_name(&sheet_name) {
return CalcResult::Number(sheet_index as f64 + 1.0);
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid name".to_string(),
}
}
}

View File

@@ -0,0 +1,321 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::util::compare_values;
impl Model {
pub(crate) fn fn_if(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 || args.len() == 3 {
let cond_result = self.get_boolean(&args[0], cell);
let cond = match cond_result {
Ok(f) => f,
Err(s) => {
return s;
}
};
if cond {
return self.evaluate_node_in_context(&args[1], cell);
} else if args.len() == 3 {
return self.evaluate_node_in_context(&args[2], cell);
} else {
return CalcResult::Boolean(false);
}
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_iferror(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let value = self.evaluate_node_in_context(&args[0], cell);
match value {
CalcResult::Error { .. } => {
return self.evaluate_node_in_context(&args[1], cell);
}
_ => return value,
}
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_ifna(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let value = self.evaluate_node_in_context(&args[0], cell);
if let CalcResult::Error { error, .. } = &value {
if error == &Error::NA {
return self.evaluate_node_in_context(&args[1], cell);
}
}
return value;
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_not(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.get_boolean(&args[0], cell) {
Ok(f) => return CalcResult::Boolean(!f),
Err(s) => {
return s;
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_and(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut true_count = 0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Boolean(b) => {
if !b {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::Number(value) => {
if value == 0.0 {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::String(_value) => {
true_count += 1;
}
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Boolean(b) => {
if !b {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::Number(value) => {
if value == 0.0 {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::String(_value) => {
true_count += 1;
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {}
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
};
}
if true_count == 0 {
return CalcResult::new_error(
Error::VALUE,
cell,
"Boolean values not found".to_string(),
);
}
CalcResult::Boolean(true)
}
pub(crate) fn fn_or(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut result = false;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Boolean(value) => result = value || result,
CalcResult::Number(value) => {
if value != 0.0 {
return CalcResult::Boolean(true);
}
}
CalcResult::String(_value) => {
return CalcResult::Boolean(true);
}
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Boolean(value) => {
result = value || result;
}
CalcResult::Number(value) => {
if value != 0.0 {
return CalcResult::Boolean(true);
}
}
CalcResult::String(_value) => {
return CalcResult::Boolean(true);
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {}
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
};
}
CalcResult::Boolean(result)
}
/// XOR(logical1, [logical]*,...)
/// Logical1 is required, subsequent logical values are optional. Can be logical values, arrays, or references.
/// The result of XOR is TRUE when the number of TRUE inputs is odd and FALSE when the number of TRUE inputs is even.
pub(crate) fn fn_xor(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut true_count = 0;
let mut false_count = 0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Boolean(b) => {
if b {
true_count += 1;
} else {
false_count += 1;
}
}
CalcResult::Number(value) => {
if value != 0.0 {
true_count += 1;
} else {
false_count += 1;
}
}
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Boolean(b) => {
if b {
true_count += 1;
} else {
false_count += 1;
}
}
CalcResult::Number(value) => {
if value != 0.0 {
true_count += 1;
} else {
false_count += 1;
}
}
_ => {}
}
}
}
}
_ => {}
};
}
if true_count == 0 && false_count == 0 {
return CalcResult::new_error(Error::VALUE, cell, "No booleans found".to_string());
}
CalcResult::Boolean(true_count % 2 == 1)
}
/// =SWITCH(expression, case1, value1, [case, value]*, [default])
pub(crate) fn fn_switch(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count < 3 {
return CalcResult::new_args_number_error(cell);
}
// TODO add implicit intersection
let expr = self.evaluate_node_in_context(&args[0], cell);
if expr.is_error() {
return expr;
}
// How many cases we have?
// 3, 4 args -> 1 case
let case_count = (args_count - 1) / 2;
for case_index in 0..case_count {
let case = self.evaluate_node_in_context(&args[2 * case_index + 1], cell);
if case.is_error() {
return case;
}
if compare_values(&expr, &case) == 0 {
return self.evaluate_node_in_context(&args[2 * case_index + 2], cell);
}
}
// None of the cases matched so we return the default
// If there is an even number of args is the last one otherwise is #N/A
if args_count % 2 == 0 {
return self.evaluate_node_in_context(&args[args_count - 1], cell);
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Did not find a match".to_string(),
}
}
/// =IFS(condition1, value, [condition, value]*)
pub(crate) fn fn_ifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count < 2 {
return CalcResult::new_args_number_error(cell);
}
if args_count % 2 != 0 {
// Missing value for last condition
return CalcResult::new_args_number_error(cell);
}
let case_count = args_count / 2;
for case_index in 0..case_count {
let value = self.get_boolean(&args[2 * case_index], cell);
match value {
Ok(b) => {
if b {
return self.evaluate_node_in_context(&args[2 * case_index + 1], cell);
}
}
Err(s) => return s,
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Did not find a match".to_string(),
}
}
}

View File

@@ -0,0 +1,843 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
utils::ParsedReference,
};
use super::util::{compare_values, from_wildcard_to_regex, result_matches_regex, values_are_equal};
impl Model {
pub(crate) fn fn_index(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let row_num;
let col_num;
if args.len() == 3 {
row_num = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if row_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
col_num = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if col_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
} else if args.len() == 2 {
row_num = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if row_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
col_num = -1.0;
} else {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Range { left, right } => {
let row;
let column;
if (col_num + 1.0).abs() < f64::EPSILON {
if left.row == right.row {
column = left.column + (row_num as i32) - 1;
row = left.row;
} else {
column = left.column;
row = left.row + (row_num as i32) - 1;
}
} else {
row = left.row + (row_num as i32) - 1;
column = left.column + (col_num as i32) - 1;
}
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Wrong reference".to_string(),
};
}
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Wrong reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Expecting a Range".to_string(),
},
}
}
// MATCH(lookup_value, lookup_array, [match_type])
// The MATCH function syntax has the following arguments:
// * lookup_value Required. The value that you want to match in lookup_array.
// The lookup_value argument can be a value (number, text, or logical value)
// or a cell reference to a number, text, or logical value.
// * lookup_array Required. The range of cells being searched.
// * match_type Optional. The number -1, 0, or 1.
// The match_type argument specifies how Excel matches lookup_value
// with values in lookup_array. The default value for this argument is 1.
// NOTE: Please read the caveat above in binary search
pub(crate) fn fn_match(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 3 || args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let target = self.evaluate_node_in_context(&args[0], cell);
if target.is_error() {
return target;
}
if matches!(target, CalcResult::EmptyCell) {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Cannot match empty cell".to_string(),
};
}
let match_type = if args.len() == 3 {
match self.get_number(&args[2], cell) {
Ok(v) => v as i32,
Err(s) => return s,
}
} else {
1
};
let match_range = self.evaluate_node_in_context(&args[1], cell);
match match_range {
CalcResult::Range { left, right } => {
match match_type {
-1 => {
// We apply binary search leftmost for value in the range
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let row;
let column;
if is_row_vector {
row = left.row + m;
column = left.column;
} else {
column = left.column + m;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
if compare_values(&value, &target) >= 0 {
l = m + 1;
} else {
r = m;
}
}
// r is the number of elements less than target in the vector
// If target is less than the minimum return #N/A
if l == 0 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
// Now l points to the leftmost element
CalcResult::Number(l as f64)
}
0 => {
// We apply linear search
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &target {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| values_are_equal(x, &target))
};
for l in 0..n {
let row;
let column;
if is_row_vector {
row = left.row + l;
column = left.column;
} else {
column = left.column + l;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
if result_matches(&value) {
return CalcResult::Number(l as f64 + 1.0);
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
_ => {
// l is the number of elements less than target in the vector
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let l = self.binary_search(&target, &left, &right, is_row_vector);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
CalcResult::Number(l as f64 + 1.0)
}
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid".to_string(),
},
}
}
/// HLOOKUP(lookup_value, table_array, row_index, [is_sorted])
/// We look for `lookup_value` in the first row of table array
/// We return the value in row `row_index` of the same column in `table_array`
/// `is_sorted` is true by default and assumes that values in first row are ordered
pub(crate) fn fn_hlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 4 || args.len() < 3 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
let row_index = match self.get_number(&args[2], cell) {
Ok(v) => v.floor() as i32,
Err(s) => return s,
};
let is_sorted = if args.len() == 4 {
match self.get_boolean(&args[3], cell) {
Ok(v) => v,
Err(s) => return s,
}
} else {
true
};
let range = self.evaluate_node_in_context(&args[1], cell);
match range {
CalcResult::Range { left, right } => {
if is_sorted {
// This assumes the values in row are in order
let l = self.binary_search(&lookup_value, &left, &right, false);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
let row = left.row + row_index - 1;
let column = left.column + l;
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
} else {
// Linear search for exact match
let n = right.column - left.column + 1;
let row = left.row + row_index - 1;
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, &lookup_value) == 0)
};
for l in 0..n {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row,
column: left.column + l,
});
if result_matches(&value) {
return self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column: left.column + l,
});
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
}
error @ CalcResult::Error { .. } => error,
CalcResult::String(_) => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
/// VLOOKUP(lookup_value, table_array, row_index, [is_sorted])
/// We look for `lookup_value` in the first column of table array
/// We return the value in column `column_index` of the same row in `table_array`
/// `is_sorted` is true by default and assumes that values in first column are ordered
pub(crate) fn fn_vlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 4 || args.len() < 3 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
let column_index = match self.get_number(&args[2], cell) {
Ok(v) => v.floor() as i32,
Err(s) => return s,
};
let is_sorted = if args.len() == 4 {
match self.get_boolean(&args[3], cell) {
Ok(v) => v,
Err(s) => return s,
}
} else {
true
};
let range = self.evaluate_node_in_context(&args[1], cell);
match range {
CalcResult::Range { left, right } => {
if is_sorted {
// This assumes the values in column are in order
let l = self.binary_search(&lookup_value, &left, &right, true);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
let row = left.row + l;
let column = left.column + column_index - 1;
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
} else {
// Linear search for exact match
let n = right.row - left.row + 1;
let column = left.column + column_index - 1;
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, &lookup_value) == 0)
};
for l in 0..n {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + l,
column: left.column,
});
if result_matches(&value) {
return self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + l,
column,
});
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
}
error @ CalcResult::Error { .. } => error,
CalcResult::String(_) => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
// LOOKUP(lookup_value, lookup_vector, [result_vector])
// Important: The values in lookup_vector must be placed in ascending order:
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
// otherwise, LOOKUP might not return the correct value.
// Uppercase and lowercase text are equivalent.
// TODO: Implement the other form of INDEX:
// INDEX(reference, row_num, [column_num], [area_num])
// NOTE: Please read the caveat above in binary search
pub(crate) fn fn_lookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 3 || args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let target = self.evaluate_node_in_context(&args[0], cell);
if target.is_error() {
return target;
}
let value = self.evaluate_node_in_context(&args[1], cell);
match value {
CalcResult::Range { left, right } => {
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Second argument must be a vector".to_string(),
};
}
let l = self.binary_search(&target, &left, &right, is_row_vector);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
if args.len() == 3 {
let target_range = self.evaluate_node_in_context(&args[2], cell);
match target_range {
CalcResult::Range {
left: l1,
right: _r1,
} => {
let row;
let column;
if is_row_vector {
row = l1.row + l;
column = l1.column;
} else {
column = l1.column + l;
row = l1.row;
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
} else {
let row;
let column;
if is_row_vector {
row = left.row + l;
column = left.column;
} else {
column = left.column + l;
row = left.row;
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
// ROW([reference])
// If reference is not present returns the row of the present cell.
// Otherwise returns the row number of reference
pub(crate) fn fn_row(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 1 {
return CalcResult::new_args_number_error(cell);
}
if args.is_empty() {
return CalcResult::Number(cell.row as f64);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number(c.left.row as f64),
Err(s) => s,
}
}
// ROWS(range)
// Returns the number of rows in range
pub(crate) fn fn_rows(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number((c.right.row - c.left.row + 1) as f64),
Err(s) => s,
}
}
// COLUMN([reference])
// If reference is not present returns the column of the present cell.
// Otherwise returns the column number of reference
pub(crate) fn fn_column(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 1 {
return CalcResult::new_args_number_error(cell);
}
if args.is_empty() {
return CalcResult::Number(cell.column as f64);
}
match self.get_reference(&args[0], cell) {
Ok(range) => CalcResult::Number(range.left.column as f64),
Err(s) => s,
}
}
/// CHOOSE(index_num, value1, [value2], ...)
/// Uses index_num to return a value from the list of value arguments.
pub(crate) fn fn_choose(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let index_num = match self.get_number(&args[0], cell) {
Ok(index_num) => index_num as usize,
Err(calc_err) => return calc_err,
};
if index_num < 1 || index_num > (args.len() - 1) {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid index".to_string(),
};
}
self.evaluate_node_with_reference(&args[index_num], cell)
}
// COLUMNS(range)
// Returns the number of columns in range
pub(crate) fn fn_columns(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number((c.right.column - c.left.column + 1) as f64),
Err(s) => s,
}
}
// INDIRECT(ref_tex)
// Returns the reference specified by 'ref_text'
pub(crate) fn fn_indirect(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 2 || args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let value = self.get_string(&args[0], cell);
match value {
Ok(s) => {
if args.len() == 2 {
return CalcResult::Error {
error: Error::NIMPL,
origin: cell,
message: "Not implemented".to_string(),
};
}
let parsed_reference = ParsedReference::parse_reference_formula(
Some(cell.sheet),
&s,
&self.locale,
|name| self.get_sheet_index_by_name(name),
);
let parsed_reference = match parsed_reference {
Ok(reference) => reference,
Err(message) => {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message,
};
}
};
match parsed_reference {
ParsedReference::CellReference(reference) => CalcResult::Range {
left: reference,
right: reference,
},
ParsedReference::Range(left, right) => CalcResult::Range { left, right },
}
}
Err(v) => v,
}
}
// OFFSET(reference, rows, cols, [height], [width])
// Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
// The reference that is returned can be a single cell or a range of cells.
// You can specify the number of rows and the number of columns to be returned.
pub(crate) fn fn_offset(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let l = args.len();
if !(3..=5).contains(&l) {
return CalcResult::new_args_number_error(cell);
}
let reference = match self.get_reference(&args[0], cell) {
Ok(c) => c,
Err(s) => return s,
};
let rows = match self.get_number(&args[1], cell) {
Ok(c) => {
if c < 0.0 {
c.ceil() as i32
} else {
c.floor() as i32
}
}
Err(s) => return s,
};
let cols = match self.get_number(&args[2], cell) {
Ok(c) => {
if c < 0.0 {
c.ceil() as i32
} else {
c.floor() as i32
}
}
Err(s) => return s,
};
let row_start = reference.left.row + rows;
let column_start = reference.left.column + cols;
let width;
let height;
if l == 4 {
height = match self.get_number(&args[3], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
width = reference.right.column - reference.left.column;
} else if l == 5 {
height = match self.get_number(&args[3], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
width = match self.get_number(&args[4], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
} else {
width = reference.right.column - reference.left.column;
height = reference.right.row - reference.left.row;
}
// This is what Excel does
if width == -1 || height == -1 {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
// NB: Excel documentation says that negative values of width and height are not valid
// but in practice they are valid. We follow the documentation and not Excel
if width < -1 || height < -1 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "width and height cannot be negative".to_string(),
};
}
let column_end = column_start + width;
let row_end = row_start + height;
if row_start < 1 || row_end > LAST_ROW || column_start < 1 || column_end > LAST_COLUMN {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let left = CellReference {
sheet: reference.left.sheet,
row: row_start,
column: column_start,
};
let right = CellReference {
sheet: reference.right.sheet,
row: row_end,
column: column_end,
};
CalcResult::Range { left, right }
}
}

View File

@@ -0,0 +1,671 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use std::f64::consts::PI;
#[cfg(not(target_arch = "wasm32"))]
pub fn random() -> f64 {
rand::random()
}
#[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: CellReference) -> 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(CellReference {
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: CellReference) -> 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(CellReference {
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_sum(&mut self, args: &[Node], cell: CellReference) -> 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 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
for row in row1..row2 + 1 {
for column in column1..(column2 + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
result += value;
}
error @ CalcResult::Error { .. } => return error,
_ => {
// 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: CellReference) -> 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 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
for row in row1..row2 + 1 {
for column in column1..(column2 + 1) {
match self.evaluate_cell(CellReference {
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: CellReference) -> 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: CellReference) -> 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: CellReference) -> 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) => f,
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: CellReference) -> 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 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: CellReference) -> 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 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)
}
}
pub(crate) fn fn_sin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.sin();
CalcResult::Number(result)
}
pub(crate) fn fn_cos(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.cos();
CalcResult::Number(result)
}
pub(crate) fn fn_tan(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.tan();
CalcResult::Number(result)
}
pub(crate) fn fn_sinh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.sinh();
CalcResult::Number(result)
}
pub(crate) fn fn_cosh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.cosh();
CalcResult::Number(result)
}
pub(crate) fn fn_tanh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.tanh();
CalcResult::Number(result)
}
pub(crate) fn fn_asin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.asin();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ASIN".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_acos(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.acos();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for COS".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_atan(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.atan();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ATAN".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_asinh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.asinh();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ASINH".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_acosh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.acosh();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ACOSH".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_atanh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.atanh();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ATANH".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_pi(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
CalcResult::Number(PI)
}
pub(crate) fn fn_abs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(value.abs())
}
pub(crate) fn fn_sqrtpi(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
if value < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Argument of SQRTPI should be >= 0".to_string(),
};
}
CalcResult::Number((value * PI).sqrt())
}
pub(crate) fn fn_sqrt(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
if value < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Argument of SQRT should be >= 0".to_string(),
};
}
CalcResult::Number(value.sqrt())
}
pub(crate) fn fn_atan2(&mut self, args: &[Node], cell: CellReference) -> 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_power(&mut self, args: &[Node], cell: CellReference) -> 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: CellReference) -> 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: CellReference) -> 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())
}
}

930
base/src/functions/mod.rs Normal file
View File

@@ -0,0 +1,930 @@
use core::fmt;
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{parser::Node, token::Error},
model::Model,
};
pub(crate) mod binary_search;
mod date_and_time;
mod engineering;
mod financial;
mod financial_util;
mod information;
mod logical;
mod lookup_and_reference;
mod mathematical;
mod statistical;
mod subtotal;
mod text;
mod text_util;
pub(crate) mod util;
mod xlookup;
/// List of all implemented functions
#[derive(PartialEq, Clone, Debug)]
pub enum Function {
// Logical
And,
False,
If,
Iferror,
Ifna,
Ifs,
Not,
Or,
Switch,
True,
Xor,
// Mathematical and trigonometry
Abs,
Acos,
Acosh,
Asin,
Asinh,
Atan,
Atan2,
Atanh,
Choose,
Column,
Columns,
Cos,
Cosh,
Max,
Min,
Pi,
Power,
Product,
Rand,
Randbetween,
Round,
Rounddown,
Roundup,
Sin,
Sinh,
Sqrt,
Sqrtpi,
Sum,
Sumif,
Sumifs,
Tan,
Tanh,
// Information
ErrorType,
Isblank,
Iserr,
Iserror,
Iseven,
Isformula,
Islogical,
Isna,
Isnontext,
Isnumber,
Isodd,
Isref,
Istext,
Na,
Sheet,
Type,
// Lookup and reference
Hlookup,
Index,
Indirect,
Lookup,
Match,
Offset,
Row,
Rows,
Vlookup,
Xlookup,
// Text
Concat,
Concatenate,
Exact,
Find,
Left,
Len,
Lower,
Mid,
Rept,
Right,
Search,
Substitute,
T,
Text,
Textafter,
Textbefore,
Textjoin,
Trim,
Upper,
Value,
Valuetotext,
// Statistical
Average,
Averagea,
Averageif,
Averageifs,
Count,
Counta,
Countblank,
Countif,
Countifs,
Maxifs,
Minifs,
// Date and time
Date,
Day,
Edate,
Eomonth,
Month,
Now,
Today,
Year,
// Financial
Cumipmt,
Cumprinc,
Db,
Ddb,
Dollarde,
Dollarfr,
Effect,
Fv,
Ipmt,
Irr,
Ispmt,
Mirr,
Nominal,
Nper,
Npv,
Pduration,
Pmt,
Ppmt,
Pv,
Rate,
Rri,
Sln,
Syd,
Tbilleq,
Tbillprice,
Tbillyield,
Xirr,
Xnpv,
// Engineering: Bessel and transcendental functions
Besseli,
Besselj,
Besselk,
Bessely,
Erf,
Erfc,
ErfcPrecise,
ErfPrecise,
// Engineering: Number systems
Bin2dec,
Bin2hex,
Bin2oct,
Dec2Bin,
Dec2hex,
Dec2oct,
Hex2bin,
Hex2dec,
Hex2oct,
Oct2bin,
Oct2dec,
Oct2hex,
// Engineering: Bit functions
Bitand,
Bitlshift,
Bitor,
Bitrshift,
Bitxor,
// Engineering: Complex functions
Complex,
Imabs,
Imaginary,
Imargument,
Imconjugate,
Imcos,
Imcosh,
Imcot,
Imcsc,
Imcsch,
Imdiv,
Imexp,
Imln,
Imlog10,
Imlog2,
Impower,
Improduct,
Imreal,
Imsec,
Imsech,
Imsin,
Imsinh,
Imsqrt,
Imsub,
Imsum,
Imtan,
// Engineering: Misc function
Convert,
Delta,
Gestep,
Subtotal,
}
impl Function {
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
pub fn to_xlsx_string(&self) -> String {
match self {
Function::Concat => "_xlfn.CONCAT".to_string(),
Function::Ifna => "_xlfn.IFNA".to_string(),
Function::Ifs => "_xlfn.IFS".to_string(),
Function::Maxifs => "_xlfn.MAXIFS".to_string(),
Function::Minifs => "_xlfn.MINIFS".to_string(),
Function::Switch => "_xlfn.SWITCH".to_string(),
Function::Xlookup => "_xlfn.XLOOKUP".to_string(),
Function::Xor => "_xlfn.XOR".to_string(),
Function::Textbefore => "_xlfn.TEXTBEFORE".to_string(),
Function::Textafter => "_xlfn.TEXTAFTER".to_string(),
Function::Textjoin => "_xlfn.TEXTJOIN".to_string(),
Function::Rri => "_xlfn.RRI".to_string(),
Function::Pduration => "_xlfn.PDURATION".to_string(),
Function::Bitand => "_xlfn.BITAND".to_string(),
Function::Bitor => "_xlfn.BITOR".to_string(),
Function::Bitxor => "_xlfn.BITXOR".to_string(),
Function::Bitlshift => "_xlfn.BITLSHIFT".to_string(),
Function::Bitrshift => "_xlfn.BITRSHIFT".to_string(),
Function::Imtan => "_xlfn.IMTAN".to_string(),
Function::Imsinh => "_xlfn.IMSINH".to_string(),
Function::Imcosh => "_xlfn.IMCOSH".to_string(),
Function::Imcot => "_xlfn.IMCOT".to_string(),
Function::Imcsc => "_xlfn.IMCSC".to_string(),
Function::Imcsch => "_xlfn.IMCSCH".to_string(),
Function::Imsec => "_xlfn.IMSEC".to_string(),
Function::ErfcPrecise => "_xlfn.ERFC.PRECISE".to_string(),
Function::ErfPrecise => "_xlfn.ERF.PRECISE".to_string(),
Function::Valuetotext => "_xlfn.VALUETOTEXT".to_string(),
Function::Isformula => "_xlfn.ISFORMULA".to_string(),
Function::Sheet => "_xlfn.SHEET".to_string(),
_ => self.to_string(),
}
}
pub(crate) fn returns_reference(&self) -> bool {
matches!(self, Function::Indirect | Function::Offset)
}
/// Gets the function from the name.
/// Note that in Excel some (modern) functions are prefixed by `_xlfn.`
pub fn get_function(name: &str) -> Option<Function> {
match name.to_ascii_uppercase().as_str() {
"AND" => Some(Function::And),
"FALSE" => Some(Function::False),
"IF" => Some(Function::If),
"IFERROR" => Some(Function::Iferror),
"IFNA" | "_XLFN.IFNA" => Some(Function::Ifna),
"IFS" | "_XLFN.IFS" => Some(Function::Ifs),
"NOT" => Some(Function::Not),
"OR" => Some(Function::Or),
"SWITCH" | "_XLFN.SWITCH" => Some(Function::Switch),
"TRUE" => Some(Function::True),
"XOR" | "_XLFN.XOR" => Some(Function::Xor),
"SIN" => Some(Function::Sin),
"COS" => Some(Function::Cos),
"TAN" => Some(Function::Tan),
"ASIN" => Some(Function::Asin),
"ACOS" => Some(Function::Acos),
"ATAN" => Some(Function::Atan),
"SINH" => Some(Function::Sinh),
"COSH" => Some(Function::Cosh),
"TANH" => Some(Function::Tanh),
"ASINH" => Some(Function::Asinh),
"ACOSH" => Some(Function::Acosh),
"ATANH" => Some(Function::Atanh),
"PI" => Some(Function::Pi),
"ABS" => Some(Function::Abs),
"SQRT" => Some(Function::Sqrt),
"SQRTPI" => Some(Function::Sqrtpi),
"POWER" => Some(Function::Power),
"ATAN2" => Some(Function::Atan2),
"MAX" => Some(Function::Max),
"MIN" => Some(Function::Min),
"PRODUCT" => Some(Function::Product),
"RAND" => Some(Function::Rand),
"RANDBETWEEN" => Some(Function::Randbetween),
"ROUND" => Some(Function::Round),
"ROUNDDOWN" => Some(Function::Rounddown),
"ROUNDUP" => Some(Function::Roundup),
"SUM" => Some(Function::Sum),
"SUMIF" => Some(Function::Sumif),
"SUMIFS" => Some(Function::Sumifs),
// Lookup and Reference
"CHOOSE" => Some(Function::Choose),
"COLUMN" => Some(Function::Column),
"COLUMNS" => Some(Function::Columns),
"INDEX" => Some(Function::Index),
"INDIRECT" => Some(Function::Indirect),
"HLOOKUP" => Some(Function::Hlookup),
"LOOKUP" => Some(Function::Lookup),
"MATCH" => Some(Function::Match),
"OFFSET" => Some(Function::Offset),
"ROW" => Some(Function::Row),
"ROWS" => Some(Function::Rows),
"VLOOKUP" => Some(Function::Vlookup),
"XLOOKUP" | "_XLFN.XLOOKUP" => Some(Function::Xlookup),
"CONCATENATE" => Some(Function::Concatenate),
"EXACT" => Some(Function::Exact),
"VALUE" => Some(Function::Value),
"T" => Some(Function::T),
"VALUETOTEXT" | "_XLFN.VALUETOTEXT" => Some(Function::Valuetotext),
"CONCAT" | "_XLFN.CONCAT" => Some(Function::Concat),
"FIND" => Some(Function::Find),
"LEFT" => Some(Function::Left),
"LEN" => Some(Function::Len),
"LOWER" => Some(Function::Lower),
"MID" => Some(Function::Mid),
"RIGHT" => Some(Function::Right),
"SEARCH" => Some(Function::Search),
"TEXT" => Some(Function::Text),
"TRIM" => Some(Function::Trim),
"UPPER" => Some(Function::Upper),
"REPT" => Some(Function::Rept),
"TEXTAFTER" | "_XLFN.TEXTAFTER" => Some(Function::Textafter),
"TEXTBEFORE" | "_XLFN.TEXTBEFORE" => Some(Function::Textbefore),
"TEXTJOIN" | "_XLFN.TEXTJOIN" => Some(Function::Textjoin),
"SUBSTITUTE" => Some(Function::Substitute),
"ISNUMBER" => Some(Function::Isnumber),
"ISNONTEXT" => Some(Function::Isnontext),
"ISTEXT" => Some(Function::Istext),
"ISLOGICAL" => Some(Function::Islogical),
"ISBLANK" => Some(Function::Isblank),
"ISERR" => Some(Function::Iserr),
"ISERROR" => Some(Function::Iserror),
"ISNA" => Some(Function::Isna),
"NA" => Some(Function::Na),
"ISREF" => Some(Function::Isref),
"ISODD" => Some(Function::Isodd),
"ISEVEN" => Some(Function::Iseven),
"ERROR.TYPE" => Some(Function::ErrorType),
"ISFORMULA" | "_XLFN.ISFORMULA" => Some(Function::Isformula),
"TYPE" => Some(Function::Type),
"SHEET" | "_XLFN.SHEET" => Some(Function::Sheet),
"AVERAGE" => Some(Function::Average),
"AVERAGEA" => Some(Function::Averagea),
"AVERAGEIF" => Some(Function::Averageif),
"AVERAGEIFS" => Some(Function::Averageifs),
"COUNT" => Some(Function::Count),
"COUNTA" => Some(Function::Counta),
"COUNTBLANK" => Some(Function::Countblank),
"COUNTIF" => Some(Function::Countif),
"COUNTIFS" => Some(Function::Countifs),
"MAXIFS" | "_XLFN.MAXIFS" => Some(Function::Maxifs),
"MINIFS" | "_XLFN.MINIFS" => Some(Function::Minifs),
// Date and Time
"YEAR" => Some(Function::Year),
"DAY" => Some(Function::Day),
"EOMONTH" => Some(Function::Eomonth),
"MONTH" => Some(Function::Month),
"DATE" => Some(Function::Date),
"EDATE" => Some(Function::Edate),
"TODAY" => Some(Function::Today),
"NOW" => Some(Function::Now),
// Financial
"PMT" => Some(Function::Pmt),
"PV" => Some(Function::Pv),
"RATE" => Some(Function::Rate),
"NPER" => Some(Function::Nper),
"FV" => Some(Function::Fv),
"PPMT" => Some(Function::Ppmt),
"IPMT" => Some(Function::Ipmt),
"NPV" => Some(Function::Npv),
"XNPV" => Some(Function::Xnpv),
"MIRR" => Some(Function::Mirr),
"IRR" => Some(Function::Irr),
"XIRR" => Some(Function::Xirr),
"ISPMT" => Some(Function::Ispmt),
"RRI" | "_XLFN.RRI" => Some(Function::Rri),
"SLN" => Some(Function::Sln),
"SYD" => Some(Function::Syd),
"NOMINAL" => Some(Function::Nominal),
"EFFECT" => Some(Function::Effect),
"PDURATION" | "_XLFN.PDURATION" => Some(Function::Pduration),
"TBILLYIELD" => Some(Function::Tbillyield),
"TBILLPRICE" => Some(Function::Tbillprice),
"TBILLEQ" => Some(Function::Tbilleq),
"DOLLARDE" => Some(Function::Dollarde),
"DOLLARFR" => Some(Function::Dollarfr),
"DDB" => Some(Function::Ddb),
"DB" => Some(Function::Db),
"CUMPRINC" => Some(Function::Cumprinc),
"CUMIPMT" => Some(Function::Cumipmt),
"BESSELI" => Some(Function::Besseli),
"BESSELJ" => Some(Function::Besselj),
"BESSELK" => Some(Function::Besselk),
"BESSELY" => Some(Function::Bessely),
"ERF" => Some(Function::Erf),
"ERF.PRECISE" | "_XLFN.ERF.PRECISE" => Some(Function::ErfPrecise),
"ERFC" => Some(Function::Erfc),
"ERFC.PRECISE" | "_XLFN.ERFC.PRECISE" => Some(Function::ErfcPrecise),
"BIN2DEC" => Some(Function::Bin2dec),
"BIN2HEX" => Some(Function::Bin2hex),
"BIN2OCT" => Some(Function::Bin2oct),
"DEC2BIN" => Some(Function::Dec2Bin),
"DEC2HEX" => Some(Function::Dec2hex),
"DEC2OCT" => Some(Function::Dec2oct),
"HEX2BIN" => Some(Function::Hex2bin),
"HEX2DEC" => Some(Function::Hex2dec),
"HEX2OCT" => Some(Function::Hex2oct),
"OCT2BIN" => Some(Function::Oct2bin),
"OCT2DEC" => Some(Function::Oct2dec),
"OCT2HEX" => Some(Function::Oct2hex),
"BITAND" | "_XLFN.BITAND" => Some(Function::Bitand),
"BITLSHIFT" | "_XLFN.BITLSHIFT" => Some(Function::Bitlshift),
"BITOR" | "_XLFN.BITOR" => Some(Function::Bitor),
"BITRSHIFT" | "_XLFN.BITRSHIFT" => Some(Function::Bitrshift),
"BITXOR" | "_XLFN.BITXOR" => Some(Function::Bitxor),
"COMPLEX" => Some(Function::Complex),
"IMABS" => Some(Function::Imabs),
"IMAGINARY" => Some(Function::Imaginary),
"IMARGUMENT" => Some(Function::Imargument),
"IMCONJUGATE" => Some(Function::Imconjugate),
"IMCOS" => Some(Function::Imcos),
"IMCOSH" | "_XLFN.IMCOSH" => Some(Function::Imcosh),
"IMCOT" | "_XLFN.IMCOT" => Some(Function::Imcot),
"IMCSC" | "_XLFN.IMCSC" => Some(Function::Imcsc),
"IMCSCH" | "_XLFN.IMCSCH" => Some(Function::Imcsch),
"IMDIV" => Some(Function::Imdiv),
"IMEXP" => Some(Function::Imexp),
"IMLN" => Some(Function::Imln),
"IMLOG10" => Some(Function::Imlog10),
"IMLOG2" => Some(Function::Imlog2),
"IMPOWER" => Some(Function::Impower),
"IMPRODUCT" => Some(Function::Improduct),
"IMREAL" => Some(Function::Imreal),
"IMSEC" | "_XLFN.IMSEC" => Some(Function::Imsec),
"IMSECH" | "_XLFN.IMSECH" => Some(Function::Imsech),
"IMSIN" => Some(Function::Imsin),
"IMSINH" | "_XLFN.IMSINH" => Some(Function::Imsinh),
"IMSQRT" => Some(Function::Imsqrt),
"IMSUB" => Some(Function::Imsub),
"IMSUM" => Some(Function::Imsum),
"IMTAN" | "_XLFN.IMTAN" => Some(Function::Imtan),
"CONVERT" => Some(Function::Convert),
"DELTA" => Some(Function::Delta),
"GESTEP" => Some(Function::Gestep),
"SUBTOTAL" => Some(Function::Subtotal),
_ => None,
}
}
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Function::And => write!(f, "AND"),
Function::False => write!(f, "FALSE"),
Function::If => write!(f, "IF"),
Function::Iferror => write!(f, "IFERROR"),
Function::Ifna => write!(f, "IFNA"),
Function::Ifs => write!(f, "IFS"),
Function::Not => write!(f, "NOT"),
Function::Or => write!(f, "OR"),
Function::Switch => write!(f, "SWITCH"),
Function::True => write!(f, "TRUE"),
Function::Xor => write!(f, "XOR"),
Function::Sin => write!(f, "SIN"),
Function::Cos => write!(f, "COS"),
Function::Tan => write!(f, "TAN"),
Function::Asin => write!(f, "ASIN"),
Function::Acos => write!(f, "ACOS"),
Function::Atan => write!(f, "ATAN"),
Function::Sinh => write!(f, "SINH"),
Function::Cosh => write!(f, "COSH"),
Function::Tanh => write!(f, "TANH"),
Function::Asinh => write!(f, "ASINH"),
Function::Acosh => write!(f, "ACOSH"),
Function::Atanh => write!(f, "ATANH"),
Function::Abs => write!(f, "ABS"),
Function::Pi => write!(f, "PI"),
Function::Sqrt => write!(f, "SQRT"),
Function::Sqrtpi => write!(f, "SQRTPI"),
Function::Atan2 => write!(f, "ATAN2"),
Function::Power => write!(f, "POWER"),
Function::Max => write!(f, "MAX"),
Function::Min => write!(f, "MIN"),
Function::Product => write!(f, "PRODUCT"),
Function::Rand => write!(f, "RAND"),
Function::Randbetween => write!(f, "RANDBETWEEN"),
Function::Round => write!(f, "ROUND"),
Function::Rounddown => write!(f, "ROUNDDOWN"),
Function::Roundup => write!(f, "ROUNDUP"),
Function::Sum => write!(f, "SUM"),
Function::Sumif => write!(f, "SUMIF"),
Function::Sumifs => write!(f, "SUMIFS"),
Function::Choose => write!(f, "CHOOSE"),
Function::Column => write!(f, "COLUMN"),
Function::Columns => write!(f, "COLUMNS"),
Function::Index => write!(f, "INDEX"),
Function::Indirect => write!(f, "INDIRECT"),
Function::Hlookup => write!(f, "HLOOKUP"),
Function::Lookup => write!(f, "LOOKUP"),
Function::Match => write!(f, "MATCH"),
Function::Offset => write!(f, "OFFSET"),
Function::Row => write!(f, "ROW"),
Function::Rows => write!(f, "ROWS"),
Function::Vlookup => write!(f, "VLOOKUP"),
Function::Xlookup => write!(f, "XLOOKUP"),
Function::Concatenate => write!(f, "CONCATENATE"),
Function::Exact => write!(f, "EXACT"),
Function::Value => write!(f, "VALUE"),
Function::T => write!(f, "T"),
Function::Valuetotext => write!(f, "VALUETOTEXT"),
Function::Concat => write!(f, "CONCAT"),
Function::Find => write!(f, "FIND"),
Function::Left => write!(f, "LEFT"),
Function::Len => write!(f, "LEN"),
Function::Lower => write!(f, "LOWER"),
Function::Mid => write!(f, "MID"),
Function::Right => write!(f, "RIGHT"),
Function::Search => write!(f, "SEARCH"),
Function::Text => write!(f, "TEXT"),
Function::Trim => write!(f, "TRIM"),
Function::Upper => write!(f, "UPPER"),
Function::Isnumber => write!(f, "ISNUMBER"),
Function::Isnontext => write!(f, "ISNONTEXT"),
Function::Istext => write!(f, "ISTEXT"),
Function::Islogical => write!(f, "ISLOGICAL"),
Function::Isblank => write!(f, "ISBLANK"),
Function::Iserr => write!(f, "ISERR"),
Function::Iserror => write!(f, "ISERROR"),
Function::Isna => write!(f, "ISNA"),
Function::Na => write!(f, "NA"),
Function::Isref => write!(f, "ISREF"),
Function::Isodd => write!(f, "ISODD"),
Function::Iseven => write!(f, "ISEVEN"),
Function::ErrorType => write!(f, "ERROR.TYPE"),
Function::Isformula => write!(f, "ISFORMULA"),
Function::Type => write!(f, "TYPE"),
Function::Sheet => write!(f, "SHEET"),
Function::Average => write!(f, "AVERAGE"),
Function::Averagea => write!(f, "AVERAGEA"),
Function::Averageif => write!(f, "AVERAGEIF"),
Function::Averageifs => write!(f, "AVERAGEIFS"),
Function::Count => write!(f, "COUNT"),
Function::Counta => write!(f, "COUNTA"),
Function::Countblank => write!(f, "COUNTBLANK"),
Function::Countif => write!(f, "COUNTIF"),
Function::Countifs => write!(f, "COUNTIFS"),
Function::Maxifs => write!(f, "MAXIFS"),
Function::Minifs => write!(f, "MINIFS"),
Function::Year => write!(f, "YEAR"),
Function::Day => write!(f, "DAY"),
Function::Month => write!(f, "MONTH"),
Function::Eomonth => write!(f, "EOMONTH"),
Function::Date => write!(f, "DATE"),
Function::Edate => write!(f, "EDATE"),
Function::Today => write!(f, "TODAY"),
Function::Now => write!(f, "NOW"),
Function::Pmt => write!(f, "PMT"),
Function::Pv => write!(f, "PV"),
Function::Rate => write!(f, "RATE"),
Function::Nper => write!(f, "NPER"),
Function::Fv => write!(f, "FV"),
Function::Ppmt => write!(f, "PPMT"),
Function::Ipmt => write!(f, "IPMT"),
Function::Npv => write!(f, "NPV"),
Function::Mirr => write!(f, "MIRR"),
Function::Irr => write!(f, "IRR"),
Function::Xirr => write!(f, "XIRR"),
Function::Xnpv => write!(f, "XNPV"),
Function::Rept => write!(f, "REPT"),
Function::Textafter => write!(f, "TEXTAFTER"),
Function::Textbefore => write!(f, "TEXTBEFORE"),
Function::Textjoin => write!(f, "TEXTJOIN"),
Function::Substitute => write!(f, "SUBSTITUTE"),
Function::Ispmt => write!(f, "ISPMT"),
Function::Rri => write!(f, "RRI"),
Function::Sln => write!(f, "SLN"),
Function::Syd => write!(f, "SYD"),
Function::Nominal => write!(f, "NOMINAL"),
Function::Effect => write!(f, "EFFECT"),
Function::Pduration => write!(f, "PDURATION"),
Function::Tbillyield => write!(f, "TBILLYIELD"),
Function::Tbillprice => write!(f, "TBILLPRICE"),
Function::Tbilleq => write!(f, "TBILLEQ"),
Function::Dollarde => write!(f, "DOLLARDE"),
Function::Dollarfr => write!(f, "DOLLARFR"),
Function::Ddb => write!(f, "DDB"),
Function::Db => write!(f, "DB"),
Function::Cumprinc => write!(f, "CUMPRINC"),
Function::Cumipmt => write!(f, "CUMIPMT"),
Function::Besseli => write!(f, "BESSELI"),
Function::Besselj => write!(f, "BESSELJ"),
Function::Besselk => write!(f, "BESSELK"),
Function::Bessely => write!(f, "BESSELY"),
Function::Erf => write!(f, "ERF"),
Function::ErfPrecise => write!(f, "ERF.PRECISE"),
Function::Erfc => write!(f, "ERFC"),
Function::ErfcPrecise => write!(f, "ERFC.PRECISE"),
Function::Bin2dec => write!(f, "BIN2DEC"),
Function::Bin2hex => write!(f, "BIN2HEX"),
Function::Bin2oct => write!(f, "BIN2OCT"),
Function::Dec2Bin => write!(f, "DEC2BIN"),
Function::Dec2hex => write!(f, "DEC2HEX"),
Function::Dec2oct => write!(f, "DEC2OCT"),
Function::Hex2bin => write!(f, "HEX2BIN"),
Function::Hex2dec => write!(f, "HEX2DEC"),
Function::Hex2oct => write!(f, "HEX2OCT"),
Function::Oct2bin => write!(f, "OCT2BIN"),
Function::Oct2dec => write!(f, "OCT2DEC"),
Function::Oct2hex => write!(f, "OCT2HEX"),
Function::Bitand => write!(f, "BITAND"),
Function::Bitlshift => write!(f, "BITLSHIFT"),
Function::Bitor => write!(f, "BITOR"),
Function::Bitrshift => write!(f, "BITRSHIFT"),
Function::Bitxor => write!(f, "BITXOR"),
Function::Complex => write!(f, "COMPLEX"),
Function::Imabs => write!(f, "IMABS"),
Function::Imaginary => write!(f, "IMAGINARY"),
Function::Imargument => write!(f, "IMARGUMENT"),
Function::Imconjugate => write!(f, "IMCONJUGATE"),
Function::Imcos => write!(f, "IMCOS"),
Function::Imcosh => write!(f, "IMCOSH"),
Function::Imcot => write!(f, "IMCOT"),
Function::Imcsc => write!(f, "IMCSC"),
Function::Imcsch => write!(f, "IMCSCH"),
Function::Imdiv => write!(f, "IMDIV"),
Function::Imexp => write!(f, "IMEXP"),
Function::Imln => write!(f, "IMLN"),
Function::Imlog10 => write!(f, "IMLOG10"),
Function::Imlog2 => write!(f, "IMLOG2"),
Function::Impower => write!(f, "IMPOWER"),
Function::Improduct => write!(f, "IMPRODUCT"),
Function::Imreal => write!(f, "IMREAL"),
Function::Imsec => write!(f, "IMSEC"),
Function::Imsech => write!(f, "IMSECH"),
Function::Imsin => write!(f, "IMSIN"),
Function::Imsinh => write!(f, "IMSINH"),
Function::Imsqrt => write!(f, "IMSQRT"),
Function::Imsub => write!(f, "IMSUB"),
Function::Imsum => write!(f, "IMSUM"),
Function::Imtan => write!(f, "IMTAN"),
Function::Convert => write!(f, "CONVERT"),
Function::Delta => write!(f, "DELTA"),
Function::Gestep => write!(f, "GESTEP"),
Function::Subtotal => write!(f, "SUBTOTAL"),
}
}
}
impl Model {
pub(crate) fn evaluate_function(
&mut self,
kind: &Function,
args: &[Node],
cell: CellReference,
) -> CalcResult {
match kind {
// Logical
Function::And => self.fn_and(args, cell),
Function::False => CalcResult::Boolean(false),
Function::If => self.fn_if(args, cell),
Function::Iferror => self.fn_iferror(args, cell),
Function::Ifna => self.fn_ifna(args, cell),
Function::Ifs => self.fn_ifs(args, cell),
Function::Not => self.fn_not(args, cell),
Function::Or => self.fn_or(args, cell),
Function::Switch => self.fn_switch(args, cell),
Function::True => CalcResult::Boolean(true),
Function::Xor => self.fn_xor(args, cell),
// Math and trigonometry
Function::Sin => self.fn_sin(args, cell),
Function::Cos => self.fn_cos(args, cell),
Function::Tan => self.fn_tan(args, cell),
Function::Asin => self.fn_asin(args, cell),
Function::Acos => self.fn_acos(args, cell),
Function::Atan => self.fn_atan(args, cell),
Function::Sinh => self.fn_sinh(args, cell),
Function::Cosh => self.fn_cosh(args, cell),
Function::Tanh => self.fn_tanh(args, cell),
Function::Asinh => self.fn_asinh(args, cell),
Function::Acosh => self.fn_acosh(args, cell),
Function::Atanh => self.fn_atanh(args, cell),
Function::Pi => self.fn_pi(args, cell),
Function::Abs => self.fn_abs(args, cell),
Function::Sqrt => self.fn_sqrt(args, cell),
Function::Sqrtpi => self.fn_sqrtpi(args, cell),
Function::Atan2 => self.fn_atan2(args, cell),
Function::Power => self.fn_power(args, cell),
Function::Max => self.fn_max(args, cell),
Function::Min => self.fn_min(args, cell),
Function::Product => self.fn_product(args, cell),
Function::Rand => self.fn_rand(args, cell),
Function::Randbetween => self.fn_randbetween(args, cell),
Function::Round => self.fn_round(args, cell),
Function::Rounddown => self.fn_rounddown(args, cell),
Function::Roundup => self.fn_roundup(args, cell),
Function::Sum => self.fn_sum(args, cell),
Function::Sumif => self.fn_sumif(args, cell),
Function::Sumifs => self.fn_sumifs(args, cell),
// Lookup and Reference
Function::Choose => self.fn_choose(args, cell),
Function::Column => self.fn_column(args, cell),
Function::Columns => self.fn_columns(args, cell),
Function::Index => self.fn_index(args, cell),
Function::Indirect => self.fn_indirect(args, cell),
Function::Hlookup => self.fn_hlookup(args, cell),
Function::Lookup => self.fn_lookup(args, cell),
Function::Match => self.fn_match(args, cell),
Function::Offset => self.fn_offset(args, cell),
Function::Row => self.fn_row(args, cell),
Function::Rows => self.fn_rows(args, cell),
Function::Vlookup => self.fn_vlookup(args, cell),
Function::Xlookup => self.fn_xlookup(args, cell),
// Text
Function::Concatenate => self.fn_concatenate(args, cell),
Function::Exact => self.fn_exact(args, cell),
Function::Value => self.fn_value(args, cell),
Function::T => self.fn_t(args, cell),
Function::Valuetotext => self.fn_valuetotext(args, cell),
Function::Concat => self.fn_concat(args, cell),
Function::Find => self.fn_find(args, cell),
Function::Left => self.fn_left(args, cell),
Function::Len => self.fn_len(args, cell),
Function::Lower => self.fn_lower(args, cell),
Function::Mid => self.fn_mid(args, cell),
Function::Right => self.fn_right(args, cell),
Function::Search => self.fn_search(args, cell),
Function::Text => self.fn_text(args, cell),
Function::Trim => self.fn_trim(args, cell),
Function::Upper => self.fn_upper(args, cell),
// Information
Function::Isnumber => self.fn_isnumber(args, cell),
Function::Isnontext => self.fn_isnontext(args, cell),
Function::Istext => self.fn_istext(args, cell),
Function::Islogical => self.fn_islogical(args, cell),
Function::Isblank => self.fn_isblank(args, cell),
Function::Iserr => self.fn_iserr(args, cell),
Function::Iserror => self.fn_iserror(args, cell),
Function::Isna => self.fn_isna(args, cell),
Function::Na => CalcResult::new_error(Error::NA, cell, "".to_string()),
Function::Isref => self.fn_isref(args, cell),
Function::Isodd => self.fn_isodd(args, cell),
Function::Iseven => self.fn_iseven(args, cell),
Function::ErrorType => self.fn_errortype(args, cell),
Function::Isformula => self.fn_isformula(args, cell),
Function::Type => self.fn_type(args, cell),
Function::Sheet => self.fn_sheet(args, cell),
// Statistical
Function::Average => self.fn_average(args, cell),
Function::Averagea => self.fn_averagea(args, cell),
Function::Averageif => self.fn_averageif(args, cell),
Function::Averageifs => self.fn_averageifs(args, cell),
Function::Count => self.fn_count(args, cell),
Function::Counta => self.fn_counta(args, cell),
Function::Countblank => self.fn_countblank(args, cell),
Function::Countif => self.fn_countif(args, cell),
Function::Countifs => self.fn_countifs(args, cell),
Function::Maxifs => self.fn_maxifs(args, cell),
Function::Minifs => self.fn_minifs(args, cell),
// Date and Time
Function::Year => self.fn_year(args, cell),
Function::Day => self.fn_day(args, cell),
Function::Eomonth => self.fn_eomonth(args, cell),
Function::Month => self.fn_month(args, cell),
Function::Date => self.fn_date(args, cell),
Function::Edate => self.fn_edate(args, cell),
Function::Today => self.fn_today(args, cell),
Function::Now => self.fn_now(args, cell),
// Financial
Function::Pmt => self.fn_pmt(args, cell),
Function::Pv => self.fn_pv(args, cell),
Function::Rate => self.fn_rate(args, cell),
Function::Nper => self.fn_nper(args, cell),
Function::Fv => self.fn_fv(args, cell),
Function::Ppmt => self.fn_ppmt(args, cell),
Function::Ipmt => self.fn_ipmt(args, cell),
Function::Npv => self.fn_npv(args, cell),
Function::Mirr => self.fn_mirr(args, cell),
Function::Irr => self.fn_irr(args, cell),
Function::Xirr => self.fn_xirr(args, cell),
Function::Xnpv => self.fn_xnpv(args, cell),
Function::Rept => self.fn_rept(args, cell),
Function::Textafter => self.fn_textafter(args, cell),
Function::Textbefore => self.fn_textbefore(args, cell),
Function::Textjoin => self.fn_textjoin(args, cell),
Function::Substitute => self.fn_substitute(args, cell),
Function::Ispmt => self.fn_ispmt(args, cell),
Function::Rri => self.fn_rri(args, cell),
Function::Sln => self.fn_sln(args, cell),
Function::Syd => self.fn_syd(args, cell),
Function::Nominal => self.fn_nominal(args, cell),
Function::Effect => self.fn_effect(args, cell),
Function::Pduration => self.fn_pduration(args, cell),
Function::Tbillyield => self.fn_tbillyield(args, cell),
Function::Tbillprice => self.fn_tbillprice(args, cell),
Function::Tbilleq => self.fn_tbilleq(args, cell),
Function::Dollarde => self.fn_dollarde(args, cell),
Function::Dollarfr => self.fn_dollarfr(args, cell),
Function::Ddb => self.fn_ddb(args, cell),
Function::Db => self.fn_db(args, cell),
Function::Cumprinc => self.fn_cumprinc(args, cell),
Function::Cumipmt => self.fn_cumipmt(args, cell),
// Engineering
Function::Besseli => self.fn_besseli(args, cell),
Function::Besselj => self.fn_besselj(args, cell),
Function::Besselk => self.fn_besselk(args, cell),
Function::Bessely => self.fn_bessely(args, cell),
Function::Erf => self.fn_erf(args, cell),
Function::ErfPrecise => self.fn_erfprecise(args, cell),
Function::Erfc => self.fn_erfc(args, cell),
Function::ErfcPrecise => self.fn_erfcprecise(args, cell),
Function::Bin2dec => self.fn_bin2dec(args, cell),
Function::Bin2hex => self.fn_bin2hex(args, cell),
Function::Bin2oct => self.fn_bin2oct(args, cell),
Function::Dec2Bin => self.fn_dec2bin(args, cell),
Function::Dec2hex => self.fn_dec2hex(args, cell),
Function::Dec2oct => self.fn_dec2oct(args, cell),
Function::Hex2bin => self.fn_hex2bin(args, cell),
Function::Hex2dec => self.fn_hex2dec(args, cell),
Function::Hex2oct => self.fn_hex2oct(args, cell),
Function::Oct2bin => self.fn_oct2bin(args, cell),
Function::Oct2dec => self.fn_oct2dec(args, cell),
Function::Oct2hex => self.fn_oct2hex(args, cell),
Function::Bitand => self.fn_bitand(args, cell),
Function::Bitlshift => self.fn_bitlshift(args, cell),
Function::Bitor => self.fn_bitor(args, cell),
Function::Bitrshift => self.fn_bitrshift(args, cell),
Function::Bitxor => self.fn_bitxor(args, cell),
Function::Complex => self.fn_complex(args, cell),
Function::Imabs => self.fn_imabs(args, cell),
Function::Imaginary => self.fn_imaginary(args, cell),
Function::Imargument => self.fn_imargument(args, cell),
Function::Imconjugate => self.fn_imconjugate(args, cell),
Function::Imcos => self.fn_imcos(args, cell),
Function::Imcosh => self.fn_imcosh(args, cell),
Function::Imcot => self.fn_imcot(args, cell),
Function::Imcsc => self.fn_imcsc(args, cell),
Function::Imcsch => self.fn_imcsch(args, cell),
Function::Imdiv => self.fn_imdiv(args, cell),
Function::Imexp => self.fn_imexp(args, cell),
Function::Imln => self.fn_imln(args, cell),
Function::Imlog10 => self.fn_imlog10(args, cell),
Function::Imlog2 => self.fn_imlog2(args, cell),
Function::Impower => self.fn_impower(args, cell),
Function::Improduct => self.fn_improduct(args, cell),
Function::Imreal => self.fn_imreal(args, cell),
Function::Imsec => self.fn_imsec(args, cell),
Function::Imsech => self.fn_imsech(args, cell),
Function::Imsin => self.fn_imsin(args, cell),
Function::Imsinh => self.fn_imsinh(args, cell),
Function::Imsqrt => self.fn_imsqrt(args, cell),
Function::Imsub => self.fn_imsub(args, cell),
Function::Imsum => self.fn_imsum(args, cell),
Function::Imtan => self.fn_imtan(args, cell),
Function::Convert => self.fn_convert(args, cell),
Function::Delta => self.fn_delta(args, cell),
Function::Gestep => self.fn_gestep(args, cell),
Function::Subtotal => self.fn_subtotal(args, cell),
}
}
}

View File

@@ -0,0 +1,624 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference, Range},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::util::build_criteria;
impl Model {
pub(crate) fn fn_average(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut count = 0.0;
let mut sum = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
CalcResult::Boolean(b) => {
if let Node::ReferenceKind { .. } = arg {
} else {
sum += if b { 1.0 } else { 0.0 };
count += 1.0;
}
}
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {
return CalcResult::new_error(
Error::ERROR,
cell,
"Unexpected Range".to_string(),
);
}
_ => {}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::String(s) => {
if let Node::ReferenceKind { .. } = arg {
// Do nothing
} else if let Ok(t) = s.parse::<f64>() {
sum += t;
count += 1.0;
} else {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument cannot be cast into number".to_string(),
};
}
}
_ => {
// Ignore everything else
}
};
}
if count == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by Zero".to_string(),
};
}
CalcResult::Number(sum / count)
}
pub(crate) fn fn_averagea(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut count = 0.0;
let mut sum = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::String(_) => count += 1.0,
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
CalcResult::Boolean(b) => {
if b {
sum += 1.0;
}
count += 1.0;
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {
return CalcResult::new_error(
Error::ERROR,
cell,
"Unexpected Range".to_string(),
);
}
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
}
}
}
}
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
CalcResult::String(s) => {
if let Node::ReferenceKind { .. } = arg {
// Do nothing
count += 1.0;
} else if let Ok(t) = s.parse::<f64>() {
sum += t;
count += 1.0;
} else {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument cannot be cast into number".to_string(),
};
}
}
CalcResult::Boolean(b) => {
count += 1.0;
if b {
sum += 1.0;
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
};
}
if count == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by Zero".to_string(),
};
}
CalcResult::Number(sum / count)
}
pub(crate) fn fn_count(&mut self, args: &[Node], cell: CellReference) -> 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(_) => {
result += 1.0;
}
CalcResult::Boolean(_) => {
if !matches!(arg, Node::ReferenceKind { .. }) {
result += 1.0;
}
}
CalcResult::String(s) => {
if !matches!(arg, Node::ReferenceKind { .. }) && s.parse::<f64>().is_ok() {
result += 1.0;
}
}
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) {
if let CalcResult::Number(_) = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
result += 1.0;
}
}
}
}
_ => {
// Ignore everything else
}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_counta(&mut self, args: &[Node], cell: CellReference) -> 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::EmptyCell | CalcResult::EmptyArg => {}
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
_ => {
result += 1.0;
}
}
}
}
}
_ => {
result += 1.0;
}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_countblank(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
// COUNTBLANK requires only one argument
if args.len() != 1 {
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::EmptyCell | CalcResult::EmptyArg => result += 1.0,
CalcResult::String(s) => {
if s.is_empty() {
result += 1.0
}
}
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(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::EmptyCell | CalcResult::EmptyArg => result += 1.0,
CalcResult::String(s) => {
if s.is_empty() {
result += 1.0
}
}
_ => {}
}
}
}
}
_ => {}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_countif(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let arguments = vec![args[0].clone(), args[1].clone()];
self.fn_countifs(&arguments, cell)
} else {
CalcResult::new_args_number_error(cell)
}
}
/// AVERAGEIF(criteria_range, criteria, [average_range])
/// if average_rage is missing then criteria_range will be used
pub(crate) fn fn_averageif(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let arguments = vec![args[0].clone(), args[0].clone(), args[1].clone()];
self.fn_averageifs(&arguments, cell)
} else if args.len() == 3 {
let arguments = vec![args[2].clone(), args[0].clone(), args[1].clone()];
self.fn_averageifs(&arguments, cell)
} else {
CalcResult::new_args_number_error(cell)
}
}
// FIXME: This function shares a lot of code with apply_ifs. Can we merge them?
pub(crate) fn fn_countifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count < 2 || args_count % 2 == 1 {
return CalcResult::new_args_number_error(cell);
}
let case_count = args_count / 2;
// NB: this is a beautiful example of the borrow checker
// The order of these two definitions cannot be swapped.
let mut criteria = Vec::new();
let mut fn_criteria = Vec::new();
let ranges = &mut Vec::new();
for case_index in 0..case_count {
let criterion = self.evaluate_node_in_context(&args[case_index * 2 + 1], cell);
criteria.push(criterion);
// NB: We cannot do:
// fn_criteria.push(build_criteria(&criterion));
// because criterion doesn't live long enough
let result = self.evaluate_node_in_context(&args[case_index * 2], cell);
if result.is_error() {
return result;
}
if let CalcResult::Range { left, right } = result {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// TODO test ranges are of the same size as sum_range
ranges.push(Range { left, right });
} else {
return CalcResult::new_error(Error::VALUE, cell, "Expected a range".to_string());
}
}
for criterion in criteria.iter() {
fn_criteria.push(build_criteria(criterion));
}
let mut total = 0.0;
let first_range = &ranges[0];
let left_row = first_range.left.row;
let left_column = first_range.left.column;
let right_row = first_range.right.row;
let right_column = first_range.right.column;
let dimension = self
.workbook
.worksheet(first_range.left.sheet)
.expect("Sheet expected during evaluation.")
.dimension();
let max_row = dimension.max_row;
let max_column = dimension.max_column;
let open_row = left_row == 1 && right_row == LAST_ROW;
let open_column = left_column == 1 && right_column == LAST_COLUMN;
for row in left_row..right_row + 1 {
if open_row && row > max_row {
// If the row is larger than the max row in the sheet then all cells are empty.
// We compute it only once
let mut is_true = true;
for fn_criterion in fn_criteria.iter() {
if !fn_criterion(&CalcResult::EmptyCell) {
is_true = false;
break;
}
}
if is_true {
total += ((LAST_ROW - max_row) * (right_column - left_column + 1)) as f64;
}
break;
}
for column in left_column..right_column + 1 {
if open_column && column > max_column {
// If the column is larger than the max column in the sheet then all cells are empty.
// We compute it only once
let mut is_true = true;
for fn_criterion in fn_criteria.iter() {
if !fn_criterion(&CalcResult::EmptyCell) {
is_true = false;
break;
}
}
if is_true {
total += (LAST_COLUMN - max_column) as f64;
}
break;
}
let mut is_true = true;
for case_index in 0..case_count {
// We check if value in range n meets criterion n
let range = &ranges[case_index];
let fn_criterion = &fn_criteria[case_index];
let value = self.evaluate_cell(CellReference {
sheet: range.left.sheet,
row: range.left.row + row - first_range.left.row,
column: range.left.column + column - first_range.left.column,
});
if !fn_criterion(&value) {
is_true = false;
break;
}
}
if is_true {
total += 1.0;
}
}
}
CalcResult::Number(total)
}
pub(crate) fn apply_ifs<F>(
&mut self,
args: &[Node],
cell: CellReference,
mut apply: F,
) -> Result<(), CalcResult>
where
F: FnMut(f64),
{
let args_count = args.len();
if args_count < 3 || args_count % 2 == 0 {
return Err(CalcResult::new_args_number_error(cell));
}
let arg_0 = self.evaluate_node_in_context(&args[0], cell);
if arg_0.is_error() {
return Err(arg_0);
}
let sum_range = if let CalcResult::Range { left, right } = arg_0 {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
));
}
Range { left, right }
} else {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Expected a range".to_string(),
));
};
let case_count = (args_count - 1) / 2;
// NB: this is a beautiful example of the borrow checker
// The order of these two definitions cannot be swapped.
let mut criteria = Vec::new();
let mut fn_criteria = Vec::new();
let ranges = &mut Vec::new();
for case_index in 1..=case_count {
let criterion = self.evaluate_node_in_context(&args[case_index * 2], cell);
// NB: criterion might be an error. That's ok
criteria.push(criterion);
// NB: We cannot do:
// fn_criteria.push(build_criteria(&criterion));
// because criterion doesn't live long enough
let result = self.evaluate_node_in_context(&args[case_index * 2 - 1], cell);
if result.is_error() {
return Err(result);
}
if let CalcResult::Range { left, right } = result {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
));
}
// TODO test ranges are of the same size as sum_range
ranges.push(Range { left, right });
} else {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Expected a range".to_string(),
));
}
}
for criterion in criteria.iter() {
fn_criteria.push(build_criteria(criterion));
}
let left_row = sum_range.left.row;
let left_column = sum_range.left.column;
let mut right_row = sum_range.right.row;
let mut right_column = sum_range.right.column;
if left_row == 1 && right_row == LAST_ROW {
right_row = self
.workbook
.worksheet(sum_range.left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if left_column == 1 && right_column == LAST_COLUMN {
right_column = self
.workbook
.worksheet(sum_range.left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
for row in left_row..right_row + 1 {
for column in left_column..right_column + 1 {
let mut is_true = true;
for case_index in 0..case_count {
// We check if value in range n meets criterion n
let range = &ranges[case_index];
let fn_criterion = &fn_criteria[case_index];
let value = self.evaluate_cell(CellReference {
sheet: range.left.sheet,
row: range.left.row + row - sum_range.left.row,
column: range.left.column + column - sum_range.left.column,
});
if !fn_criterion(&value) {
is_true = false;
break;
}
}
if is_true {
let v = self.evaluate_cell(CellReference {
sheet: sum_range.left.sheet,
row,
column,
});
match v {
CalcResult::Number(n) => apply(n),
CalcResult::Error { .. } => return Err(v),
_ => {}
}
}
}
}
Ok(())
}
pub(crate) fn fn_averageifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut total = 0.0;
let mut count = 0.0;
let average = |value: f64| {
total += value;
count += 1.0;
};
if let Err(e) = self.apply_ifs(args, cell, average) {
return e;
}
if count == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "division by 0".to_string(),
};
}
CalcResult::Number(total / count)
}
pub(crate) fn fn_minifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut min = f64::INFINITY;
let apply_min = |value: f64| min = value.min(min);
if let Err(e) = self.apply_ifs(args, cell, apply_min) {
return e;
}
if min.is_infinite() {
min = 0.0;
}
CalcResult::Number(min)
}
pub(crate) fn fn_maxifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut max = -f64::INFINITY;
let apply_max = |value: f64| max = value.max(max);
if let Err(e) = self.apply_ifs(args, cell, apply_max) {
return e;
}
if max.is_infinite() {
max = 0.0;
}
CalcResult::Number(max)
}
}

View File

@@ -0,0 +1,584 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{
parser::{parse_range, Node},
token::Error,
},
functions::Function,
model::Model,
};
/// Excel has a complicated way of filtering + hidden rows
/// As a first a approximation a table can either have filtered rows or hidden rows, but not both.
/// Internally hey both will be marked as hidden rows. Hidden rows
/// The behaviour is the same for SUBTOTAL(100s,) => ignore those
/// But changes for the SUBTOTAL(1-11, ) those ignore filtered but take hidden into account.
/// In Excel filters are non-dynamic. Once you apply filters in a table (say value in column 1 should > 20) they
/// stay that way, even if you change the value of the values in the table after the fact.
/// If you try to hide rows in a table with filtered rows they will behave as if filtered
/// // Also subtotals ignore subtotals
///
#[derive(PartialEq)]
enum SubTotalMode {
Full,
SkipHidden,
}
#[derive(PartialEq, Debug)]
pub enum CellTableStatus {
Normal,
Hidden,
Filtered,
}
impl Model {
fn get_table_for_cell(&self, sheet_index: u32, row: i32, column: i32) -> bool {
let worksheet = match self.workbook.worksheet(sheet_index) {
Ok(ws) => ws,
Err(_) => return false,
};
for table in self.workbook.tables.values() {
if worksheet.name != table.sheet_name {
continue;
}
// (column, row, column, row)
if let Ok((column1, row1, column2, row2)) = parse_range(&table.reference) {
if ((column >= column1) && (column <= column2)) && ((row >= row1) && (row <= row2))
{
return table.has_filters;
}
}
}
false
}
fn cell_hidden_status(&self, sheet_index: u32, row: i32, column: i32) -> CellTableStatus {
let worksheet = self.workbook.worksheet(sheet_index).expect("");
let mut hidden = false;
for row_style in &worksheet.rows {
if row_style.r == row {
hidden = row_style.hidden;
break;
}
}
if !hidden {
return CellTableStatus::Normal;
}
// The row is hidden we need to know if the table has filters
if self.get_table_for_cell(sheet_index, row, column) {
CellTableStatus::Filtered
} else {
CellTableStatus::Hidden
}
}
// FIXME(TD): This is too much
fn cell_is_subtotal(&self, sheet_index: u32, row: i32, column: i32) -> bool {
let row_data = match self.workbook.worksheets[sheet_index as usize]
.sheet_data
.get(&row)
{
Some(r) => r,
None => return false,
};
let cell = match row_data.get(&column) {
Some(c) => c,
None => {
return false;
}
};
match cell.get_formula() {
Some(f) => {
let node = &self.parsed_formulas[sheet_index as usize][f as usize];
matches!(
node,
Node::FunctionKind {
kind: Function::Subtotal,
args: _
}
)
}
None => false,
}
}
fn subtotal_get_values(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> Result<Vec<f64>, CalcResult> {
let mut result: Vec<f64> = Vec::new();
for arg in args {
match arg {
Node::FunctionKind {
kind: Function::Subtotal,
args: _,
} => {
// skip
}
_ => {
match self.evaluate_node_with_reference(arg, cell) {
CalcResult::String(_) | CalcResult::Boolean(_) => {
// Skip
}
CalcResult::Number(f) => result.push(f),
error @ CalcResult::Error { .. } => {
return Err(error);
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
));
}
// We are not expecting subtotal to have open ranges
let row1 = left.row;
let row2 = right.row;
let column1 = left.column;
let column2 = right.column;
for row in row1..=row2 {
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
if cell_status == CellTableStatus::Filtered {
continue;
}
if mode == SubTotalMode::SkipHidden
&& cell_status == CellTableStatus::Hidden
{
continue;
}
for column in column1..=column2 {
if self.cell_is_subtotal(left.sheet, row, column) {
continue;
}
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
result.push(value);
}
error @ CalcResult::Error { .. } => return Err(error),
_ => {
// We ignore booleans and strings
}
}
}
}
}
CalcResult::EmptyCell | CalcResult::EmptyArg => result.push(0.0),
}
}
}
}
Ok(result)
}
pub(crate) fn fn_subtotal(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f.trunc() as i32,
Err(s) => return s,
};
match value {
1 => self.subtotal_average(&args[1..], cell, SubTotalMode::Full),
2 => self.subtotal_count(&args[1..], cell, SubTotalMode::Full),
3 => self.subtotal_counta(&args[1..], cell, SubTotalMode::Full),
4 => self.subtotal_max(&args[1..], cell, SubTotalMode::Full),
5 => self.subtotal_min(&args[1..], cell, SubTotalMode::Full),
6 => self.subtotal_product(&args[1..], cell, SubTotalMode::Full),
7 => self.subtotal_stdevs(&args[1..], cell, SubTotalMode::Full),
8 => self.subtotal_stdevp(&args[1..], cell, SubTotalMode::Full),
9 => self.subtotal_sum(&args[1..], cell, SubTotalMode::Full),
10 => self.subtotal_vars(&args[1..], cell, SubTotalMode::Full),
11 => self.subtotal_varp(&args[1..], cell, SubTotalMode::Full),
101 => self.subtotal_average(&args[1..], cell, SubTotalMode::SkipHidden),
102 => self.subtotal_count(&args[1..], cell, SubTotalMode::SkipHidden),
103 => self.subtotal_counta(&args[1..], cell, SubTotalMode::SkipHidden),
104 => self.subtotal_max(&args[1..], cell, SubTotalMode::SkipHidden),
105 => self.subtotal_min(&args[1..], cell, SubTotalMode::SkipHidden),
106 => self.subtotal_product(&args[1..], cell, SubTotalMode::SkipHidden),
107 => self.subtotal_stdevs(&args[1..], cell, SubTotalMode::SkipHidden),
108 => self.subtotal_stdevp(&args[1..], cell, SubTotalMode::SkipHidden),
109 => self.subtotal_sum(&args[1..], cell, SubTotalMode::SkipHidden),
110 => self.subtotal_vars(&args[1..], cell, SubTotalMode::Full),
111 => self.subtotal_varp(&args[1..], cell, SubTotalMode::Full),
_ => CalcResult::new_error(
Error::VALUE,
cell,
format!("Invalid value for SUBTOTAL: {value}"),
),
}
}
fn subtotal_vars(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l < 2 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64 - 1.0)
}
CalcResult::Number(result)
}
fn subtotal_varp(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l == 0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64)
}
CalcResult::Number(result)
}
fn subtotal_stdevs(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l < 2 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64 - 1.0)
}
CalcResult::Number(result.sqrt())
}
fn subtotal_stdevp(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l == 0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64)
}
CalcResult::Number(result.sqrt())
}
fn subtotal_counta(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let mut counta = 0;
for arg in args {
match arg {
Node::FunctionKind {
kind: Function::Subtotal,
args: _,
} => {
// skip
}
_ => {
match self.evaluate_node_with_reference(arg, cell) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {
// skip
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// We are not expecting subtotal to have open ranges
let row1 = left.row;
let row2 = right.row;
let column1 = left.column;
let column2 = right.column;
for row in row1..=row2 {
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
if cell_status == CellTableStatus::Filtered {
continue;
}
if mode == SubTotalMode::SkipHidden
&& cell_status == CellTableStatus::Hidden
{
continue;
}
for column in column1..=column2 {
if self.cell_is_subtotal(left.sheet, row, column) {
continue;
}
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {
// skip
}
_ => counta += 1,
}
}
}
}
CalcResult::String(_)
| CalcResult::Number(_)
| CalcResult::Boolean(_)
| CalcResult::Error { .. } => counta += 1,
}
}
}
}
CalcResult::Number(counta as f64)
}
fn subtotal_count(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let mut count = 0;
for arg in args {
match arg {
Node::FunctionKind {
kind: Function::Subtotal,
args: _,
} => {
// skip
}
_ => {
match self.evaluate_node_with_reference(arg, cell) {
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// We are not expecting subtotal to have open ranges
let row1 = left.row;
let row2 = right.row;
let column1 = left.column;
let column2 = right.column;
for row in row1..=row2 {
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
if cell_status == CellTableStatus::Filtered {
continue;
}
if mode == SubTotalMode::SkipHidden
&& cell_status == CellTableStatus::Hidden
{
continue;
}
for column in column1..=column2 {
if self.cell_is_subtotal(left.sheet, row, column) {
continue;
}
if let CalcResult::Number(_) =
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
{
count += 1;
}
}
}
}
// This hasn't been tested
CalcResult::Number(_) => count += 1,
_ => {}
}
}
}
}
CalcResult::Number(count as f64)
}
fn subtotal_average(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in values {
result += value;
}
if l == 0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
CalcResult::Number(result / (l as f64))
}
fn subtotal_sum(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
for value in values {
result += value;
}
CalcResult::Number(result)
}
fn subtotal_product(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 1.0;
for value in values {
result *= value;
}
CalcResult::Number(result)
}
fn subtotal_max(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = f64::NAN;
for value in values {
result = value.max(result);
}
if result.is_nan() {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
fn subtotal_min(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = f64::NAN;
for value in values {
result = value.min(result);
}
if result.is_nan() {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
}

1104
base/src/functions/text.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,196 @@
pub(crate) enum Case {
Sensitive,
Insensitive,
}
/// Finds the text after the occurrence instance of 'search_for' in text
pub(crate) fn text_after(
text: &str,
delimiter: &str,
instance_num: i32,
match_mode: Case,
) -> Option<String> {
if let Some((_, right)) = match_text(text, delimiter, instance_num, match_mode) {
return Some(text[right..].to_string());
};
None
}
pub(crate) fn text_before(
text: &str,
delimiter: &str,
instance_num: i32,
match_mode: Case,
) -> Option<String> {
if let Some((left, _)) = match_text(text, delimiter, instance_num, match_mode) {
return Some(text[..left].to_string());
};
None
}
pub(crate) fn substitute(text: &str, old_text: &str, new_text: &str, instance_num: i32) -> String {
if let Some((left, right)) = match_text(text, old_text, instance_num, Case::Sensitive) {
return format!("{}{}{}", &text[..left], new_text, &text[right..]);
};
text.to_string()
}
fn match_text(
text: &str,
delimiter: &str,
instance_num: i32,
match_mode: Case,
) -> Option<(usize, usize)> {
match match_mode {
Case::Sensitive => {
if instance_num > 0 {
text_sensitive(text, delimiter, instance_num)
} else {
text_sensitive_reverse(text, delimiter, -instance_num)
}
}
Case::Insensitive => {
if instance_num > 0 {
text_sensitive(
&text.to_lowercase(),
&delimiter.to_lowercase(),
instance_num,
)
} else {
text_sensitive_reverse(
&text.to_lowercase(),
&delimiter.to_lowercase(),
-instance_num,
)
}
}
}
}
fn text_sensitive(text: &str, delimiter: &str, instance_num: i32) -> Option<(usize, usize)> {
let mut byte_index = 0;
let mut local_index = 1;
// delimiter length in bytes
let delimiter_len = delimiter.len();
for c in text.chars() {
if text[byte_index..].starts_with(delimiter) {
if local_index == instance_num {
return Some((byte_index, byte_index + delimiter_len));
} else {
local_index += 1;
}
}
byte_index += c.len_utf8();
}
None
}
fn text_sensitive_reverse(
text: &str,
delimiter: &str,
instance_num: i32,
) -> Option<(usize, usize)> {
let text_len = text.len();
let mut byte_index = text_len;
let mut local_index = 1;
let delimiter_len = delimiter.len();
for c in text.chars().rev() {
if text[byte_index..].starts_with(delimiter) {
if local_index == instance_num {
return Some((byte_index, byte_index + delimiter_len));
} else {
local_index += 1;
}
}
byte_index -= c.len_utf8();
}
None
}
#[cfg(test)]
mod tests {
use crate::functions::text_util::Case;
use super::{text_after, text_before};
#[test]
fn test_text_after_sensitive() {
assert_eq!(
text_after("One element", "ele", 1, Case::Sensitive),
Some("ment".to_string())
);
assert_eq!(
text_after("One element", "e", 1, Case::Sensitive),
Some(" element".to_string())
);
assert_eq!(
text_after("One element", "e", 4, Case::Sensitive),
Some("nt".to_string())
);
assert_eq!(text_after("One element", "e", 5, Case::Sensitive), None);
assert_eq!(
text_after("長壽相等!", "", 1, Case::Sensitive),
Some("等!".to_string())
);
}
#[test]
fn test_text_before_sensitive() {
assert_eq!(
text_before("One element", "ele", 1, Case::Sensitive),
Some("One ".to_string())
);
assert_eq!(
text_before("One element", "e", 1, Case::Sensitive),
Some("On".to_string())
);
assert_eq!(
text_before("One element", "e", 4, Case::Sensitive),
Some("One elem".to_string())
);
assert_eq!(text_before("One element", "e", 5, Case::Sensitive), None);
assert_eq!(
text_before("長壽相等!", "", 1, Case::Sensitive),
Some("長壽".to_string())
);
}
#[test]
fn test_text_after_insensitive() {
assert_eq!(
text_after("One element", "eLe", 1, Case::Insensitive),
Some("ment".to_string())
);
assert_eq!(
text_after("One element", "E", 1, Case::Insensitive),
Some(" element".to_string())
);
assert_eq!(
text_after("One element", "E", 4, Case::Insensitive),
Some("nt".to_string())
);
assert_eq!(text_after("One element", "E", 5, Case::Insensitive), None);
assert_eq!(
text_after("長壽相等!", "", 1, Case::Insensitive),
Some("等!".to_string())
);
}
#[test]
fn test_text_before_insensitive() {
assert_eq!(
text_before("One element", "eLe", 1, Case::Insensitive),
Some("One ".to_string())
);
assert_eq!(
text_before("One element", "E", 1, Case::Insensitive),
Some("On".to_string())
);
assert_eq!(
text_before("One element", "E", 4, Case::Insensitive),
Some("One elem".to_string())
);
assert_eq!(text_before("One element", "E", 5, Case::Insensitive), None);
assert_eq!(
text_before("長壽相等!", "", 1, Case::Insensitive),
Some("長壽".to_string())
);
}
}

401
base/src/functions/util.rs Normal file
View File

@@ -0,0 +1,401 @@
use regex::{escape, Regex};
use crate::{calc_result::CalcResult, expressions::token::is_english_error_string};
/// This test for exact match (modulo case).
/// * strings are not cast into bools or numbers
/// * empty cell is not cast into empty string or zero
pub(crate) fn values_are_equal(left: &CalcResult, right: &CalcResult) -> bool {
match (left, right) {
(CalcResult::Number(value1), CalcResult::Number(value2)) => {
if (value2 - value1).abs() < f64::EPSILON {
return true;
}
false
}
(CalcResult::String(value1), CalcResult::String(value2)) => {
let value1 = value1.to_uppercase();
let value2 = value2.to_uppercase();
value1 == value2
}
(CalcResult::Boolean(value1), CalcResult::Boolean(value2)) => value1 == value2,
(CalcResult::EmptyCell, CalcResult::EmptyCell) => true,
// NOTE: Errors and Ranges are not covered
(_, _) => false,
}
}
/// In Excel there are two ways of comparing cell values.
/// The old school comparison valid in formulas like D3 < D4 or HLOOKUP,... cast empty cells into empty strings or 0
/// For the new formulas like XLOOKUP or SORT an empty cell is always larger than anything else.
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
pub(crate) fn compare_values(left: &CalcResult, right: &CalcResult) -> i32 {
match (left, right) {
(CalcResult::Number(value1), CalcResult::Number(value2)) => {
if (value2 - value1).abs() < f64::EPSILON {
return 0;
}
if value1 < value2 {
return -1;
}
1
}
(CalcResult::Number(_value1), CalcResult::String(_value2)) => -1,
(CalcResult::Number(_value1), CalcResult::Boolean(_value2)) => -1,
(CalcResult::String(value1), CalcResult::String(value2)) => {
let value1 = value1.to_uppercase();
let value2 = value2.to_uppercase();
match value1.cmp(&value2) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
(CalcResult::String(_value1), CalcResult::Boolean(_value2)) => -1,
(CalcResult::Boolean(value1), CalcResult::Boolean(value2)) => {
if value1 == value2 {
return 0;
}
if *value1 {
return 1;
}
-1
}
(CalcResult::EmptyCell, CalcResult::String(_value2)) => {
compare_values(&CalcResult::String("".to_string()), right)
}
(CalcResult::String(_value1), CalcResult::EmptyCell) => {
compare_values(left, &CalcResult::String("".to_string()))
}
(CalcResult::EmptyCell, CalcResult::Number(_value2)) => {
compare_values(&CalcResult::Number(0.0), right)
}
(CalcResult::Number(_value1), CalcResult::EmptyCell) => {
compare_values(left, &CalcResult::Number(0.0))
}
(CalcResult::EmptyCell, CalcResult::EmptyCell) => 0,
// NOTE: Errors and Ranges are not covered
(_, _) => 1,
}
}
/// We convert an Excel wildcard into a Rust (Perl family) regex
pub(crate) fn from_wildcard_to_regex(
wildcard: &str,
exact: bool,
) -> Result<regex::Regex, regex::Error> {
// 1. Escape all
let reg = &escape(wildcard);
// 2. We convert the escaped '?' into '.' (matches a single character)
let reg = &reg.replace("\\?", ".");
// 3. We convert the escaped '*' into '.*' (matches anything)
let reg = &reg.replace("\\*", ".*");
// 4. We send '\\~\\~' to '??' that is an unescaped regular expression, therefore cannot be in reg
let reg = &reg.replace("\\~\\~", "??");
// 5. If the escaped and converted '*' is preceded by '~' then it's a raw '*'
let reg = &reg.replace("\\~.*", "\\*");
// 6. If the escaped and converted '.' is preceded by '~' then it's a raw '?'
let reg = &reg.replace("\\~.", "\\?");
// '~' is used in Excel to escape any other character.
// So ~x goes to x (whatever x is)
// 7. Remove all the others '\\~d' --> 'd'
let reg = &reg.replace("\\~", "");
// 8. Put back the '\\~\\~' as '\\~'
let reg = &reg.replace("??", "\\~");
// And we have a valid Perl regex! (As Kim Kardashian said before me: "I know, right?")
if exact {
return Regex::new(&format!("^{}$", reg));
}
Regex::new(reg)
}
/// NUMBERS ///
///*********///
// It could be either the number or a string representation of the number
// In the rest of the cases calc_result needs to be a number (cannot be the string "23", for instance)
fn result_is_equal_to_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => {
if (f - target).abs() < f64::EPSILON {
return true;
}
false
}
CalcResult::String(s) => {
if let Ok(f) = s.parse::<f64>() {
if (f - target).abs() < f64::EPSILON {
return true;
}
return false;
}
false
}
_ => false,
}
}
fn result_is_less_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f < target,
_ => false,
}
}
fn result_is_less_or_equal_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f <= target,
_ => false,
}
}
fn result_is_greater_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f > target,
_ => false,
}
}
fn result_is_greater_or_equal_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f >= target,
_ => false,
}
}
fn result_is_not_equal_to_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => {
if (f - target).abs() > f64::EPSILON {
return true;
}
false
}
_ => true,
}
}
/// BOOLEANS ///
///**********///
// Booleans have to be "exactly" equal
fn result_is_equal_to_bool(calc_result: &CalcResult, target: bool) -> bool {
match calc_result {
CalcResult::Boolean(f) => target == *f,
_ => false,
}
}
fn result_is_not_equal_to_bool(calc_result: &CalcResult, target: bool) -> bool {
match calc_result {
CalcResult::Boolean(f) => target != *f,
_ => true,
}
}
/// STRINGS ///
///*********///
/// Note that strings are case insensitive. `target` must always be lower case.
pub(crate) fn result_matches_regex(calc_result: &CalcResult, reg: &Regex) -> bool {
match calc_result {
CalcResult::String(s) => reg.is_match(&s.to_lowercase()),
_ => false,
}
}
fn result_is_equal_to_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
if target == s.to_lowercase() {
return true;
}
false
}
CalcResult::EmptyCell => target.is_empty(),
_ => false,
}
}
fn result_is_not_equal_to_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
if target != s.to_lowercase() {
return true;
}
false
}
_ => false,
}
}
fn result_is_less_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => target.cmp(&s.to_lowercase()) == std::cmp::Ordering::Greater,
_ => false,
}
}
fn result_is_less_or_equal_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
let lower_case = &s.to_lowercase();
target.cmp(lower_case) == std::cmp::Ordering::Less || lower_case == target
}
_ => false,
}
}
fn result_is_greater_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => target.cmp(&s.to_lowercase()) == std::cmp::Ordering::Less,
_ => false,
}
}
fn result_is_greater_or_equal_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
let lower_case = &s.to_lowercase();
target.cmp(lower_case) == std::cmp::Ordering::Greater || lower_case == target
}
_ => false,
}
}
/// ERRORS ///
///********///
fn result_is_equal_to_error(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::Error { error, .. } => target == error.to_string(),
_ => false,
}
}
fn result_is_not_equal_to_error(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::Error { error, .. } => target != error.to_string(),
_ => true,
}
}
/// EMPTY ///
///*******///
// Note that these two are not inverse of each other.
// In particular, you can never match an empty cell.
fn result_is_not_equal_to_empty(calc_result: &CalcResult) -> bool {
!matches!(calc_result, CalcResult::EmptyCell)
}
fn result_is_equal_to_empty(calc_result: &CalcResult) -> bool {
match calc_result {
CalcResult::Number(f) => (f - 0.0).abs() < f64::EPSILON,
_ => false,
}
}
/// This returns a function (closure) of signature fn(&CalcResult) -> bool
/// It is Boxed because it returns different closures, so the size cannot be known at compile time
/// The lifetime (a) of value has to be longer or equal to the lifetime of the returned closure
pub(crate) fn build_criteria<'a>(value: &'a CalcResult) -> Box<dyn Fn(&CalcResult) -> bool + 'a> {
match value {
CalcResult::String(s) => {
if let Some(v) = s.strip_prefix("<=") {
// TODO: I am not implementing <= ERROR or <= BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_less_or_equal_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_less_or_equal_than_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix(">=") {
// TODO: I am not implementing >= ERROR or >= BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_greater_or_equal_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_greater_or_equal_than_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix("<>") {
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_not_equal_to_number(x, f))
} else if let Ok(b) = v.to_lowercase().parse::<bool>() {
Box::new(move |x| result_is_not_equal_to_bool(x, b))
} else if is_english_error_string(v) {
Box::new(move |x| result_is_not_equal_to_error(x, v))
} else if v.contains('*') || v.contains('?') {
if let Ok(reg) = from_wildcard_to_regex(&v.to_lowercase(), true) {
Box::new(move |x| !result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else if v.is_empty() {
Box::new(result_is_not_equal_to_empty)
} else {
Box::new(move |x| result_is_not_equal_to_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix('<') {
// TODO: I am not implementing < ERROR or < BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_less_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_less_than_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix('>') {
// TODO: I am not implementing > ERROR or > BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_greater_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_greater_than_string(x, &v.to_lowercase()))
}
} else {
let v = if let Some(a) = s.strip_prefix('=') {
a
} else {
s
};
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_equal_to_number(x, f))
} else if let Ok(b) = v.to_lowercase().parse::<bool>() {
Box::new(move |x| result_is_equal_to_bool(x, b))
} else if is_english_error_string(v) {
Box::new(move |x| result_is_equal_to_error(x, v))
} else if v.contains('*') || v.contains('?') {
if let Ok(reg) = from_wildcard_to_regex(&v.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| result_is_equal_to_string(x, &v.to_lowercase()))
}
}
}
CalcResult::Number(target) => Box::new(move |x| result_is_equal_to_number(x, *target)),
CalcResult::Boolean(b) => Box::new(move |x| result_is_equal_to_bool(x, *b)),
CalcResult::Error { error, .. } => {
// An error will match an error (never a string that is an error)
Box::new(move |x| result_is_equal_to_error(x, &error.to_string()))
}
CalcResult::Range { left: _, right: _ } => {
// TODO: Implicit Intersection
Box::new(move |_x| false)
}
CalcResult::EmptyCell | CalcResult::EmptyArg => Box::new(result_is_equal_to_empty),
}
}

View File

@@ -0,0 +1,384 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::{
binary_search::{
binary_search_descending_or_greater, binary_search_descending_or_smaller,
binary_search_or_greater, binary_search_or_smaller,
},
util::{compare_values, from_wildcard_to_regex, result_matches_regex},
};
#[derive(PartialEq)]
enum SearchMode {
StartAtFirstItem = 1,
StartAtLastItem = -1,
BinarySearchDescending = -2,
BinarySearchAscending = 2,
}
#[derive(PartialEq)]
enum MatchMode {
ExactMatchSmaller = -1,
ExactMatch = 0,
ExactMatchLarger = 1,
WildcardMatch = 2,
}
// lookup_value in array, match_mode search_mode
fn linear_search(
lookup_value: &CalcResult,
array: &[CalcResult],
search_mode: SearchMode,
match_mode: MatchMode,
) -> Option<usize> {
let length = array.len();
match match_mode {
MatchMode::ExactMatch => {
// exact match
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
if compare_values(value, lookup_value) == 0 {
return Some(index);
}
}
return None;
}
MatchMode::ExactMatchSmaller | MatchMode::ExactMatchLarger => {
// exact match, if none found return the next smaller/larger item
let mut found_index = 0;
let mut approx = None;
let m_mode = match_mode as i32;
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
let c = compare_values(value, lookup_value);
if c == 0 {
return Some(index);
} else if c == m_mode {
match approx {
None => {
approx = Some(value.clone());
found_index = index;
}
Some(ref p) => {
if compare_values(p, value) == m_mode {
approx = Some(value.clone());
found_index = index;
}
}
}
}
}
if approx.is_none() {
return None;
} else {
return Some(found_index);
}
}
MatchMode::WildcardMatch => {
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, lookup_value) == 0)
};
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
if result_matches(value) {
return Some(index);
}
}
}
}
None
}
impl Model {
/// The XLOOKUP function searches a range or an array, and then returns the item corresponding
/// to the first match it finds. If no match exists, then XLOOKUP can return the closest (approximate) match.
/// =XLOOKUP(lookup_value, lookup_array, return_array, [if_not_found], [match_mode], [search_mode])
///
/// lookup_array and return_array must be column or row arrays and of the same dimension.
/// Otherwise #VALUE! is returned
/// [if_not_found]
/// Where a valid match is not found, return the [if_not_found] text you supply.
/// If a valid match is not found, and [if_not_found] is missing, #N/A is returned.
///
/// [match_mode]
/// Specify the match type:
/// * 0 - Exact match. If none found, return #N/A. This is the default.
/// * -1 - Exact match. If none found, return the next smaller item.
/// * 1 - Exact match. If none found, return the next larger item.
/// * 2 - A wildcard match where *, ?, and ~ have special meaning.
///
/// [search_mode]
/// Specify the search mode to use:
/// * 1 - Perform a search starting at the first item. This is the default.
/// * -1 - Perform a reverse search starting at the last item.
/// * 2 - Perform a binary search that relies on lookup_array being sorted
/// in ascending order. If not sorted, invalid results will be returned.
/// * -2 - Perform a binary search that relies on lookup_array being sorted
/// in descending order. If not sorted, invalid results will be returned.
pub(crate) fn fn_xlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 3 || args.len() > 6 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
// Get optional arguments
let if_not_found = if args.len() >= 4 {
let v = self.evaluate_node_in_context(&args[3], cell);
match v {
CalcResult::EmptyArg => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
},
_ => v,
}
} else {
// default
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
};
let match_mode = if args.len() >= 5 {
match self.get_number(&args[4], cell) {
Ok(c) => match c.floor() as i32 {
-1 => MatchMode::ExactMatchSmaller,
1 => MatchMode::ExactMatchLarger,
0 => MatchMode::ExactMatch,
2 => MatchMode::WildcardMatch,
_ => {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Unexpected number".to_string(),
};
}
},
Err(s) => return s,
}
} else {
// default
MatchMode::ExactMatch
};
let search_mode = if args.len() == 6 {
match self.get_number(&args[5], cell) {
Ok(c) => match c.floor() as i32 {
1 => SearchMode::StartAtFirstItem,
-1 => SearchMode::StartAtLastItem,
-2 => SearchMode::BinarySearchDescending,
2 => SearchMode::BinarySearchAscending,
_ => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Unexpected number".to_string(),
};
}
},
Err(s) => return s,
}
} else {
// default
SearchMode::StartAtFirstItem
};
// lookup_array
match self.evaluate_node_in_context(&args[1], cell) {
CalcResult::Range { left, right } => {
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Second argument must be a vector".to_string(),
};
}
// return array
match self.evaluate_node_in_context(&args[2], cell) {
CalcResult::Range {
left: result_left,
right: result_right,
} => {
if result_right.row - result_left.row != right.row - left.row
|| result_right.column - result_left.column
!= right.column - left.column
{
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Arrays must be of the same size".to_string(),
};
}
let mut row2 = right.row;
let row1 = left.row;
let mut column2 = right.column;
let column1 = left.column;
if row1 == 1 && row2 == LAST_ROW {
row2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
let left = CellReference {
sheet: left.sheet,
column: column1,
row: row1,
};
let right = CellReference {
sheet: left.sheet,
column: column2,
row: row2,
};
match search_mode {
SearchMode::StartAtFirstItem | SearchMode::StartAtLastItem => {
let array = &self.prepare_array(&left, &right, is_row_vector);
match linear_search(&lookup_value, array, search_mode, match_mode) {
Some(index) => {
let row_index =
if is_row_vector { index as i32 } else { 0 };
let column_index =
if is_row_vector { 0 } else { index as i32 };
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row: result_left.row + row_index,
column: result_left.column + column_index,
})
}
None => if_not_found,
}
}
SearchMode::BinarySearchAscending
| SearchMode::BinarySearchDescending => {
let index = if match_mode == MatchMode::ExactMatchLarger {
if search_mode == SearchMode::BinarySearchAscending {
binary_search_or_greater(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
} else {
binary_search_descending_or_greater(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
}
} else if search_mode == SearchMode::BinarySearchAscending {
binary_search_or_smaller(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
} else {
binary_search_descending_or_smaller(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
};
match index {
None => if_not_found,
Some(l) => {
let row =
result_left.row + if is_row_vector { l } else { 0 };
let column =
result_left.column + if is_row_vector { 0 } else { l };
if match_mode == MatchMode::ExactMatch {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + if is_row_vector { l } else { 0 },
column: left.column
+ if is_row_vector { 0 } else { l },
});
if compare_values(&value, &lookup_value) == 0 {
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row,
column,
})
} else {
if_not_found
}
} else if match_mode == MatchMode::ExactMatchSmaller
|| match_mode == MatchMode::ExactMatchLarger
{
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row,
column,
})
} else {
CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Cannot use wildcard in binary search"
.to_string(),
}
}
}
}
}
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
}