UPDATE: Dump of initial files
This commit is contained in:
210
base/src/functions/binary_search.rs
Normal file
210
base/src/functions/binary_search.rs
Normal 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
|
||||
}
|
||||
}
|
||||
314
base/src/functions/date_and_time.rs
Normal file
314
base/src/functions/date_and_time.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
176
base/src/functions/engineering/bessel.rs
Normal file
176
base/src/functions/engineering/bessel.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
233
base/src/functions/engineering/bit_operations.rs
Normal file
233
base/src/functions/engineering/bit_operations.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
793
base/src/functions/engineering/complex.rs
Normal file
793
base/src/functions/engineering/complex.rs
Normal 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)));
|
||||
}
|
||||
}
|
||||
418
base/src/functions/engineering/convert.rs
Normal file
418
base/src/functions/engineering/convert.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
59
base/src/functions/engineering/misc.rs
Normal file
59
base/src/functions/engineering/misc.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
7
base/src/functions/engineering/mod.rs
Normal file
7
base/src/functions/engineering/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod bessel;
|
||||
mod bit_operations;
|
||||
mod complex;
|
||||
mod convert;
|
||||
mod misc;
|
||||
mod number_basis;
|
||||
mod transcendental;
|
||||
546
base/src/functions/engineering/number_basis.rs
Normal file
546
base/src/functions/engineering/number_basis.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
29
base/src/functions/engineering/transcendental/README.md
Normal file
29
base/src/functions/engineering/transcendental/README.md
Normal 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/
|
||||
144
base/src/functions/engineering/transcendental/bessel_i.rs
Normal file
144
base/src/functions/engineering/transcendental/bessel_i.rs
Normal 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
|
||||
}
|
||||
402
base/src/functions/engineering/transcendental/bessel_j0_y0.rs
Normal file
402
base/src/functions/engineering/transcendental/bessel_j0_y0.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
391
base/src/functions/engineering/transcendental/bessel_j1_y1.rs
Normal file
391
base/src/functions/engineering/transcendental/bessel_j1_y1.rs
Normal 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
|
||||
}
|
||||
329
base/src/functions/engineering/transcendental/bessel_jn_yn.rs
Normal file
329
base/src/functions/engineering/transcendental/bessel_jn_yn.rs
Normal 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
|
||||
}
|
||||
}
|
||||
90
base/src/functions/engineering/transcendental/bessel_k.rs
Normal file
90
base/src/functions/engineering/transcendental/bessel_k.rs
Normal 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
|
||||
}
|
||||
19
base/src/functions/engineering/transcendental/bessel_util.rs
Normal file
19
base/src/functions/engineering/transcendental/bessel_util.rs
Normal 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]),
|
||||
)
|
||||
}
|
||||
14
base/src/functions/engineering/transcendental/create_test.jl
Normal file
14
base/src/functions/engineering/transcendental/create_test.jl
Normal 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
|
||||
53
base/src/functions/engineering/transcendental/erf.rs
Normal file
53
base/src/functions/engineering/transcendental/erf.rs
Normal 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
|
||||
}
|
||||
}
|
||||
16
base/src/functions/engineering/transcendental/mod.rs
Normal file
16
base/src/functions/engineering/transcendental/mod.rs
Normal 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;
|
||||
183
base/src/functions/engineering/transcendental/test_bessel.rs
Normal file
183
base/src/functions/engineering/transcendental/test_bessel.rs
Normal 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})"
|
||||
);
|
||||
}
|
||||
}
|
||||
1884
base/src/functions/financial.rs
Normal file
1884
base/src/functions/financial.rs
Normal file
File diff suppressed because it is too large
Load Diff
255
base/src/functions/financial_util.rs
Normal file
255
base/src/functions/financial_util.rs
Normal 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()))
|
||||
}
|
||||
296
base/src/functions/information.rs
Normal file
296
base/src/functions/information.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
321
base/src/functions/logical.rs
Normal file
321
base/src/functions/logical.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
843
base/src/functions/lookup_and_reference.rs
Normal file
843
base/src/functions/lookup_and_reference.rs
Normal 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, ®))
|
||||
} 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, ®))
|
||||
} 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, ®))
|
||||
} 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 }
|
||||
}
|
||||
}
|
||||
671
base/src/functions/mathematical.rs
Normal file
671
base/src/functions/mathematical.rs
Normal 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
930
base/src/functions/mod.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
624
base/src/functions/statistical.rs
Normal file
624
base/src/functions/statistical.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
584
base/src/functions/subtotal.rs
Normal file
584
base/src/functions/subtotal.rs
Normal 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
1104
base/src/functions/text.rs
Normal file
File diff suppressed because it is too large
Load Diff
196
base/src/functions/text_util.rs
Normal file
196
base/src/functions/text_util.rs
Normal 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
401
base/src/functions/util.rs
Normal 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 = ®.replace("\\?", ".");
|
||||
// 3. We convert the escaped '*' into '.*' (matches anything)
|
||||
let reg = ®.replace("\\*", ".*");
|
||||
|
||||
// 4. We send '\\~\\~' to '??' that is an unescaped regular expression, therefore cannot be in reg
|
||||
let reg = ®.replace("\\~\\~", "??");
|
||||
|
||||
// 5. If the escaped and converted '*' is preceded by '~' then it's a raw '*'
|
||||
let reg = ®.replace("\\~.*", "\\*");
|
||||
// 6. If the escaped and converted '.' is preceded by '~' then it's a raw '?'
|
||||
let 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 = ®.replace("\\~", "");
|
||||
// 8. Put back the '\\~\\~' as '\\~'
|
||||
let 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, ®))
|
||||
} 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, ®))
|
||||
} 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),
|
||||
}
|
||||
}
|
||||
384
base/src/functions/xlookup.rs
Normal file
384
base/src/functions/xlookup.rs
Normal 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, ®))
|
||||
} 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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user