use chrono::Datelike;
use crate::{
calc_result::CalcResult,
constants::{LAST_COLUMN, LAST_ROW, MAXIMUM_DATE_SERIAL_NUMBER, MINIMUM_DATE_SERIAL_NUMBER},
expressions::{parser::Node, token::Error, types::CellReferenceIndex},
formatter::dates::from_excel_date,
model::Model,
};
use super::financial_util::{compute_irr, compute_npv, compute_rate, compute_xirr, compute_xnpv};
fn is_less_than_one_year(start_date: i64, end_date: i64) -> Result<bool, String> {
let end = from_excel_date(end_date)?;
let start = from_excel_date(start_date)?;
if end_date - start_date < 365 {
return Ok(true);
}
let end_year = end.year();
let start_year = start.year();
if end_year == start_year {
return Ok(true);
}
if end_year != start_year + 1 {
return Ok(false);
}
let start_month = start.month();
let end_month = end.month();
if end_month < start_month {
return Ok(true);
}
if end_month > start_month {
return Ok(false);
}
let start_day = start.day();
let end_day = end.day();
Ok(end_day <= start_day)
}
fn compute_payment(
rate: f64,
nper: f64,
pv: f64,
fv: f64,
period_start: bool,
) -> Result<f64, (Error, String)> {
if rate == 0.0 {
if nper == 0.0 {
return Err((Error::NUM, "Period count must be non zero".to_string()));
}
return Ok(-(pv + fv) / nper);
}
if rate <= -1.0 {
return Err((Error::NUM, "Rate must be > -1".to_string()));
};
let rate_nper = if nper == 0.0 {
1.0
} else {
(1.0 + rate).powf(nper)
};
let result = if period_start {
(fv + pv * rate_nper) * rate / ((1.0 + rate) * (1.0 - rate_nper))
} else {
(fv * rate + pv * rate * rate_nper) / (1.0 - rate_nper)
};
if result.is_nan() || result.is_infinite() {
return Err((Error::NUM, "Invalid result".to_string()));
}
Ok(result)
}
fn compute_future_value(
rate: f64,
nper: f64,
pmt: f64,
pv: f64,
period_start: bool,
) -> Result<f64, (Error, String)> {
if rate == 0.0 {
return Ok(-pv - pmt * nper);
}
if rate == -1.0 && nper < 0.0 {
return Err((Error::DIV, "Divide by zero".to_string()));
}
let rate_nper = (1.0 + rate).powf(nper);
let fv = if period_start {
-pv * rate_nper - pmt * (1.0 + rate) * (rate_nper - 1.0) / rate
} else {
-pv * rate_nper - pmt * (rate_nper - 1.0) / rate
};
if fv.is_nan() {
return Err((Error::NUM, "Invalid result".to_string()));
}
if !fv.is_finite() {
return Err((Error::DIV, "Divide by zero".to_string()));
}
Ok(fv)
}
fn compute_ipmt(
rate: f64,
period: f64,
period_count: f64,
present_value: f64,
future_value: f64,
period_start: bool,
) -> Result<f64, (Error, String)> {
let payment = compute_payment(
rate,
period_count,
present_value,
future_value,
period_start,
)?;
if period < 1.0 || period >= period_count + 1.0 {
return Err((
Error::NUM,
format!("Period must be between 1 and {}", period_count + 1.0),
));
}
if period == 1.0 && period_start {
Ok(0.0)
} else {
let p = if period_start {
period - 2.0
} else {
period - 1.0
};
let c = if period_start { -payment } else { 0.0 };
let fv = compute_future_value(rate, p, payment, present_value, period_start)?;
Ok((fv + c) * rate)
}
}
fn compute_ppmt(
rate: f64,
period: f64,
period_count: f64,
present_value: f64,
future_value: f64,
period_start: bool,
) -> Result<f64, (Error, String)> {
let payment = compute_payment(
rate,
period_count,
present_value,
future_value,
period_start,
)?;
let ipmt = compute_ipmt(
rate,
period,
period_count,
present_value,
future_value,
period_start,
)?;
Ok(payment - ipmt)
}
impl<'a> Model<'a> {
fn get_array_of_numbers_generic(
&mut self,
arg: &Node,
cell: &CellReferenceIndex,
accept_number_node: bool,
handle_empty_cell: impl Fn() -> Result<Option<f64>, CalcResult>,
handle_non_number_cell: impl Fn() -> Result<Option<f64>, CalcResult>,
) -> Result<Vec<f64>, CalcResult> {
let mut values = Vec::new();
match self.evaluate_node_in_context(arg, *cell) {
CalcResult::Number(value) if accept_number_node => values.push(value),
CalcResult::Number(_) => {
return Err(CalcResult::new_error(
Error::VALUE,
*cell,
"Expected range of numbers".to_string(),
));
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
*cell,
"Ranges are in different sheets".to_string(),
));
}
let sheet = left.sheet;
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(sheet)
.map_err(|_| {
CalcResult::new_error(
Error::ERROR,
*cell,
format!("Invalid worksheet index: '{sheet}'"),
)
})?
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(sheet)
.map_err(|_| {
CalcResult::new_error(
Error::ERROR,
*cell,
format!("Invalid worksheet index: '{sheet}'"),
)
})?
.dimension()
.max_column;
}
for row in row1..=row2 {
for column in column1..=column2 {
let cell_ref = CellReferenceIndex { sheet, row, column };
match self.evaluate_cell(cell_ref) {
CalcResult::Number(value) => values.push(value),
error @ CalcResult::Error { .. } => return Err(error),
CalcResult::EmptyCell => {
if let Some(value) = handle_empty_cell()? {
values.push(value);
}
}
_ => {
if let Some(value) = handle_non_number_cell()? {
values.push(value);
}
}
}
}
}
}
error @ CalcResult::Error { .. } => return Err(error),
_ => {
handle_non_number_cell()?;
}
}
Ok(values)
}
fn get_array_of_numbers(
&mut self,
arg: &Node,
cell: &CellReferenceIndex,
) -> Result<Vec<f64>, CalcResult> {
self.get_array_of_numbers_generic(
arg,
cell,
true, || Ok(None), || Ok(None), )
}
fn get_array_of_numbers_xpnv(
&mut self,
arg: &Node,
cell: &CellReferenceIndex,
error: Error,
) -> Result<Vec<f64>, CalcResult> {
self.get_array_of_numbers_generic(
arg,
cell,
true, || {
Err(CalcResult::new_error(
Error::NUM,
*cell,
"Expected number".to_string(),
))
},
|| {
Err(CalcResult::new_error(
error.clone(),
*cell,
"Expected number".to_string(),
))
},
)
}
fn get_array_of_numbers_xirr(
&mut self,
arg: &Node,
cell: &CellReferenceIndex,
) -> Result<Vec<f64>, CalcResult> {
self.get_array_of_numbers_generic(
arg,
cell,
false, || Ok(Some(0.0)), || {
Err(CalcResult::new_error(
Error::VALUE,
*cell,
"Expected number".to_string(),
))
},
)
}
pub(crate) fn fn_pmt(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(3..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let nper = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let fv = if arg_count > 3 {
match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let period_start = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => f != 0.0,
Err(s) => return s,
}
} else {
false
};
match compute_payment(rate, nper, pv, fv, period_start) {
Ok(p) => CalcResult::Number(p),
Err(error) => CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
},
}
}
pub(crate) fn fn_pv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(3..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period_count = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let payment = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let future_value = if arg_count > 3 {
match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let period_start = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => f != 0.0,
Err(s) => return s,
}
} else {
false
};
if rate == 0.0 {
return CalcResult::Number(-future_value - payment * period_count);
}
if rate == -1.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Rate must be != -1".to_string(),
};
};
let rate_nper = (1.0 + rate).powf(period_count);
let result = if period_start {
-(future_value * rate + payment * (1.0 + rate) * (rate_nper - 1.0)) / (rate * rate_nper)
} else {
(-future_value * rate - payment * (rate_nper - 1.0)) / (rate * rate_nper)
};
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid result".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_rate(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(3..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let nper = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pmt = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let fv = if arg_count > 3 {
match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let annuity_type = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => i32::from(f != 0.0),
Err(s) => return s,
}
} else {
0
};
let guess = if arg_count > 5 {
match self.get_number(&args[5], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.1
};
match compute_rate(pv, fv, nper, pmt, annuity_type, guess) {
Ok(f) => CalcResult::Number(f),
Err(error) => CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
},
}
}
pub(crate) fn fn_nper(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(3..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let payment = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let present_value = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let future_value = if arg_count > 3 {
match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let period_start = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => f != 0.0,
Err(s) => return s,
}
} else {
false
};
if rate == 0.0 {
if payment == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Divide by zero".to_string(),
};
}
return CalcResult::Number(-(future_value + present_value) / payment);
}
if rate < -1.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Rate must be > -1".to_string(),
};
};
let rate_nper = if period_start {
if payment != 0.0 {
let term = payment * (1.0 + rate) / rate;
(1.0 - future_value / term) / (1.0 + present_value / term)
} else {
-future_value / present_value
}
} else {
if payment != 0.0 {
let term = payment / rate;
(1.0 - future_value / term) / (1.0 + present_value / term)
} else {
-future_value / present_value
}
};
if rate_nper <= 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Cannot compute.".to_string(),
};
}
let result = rate_nper.ln() / (1.0 + rate).ln();
CalcResult::Number(result)
}
pub(crate) fn fn_fv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(3..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let nper = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pmt = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = if arg_count > 3 {
match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let period_start = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => f != 0.0,
Err(s) => return s,
}
} else {
false
};
match compute_future_value(rate, nper, pmt, pv, period_start) {
Ok(f) => CalcResult::Number(f),
Err(error) => CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
},
}
}
pub(crate) fn fn_ipmt(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(4..=6).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period_count = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let present_value = match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
};
let future_value = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let period_start = if arg_count > 5 {
match self.get_number(&args[5], cell) {
Ok(f) => f != 0.0,
Err(s) => return s,
}
} else {
false
};
let ipmt = match compute_ipmt(
rate,
period,
period_count,
present_value,
future_value,
period_start,
) {
Ok(f) => f,
Err(error) => {
return CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
}
}
};
CalcResult::Number(ipmt)
}
pub(crate) fn fn_ppmt(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(4..=6).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period_count = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let present_value = match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
};
let future_value = if arg_count > 4 {
match self.get_number(&args[4], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.0
};
let period_start = if arg_count > 5 {
match self.get_number(&args[5], cell) {
Ok(f) => f != 0.0,
Err(s) => return s,
}
} else {
false
};
let ppmt = match compute_ppmt(
rate,
period,
period_count,
present_value,
future_value,
period_start,
) {
Ok(f) => f,
Err(error) => {
return CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
}
}
};
CalcResult::Number(ppmt)
}
pub(crate) fn fn_npv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if arg_count < 2 {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let mut values = Vec::new();
for arg in &args[1..] {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => values.push(value),
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
let row1 = left.row;
let mut row2 = right.row;
let column1 = left.column;
let mut column2 = right.column;
if row1 == 1 && row2 == LAST_ROW {
row2 = match self.workbook.worksheet(left.sheet) {
Ok(s) => s.dimension().max_row,
Err(_) => {
return CalcResult::new_error(
Error::ERROR,
cell,
format!("Invalid worksheet index: '{}'", left.sheet),
);
}
};
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = match self.workbook.worksheet(left.sheet) {
Ok(s) => s.dimension().max_column,
Err(_) => {
return CalcResult::new_error(
Error::ERROR,
cell,
format!("Invalid worksheet index: '{}'", left.sheet),
);
}
};
}
for row in row1..row2 + 1 {
for column in column1..(column2 + 1) {
match self.evaluate_cell(CellReferenceIndex {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
values.push(value);
}
error @ CalcResult::Error { .. } => return error,
_ => {
}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
_ => {
}
};
}
match compute_npv(rate, &values) {
Ok(f) => CalcResult::Number(f),
Err(error) => CalcResult::new_error(error.0, cell, error.1),
}
}
pub(crate) fn fn_irr(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if arg_count > 2 || arg_count == 0 {
return CalcResult::new_args_number_error(cell);
}
let values = match self.get_array_of_numbers(&args[0], &cell) {
Ok(s) => s,
Err(error) => return error,
};
let guess = if arg_count == 2 {
match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.1
};
match compute_irr(&values, guess) {
Ok(f) => CalcResult::Number(f),
Err(error) => CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
},
}
}
pub(crate) fn fn_xnpv(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(2..=3).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let values = match self.get_array_of_numbers_xpnv(&args[1], &cell, Error::NUM) {
Ok(s) => s,
Err(error) => return error,
};
let dates = match self.get_array_of_numbers_xpnv(&args[2], &cell, Error::VALUE) {
Ok(s) => s,
Err(error) => return error,
};
let dates: Vec<f64> = dates.iter().map(|s| s.floor()).collect();
let values_count = values.len();
if values_count != dates.len() {
return CalcResult::new_error(
Error::NUM,
cell,
"Values and dates must be the same length".to_string(),
);
}
if values_count == 0 {
return CalcResult::new_error(Error::NUM, cell, "Not enough values".to_string());
}
let first_date = dates[0];
for date in &dates {
if *date < MINIMUM_DATE_SERIAL_NUMBER as f64
|| *date > MAXIMUM_DATE_SERIAL_NUMBER as f64
{
return CalcResult::new_error(
Error::NUM,
cell,
"Invalid number for date".to_string(),
);
}
if date < &first_date {
return CalcResult::new_error(
Error::NUM,
cell,
"Date precedes the starting date".to_string(),
);
}
}
if rate <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "rate needs to be > 0".to_string());
}
match compute_xnpv(rate, &values, &dates) {
Ok(f) => CalcResult::Number(f),
Err((error, message)) => CalcResult::new_error(error, cell, message),
}
}
pub(crate) fn fn_xirr(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(2..=3).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let values = match self.get_array_of_numbers_xirr(&args[0], &cell) {
Ok(s) => s,
Err(error) => return error,
};
let dates = match self.get_array_of_numbers_xirr(&args[1], &cell) {
Ok(s) => s,
Err(error) => return error,
};
let guess = if arg_count == 3 {
match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
0.1
};
let dates: Vec<f64> = dates.iter().map(|s| s.floor()).collect();
let values_count = values.len();
if values_count != dates.len() {
return CalcResult::new_error(
Error::NUM,
cell,
"Values and dates must be the same length".to_string(),
);
}
if values_count == 0 {
return CalcResult::new_error(Error::NUM, cell, "Not enough values".to_string());
}
let first_date = dates[0];
for date in &dates {
if *date < MINIMUM_DATE_SERIAL_NUMBER as f64
|| *date > MAXIMUM_DATE_SERIAL_NUMBER as f64
{
return CalcResult::new_error(
Error::NUM,
cell,
"Invalid number for date".to_string(),
);
}
if date < &first_date {
return CalcResult::new_error(
Error::NUM,
cell,
"Date precedes the starting date".to_string(),
);
}
}
match compute_xirr(&values, &dates, guess) {
Ok(f) => CalcResult::Number(f),
Err((error, message)) => CalcResult::Error {
error,
origin: cell,
message,
},
}
}
pub(crate) fn fn_mirr(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let values = match self.get_array_of_numbers(&args[0], &cell) {
Ok(s) => s,
Err(error) => return error,
};
let finance_rate = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let reinvest_rate = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let mut positive_values = Vec::new();
let mut negative_values = Vec::new();
let mut last_negative_index = -1;
for (index, &value) in values.iter().enumerate() {
let (p, n) = if value >= 0.0 {
(value, 0.0)
} else {
last_negative_index = index as i32;
(0.0, value)
};
positive_values.push(p);
negative_values.push(n);
}
if last_negative_index == -1 {
return CalcResult::new_error(
Error::DIV,
cell,
"Invalid data for MIRR function".to_string(),
);
}
let years = values.len() as f64;
let top = if reinvest_rate == -1.0 {
match positive_values.last() {
Some(f) => *f,
None => 0.0,
}
} else {
match compute_npv(reinvest_rate, &positive_values) {
Ok(npv) => -npv * ((1.0 + reinvest_rate).powf(years)),
Err((error, message)) => {
return CalcResult::Error {
error,
origin: cell,
message,
}
}
}
};
let bottom = if finance_rate == -1.0 {
if last_negative_index == 0 {
negative_values[last_negative_index as usize]
} else {
f64::INFINITY
}
} else {
match compute_npv(finance_rate, &negative_values) {
Ok(npv) => npv * (1.0 + finance_rate),
Err((error, message)) => {
return CalcResult::Error {
error,
origin: cell,
message,
}
}
}
};
let result = (top / bottom).powf(1.0 / (years - 1.0)) - 1.0;
if result.is_infinite() {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
if result.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for MIRR".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_ispmt(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 4 {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let per = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let nper = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
};
if nper == 0.0 {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
CalcResult::Number(pv * rate * (per / nper - 1.0))
}
pub(crate) fn fn_rri(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let nper = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let fv = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
if nper <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "nper should be >0".to_string());
}
if pv == 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Division by 0".to_string());
}
let result = (fv / pv).powf(1.0 / nper) - 1.0;
if result.is_infinite() {
return CalcResult::new_error(Error::NUM, cell, "Division by 0".to_string());
}
if result.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for RRI".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_sln(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let cost = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let salvage = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let life = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
if life == 0.0 {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
let result = (cost - salvage) / life;
CalcResult::Number(result)
}
pub(crate) fn fn_syd(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 4 {
return CalcResult::new_args_number_error(cell);
}
let cost = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let salvage = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let life = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let per = match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
};
if life == 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Division by 0".to_string());
}
if per > life || per <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "per should be <= life".to_string());
}
let result = ((cost - salvage) * (life - per + 1.0) * 2.0) / (life * (life + 1.0));
CalcResult::Number(result)
}
pub(crate) fn fn_nominal(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let effect_rate = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let npery = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f.floor(),
Err(s) => return s,
};
if effect_rate <= 0.0 || npery < 1.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid arguments".to_string());
}
let result = ((1.0 + effect_rate).powf(1.0 / npery) - 1.0) * npery;
if result.is_infinite() {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
if result.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for RRI".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_effect(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let nominal_rate = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let npery = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f.floor(),
Err(s) => return s,
};
if nominal_rate <= 0.0 || npery < 1.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid arguments".to_string());
}
let result = (1.0 + nominal_rate / npery).powf(npery) - 1.0;
if result.is_infinite() {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
if result.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for RRI".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_pduration(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let fv = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
if fv <= 0.0 || pv <= 0.0 || rate <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid arguments".to_string());
}
let result = (fv.ln() - pv.ln()) / ((1.0 + rate).ln());
if result.is_infinite() {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
if result.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for RRI".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_tbilleq(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let settlement = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let maturity = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let discount = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let less_than_one_year = match is_less_than_one_year(settlement as i64, maturity as i64) {
Ok(f) => f,
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
};
if settlement > maturity {
return CalcResult::new_error(
Error::NUM,
cell,
"settlement should be <= maturity".to_string(),
);
}
if !less_than_one_year {
return CalcResult::new_error(
Error::NUM,
cell,
"maturity <= settlement + year".to_string(),
);
}
if discount <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "discount should be >0".to_string());
}
let d_m = maturity - settlement;
let result = if d_m < 183.0 {
365.0 * discount / (360.0 - discount * d_m)
} else {
let year = if d_m == 366.0 { 366.0 } else { 365.0 };
let d_extra = d_m - year / 2.0;
let alpha = 1.0 - d_m * discount / 360.0;
let beta = 0.5 + d_extra / year;
let a = d_extra * alpha / (year * 2.0);
let b = alpha * beta;
let c = alpha - 1.0;
(-b + (b * b - 4.0 * a * c).sqrt()) / (2.0 * a)
};
if result.is_infinite() {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
if result.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for RRI".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_tbillprice(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let settlement = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let maturity = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let discount = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let less_than_one_year = match is_less_than_one_year(settlement as i64, maturity as i64) {
Ok(f) => f,
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
};
if settlement > maturity {
return CalcResult::new_error(
Error::NUM,
cell,
"settlement should be <= maturity".to_string(),
);
}
if !less_than_one_year {
return CalcResult::new_error(
Error::NUM,
cell,
"maturity <= settlement + year".to_string(),
);
}
if discount <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "discount should be >0".to_string());
}
let d_m = maturity - settlement;
let result = 100.0 * (1.0 - discount * d_m / 360.0);
if result.is_infinite() {
return CalcResult::new_error(Error::DIV, cell, "Division by 0".to_string());
}
if result.is_nan() || result < 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid data for RRI".to_string());
}
CalcResult::Number(result)
}
pub(crate) fn fn_tbillyield(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let settlement = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let maturity = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pr = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let less_than_one_year = match is_less_than_one_year(settlement as i64, maturity as i64) {
Ok(f) => f,
Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()),
};
if settlement > maturity {
return CalcResult::new_error(
Error::NUM,
cell,
"settlement should be <= maturity".to_string(),
);
}
if !less_than_one_year {
return CalcResult::new_error(
Error::NUM,
cell,
"maturity <= settlement + year".to_string(),
);
}
if pr <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "discount should be >0".to_string());
}
let days = maturity - settlement;
let result = (100.0 - pr) * 360.0 / (pr * days);
CalcResult::Number(result)
}
pub(crate) fn fn_dollarde(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let fractional_dollar = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let mut fraction = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if fraction < 0.0 {
return CalcResult::new_error(Error::NUM, cell, "fraction should be >= 1".to_string());
}
if fraction < 1.0 {
return CalcResult::new_error(Error::DIV, cell, "fraction should be >= 1".to_string());
}
fraction = fraction.trunc();
while fraction > 10.0 {
fraction /= 10.0;
}
let t = fractional_dollar.trunc();
let result = t + (fractional_dollar - t) * 10.0 / fraction;
CalcResult::Number(result)
}
pub(crate) fn fn_dollarfr(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let decimal_dollar = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let mut fraction = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if fraction < 0.0 {
return CalcResult::new_error(Error::NUM, cell, "fraction should be >= 1".to_string());
}
if fraction < 1.0 {
return CalcResult::new_error(Error::DIV, cell, "fraction should be >= 1".to_string());
}
fraction = fraction.trunc();
while fraction > 10.0 {
fraction /= 10.0;
}
let t = decimal_dollar.trunc();
let result = t + (decimal_dollar - t) * fraction / 10.0;
CalcResult::Number(result)
}
pub(crate) fn fn_cumipmt(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 6 {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let nper = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let start_period = match self.get_number_no_bools(&args[3], cell) {
Ok(f) => f.ceil() as i32,
Err(s) => return s,
};
let end_period = match self.get_number_no_bools(&args[4], cell) {
Ok(f) => f.trunc() as i32,
Err(s) => return s,
};
let period_type = match self.get_number_no_bools(&args[5], cell) {
Ok(f) => {
if f == 0.0 {
false
} else if f == 1.0 {
true
} else {
return CalcResult::new_error(
Error::NUM,
cell,
"invalid period type".to_string(),
);
}
}
Err(s) => return s,
};
if start_period > end_period {
return CalcResult::new_error(
Error::NUM,
cell,
"start period should come before end period".to_string(),
);
}
if rate <= 0.0 || nper <= 0.0 || pv <= 0.0 || start_period < 1 {
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
}
let mut result = 0.0;
for period in start_period..=end_period {
result += match compute_ipmt(rate, period as f64, nper, pv, 0.0, period_type) {
Ok(f) => f,
Err(error) => {
return CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
}
}
}
}
CalcResult::Number(result)
}
pub(crate) fn fn_cumprinc(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
if args.len() != 6 {
return CalcResult::new_args_number_error(cell);
}
let rate = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let nper = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let pv = match self.get_number_no_bools(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let start_period = match self.get_number_no_bools(&args[3], cell) {
Ok(f) => f.ceil() as i32,
Err(s) => return s,
};
let end_period = match self.get_number_no_bools(&args[4], cell) {
Ok(f) => f.trunc() as i32,
Err(s) => return s,
};
let period_type = match self.get_number_no_bools(&args[5], cell) {
Ok(f) => {
if f == 0.0 {
false
} else if f == 1.0 {
true
} else {
return CalcResult::new_error(
Error::NUM,
cell,
"invalid period type".to_string(),
);
}
}
Err(s) => return s,
};
if start_period > end_period {
return CalcResult::new_error(
Error::NUM,
cell,
"start period should come before end period".to_string(),
);
}
if rate <= 0.0 || nper <= 0.0 || pv <= 0.0 || start_period < 1 {
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
}
let mut result = 0.0;
for period in start_period..=end_period {
result += match compute_ppmt(rate, period as f64, nper, pv, 0.0, period_type) {
Ok(f) => f,
Err(error) => {
return CalcResult::Error {
error: error.0,
origin: cell,
message: error.1,
}
}
}
}
CalcResult::Number(result)
}
pub(crate) fn fn_ddb(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(4..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let cost = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let salvage = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let life = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period = match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
};
let factor = if arg_count > 4 {
match self.get_number_no_bools(&args[4], cell) {
Ok(f) => f,
Err(s) => return s,
}
} else {
2.0
};
if period > life || cost < 0.0 || salvage < 0.0 || period <= 0.0 || factor <= 0.0 {
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
};
let mut rate = factor / life;
if rate > 1.0 {
rate = 1.0
};
let value = if rate == 1.0 {
if period == 1.0 {
cost
} else {
0.0
}
} else {
cost * (1.0 - rate).powf(period - 1.0)
};
let new_value = cost * (1.0 - rate).powf(period);
let result = f64::max(value - f64::max(salvage, new_value), 0.0);
CalcResult::Number(result)
}
pub(crate) fn fn_db(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
let arg_count = args.len();
if !(4..=5).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let cost = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let salvage = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let life = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => return s,
};
let period = match self.get_number(&args[3], cell) {
Ok(f) => f,
Err(s) => return s,
};
let month = if arg_count > 4 {
match self.get_number_no_bools(&args[4], cell) {
Ok(f) => f.trunc(),
Err(s) => return s,
}
} else {
12.0
};
if month == 12.0 && period > life
|| (period > life + 1.0)
|| month <= 0.0
|| month > 12.0
|| period <= 0.0
|| cost < 0.0
{
return CalcResult::new_error(Error::NUM, cell, "invalid parameters".to_string());
};
if cost == 0.0 {
return CalcResult::Number(0.0);
}
let rate = f64::round((1.0 - f64::powf(salvage / cost, 1.0 / life)) * 1000.0) / 1000.0;
let mut result = cost * rate * month / 12.0;
let period = period.floor() as i32;
let life = life.floor() as i32;
if period == 1 {
return CalcResult::Number(result);
};
for _ in 0..period - 2 {
result += (cost - result) * rate;
}
if period == life + 1 {
return CalcResult::Number((cost - result) * rate * (12.0 - month) / 12.0);
}
CalcResult::Number(rate * (cost - result))
}
}