Shared Rust kernels
ported/cashflow_rate_family
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
numeric_from_cell · 12 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubfn numeric_from_cell(cell: &CalcValue) -> Result<f64, CashflowRateEvalError> {
match cell.core_ref() {
CoreRef::Number(n) => Ok(n),
CoreRef::Error(code) => Err(CashflowRateEvalError::Domain(code)),
CoreRef::Text(_)
| CoreRef::Logical(_)
| CoreRef::Empty
| CoreRef::Missing
| CoreRef::Array(_)
| CoreRef::Reference(_) => Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value)),
}
}
Used by 3 functions.
scalar_number_from_eval · 19 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubpub(crate) fn scalar_number_from_eval(value: &CalcValue) -> Result<f64, CashflowRateEvalError> {
match value.core_ref() {
CoreRef::Number(n) => Ok(n),
CoreRef::Logical(flag) => Ok(if flag { 1.0 } else { 0.0 }),
CoreRef::Error(code) => Err(CashflowRateEvalError::Domain(code)),
CoreRef::Array(array) => {
let shape = array.shape();
if shape.rows == 1 && shape.cols == 1 {
numeric_from_cell(array.get(0, 0).expect("single cell"))
} else {
Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value))
}
}
CoreRef::Text(_) | CoreRef::Reference(_) => {
Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value))
}
_ => Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value)),
}
}
Used by 3 functions.
collect_numeric_vector_from_eval · 21 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubpub(crate) fn collect_numeric_vector_from_eval(value: &CalcValue) -> Result<Vec<f64>, CashflowRateEvalError> {
match value.core_ref() {
CoreRef::Number(n) => Ok(vec![n]),
CoreRef::Error(code) => Err(CashflowRateEvalError::Domain(code)),
CoreRef::Array(array) => {
let shape = array.shape();
if shape.rows > 1 && shape.cols > 1 {
return Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Ref));
}
let mut out = Vec::with_capacity(shape.rows * shape.cols);
for cell in array.iter_row_major() {
out.push(numeric_from_cell(cell)?);
}
Ok(out)
}
CoreRef::Text(_) | CoreRef::Logical(_) | CoreRef::Reference(_) => {
Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value))
}
_ => Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value)),
}
}
Used by 2 functions.
serial_from_number · 10 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubfn serial_from_number(value: f64) -> Result<i64, CashflowRateEvalError> {
if !value.is_finite() {
return Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Num));
}
let serial = value.trunc() as i64;
if serial < 0 {
return Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Num));
}
Ok(serial)
}
Used by 2 functions.
collect_serial_vector_from_eval · 21 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubpub(crate) fn collect_serial_vector_from_eval(value: &CalcValue) -> Result<Vec<i64>, CashflowRateEvalError> {
match value.core_ref() {
CoreRef::Number(n) => Ok(vec![serial_from_number(n)?]),
CoreRef::Error(code) => Err(CashflowRateEvalError::Domain(code)),
CoreRef::Array(array) => {
let shape = array.shape();
if shape.rows > 1 && shape.cols > 1 {
return Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Ref));
}
let mut out = Vec::with_capacity(shape.rows * shape.cols);
for cell in array.iter_row_major() {
out.push(serial_from_number(numeric_from_cell(cell)?)?);
}
Ok(out)
}
CoreRef::Text(_) | CoreRef::Logical(_) | CoreRef::Reference(_) => {
Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value))
}
_ => Err(CashflowRateEvalError::Domain(WorksheetErrorCode::Value)),
}
}
Used by 2 functions.
validate_cashflows · 14 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubfn validate_cashflows(cashflows: &[f64]) -> Result<(), WorksheetErrorCode> {
if cashflows.len() < 2 {
return Err(WorksheetErrorCode::Num);
}
if !cashflows.iter().all(|value| value.is_finite()) {
return Err(WorksheetErrorCode::Value);
}
let has_positive = cashflows.iter().any(|value| *value > 0.0);
let has_negative = cashflows.iter().any(|value| *value < 0.0);
if !has_positive || !has_negative {
return Err(WorksheetErrorCode::Num);
}
Ok(())
}
Used by 2 functions.
validate_xnpv_inputs · 13 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubfn validate_xnpv_inputs(values: &[f64], dates: &[i64]) -> Result<(), WorksheetErrorCode> {
if values.len() != dates.len() || dates.is_empty() {
return Err(WorksheetErrorCode::Num);
}
if !values.iter().all(|v| v.is_finite()) {
return Err(WorksheetErrorCode::Value);
}
let anchor = dates[0];
if dates.iter().any(|date| *date < anchor) {
return Err(WorksheetErrorCode::Num);
}
Ok(())
}
Used by 2 functions.
bounded_rate_solve · 62 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubfn bounded_rate_solve<F, D>(
guess: f64,
mut f: F,
mut derivative: D,
) -> Result<f64, WorksheetErrorCode>
where
F: FnMut(f64) -> Result<f64, WorksheetErrorCode>,
D: FnMut(f64) -> Result<f64, WorksheetErrorCode>,
{
let mut prev_rate = 0.0;
let mut prev_value = f(prev_rate)?;
let mut current_rate = if guess <= MIN_VALID_RATE { 0.1 } else { guess };
let mut current_value = f(current_rate)?;
for _ in 0..ROOT_MAX_ITERATIONS {
if current_value.abs() <= ROOT_TOLERANCE {
return polish_rate(current_rate, &mut f, &mut derivative);
}
let secant_next = if (current_value - prev_value).abs() > ROOT_DERIVATIVE_EPS {
let next = current_rate
- current_value * (current_rate - prev_rate) / (current_value - prev_value);
(next.is_finite() && next > MIN_VALID_RATE).then_some(next)
} else {
None
};
let newton_next = if let Ok(deriv) = derivative(current_rate) {
if deriv.abs() > ROOT_DERIVATIVE_EPS {
let next = current_rate - current_value / deriv;
(next.is_finite() && next > MIN_VALID_RATE).then_some(next)
} else {
None
}
} else {
None
};
let next_rate = secant_next.or(newton_next).unwrap_or_else(|| {
if current_rate > 0.0 {
current_rate / 2.0
} else {
0.1
}
});
prev_rate = current_rate;
prev_value = current_value;
current_rate = if next_rate <= MIN_VALID_RATE {
0.1
} else {
next_rate
};
current_value = f(current_rate)?;
}
if current_value.abs() <= ROOT_TOLERANCE {
polish_rate(current_rate, &mut f, &mut derivative)
} else {
Err(WorksheetErrorCode::Num)
}
}
Used by 2 functions.
polish_rate · 41 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHub/// Once the bracketed solve is within `ROOT_TOLERANCE` of a root, the residual
/// `|NPV|` can still leave the *rate* far (10^4–10^5 ULP) from the true root when
/// the NPV slope is gentle — beyond the `IRR_PUBLICATION_ULP_SCAN_RADIUS` plateau
/// scan that selects Excel's published double. Polish the seed with analytic
/// Newton to a rate fixpoint so the plateau sits adjacent to the |NPV|-minimal
/// double (for a representable exact root this lands on it bit-for-bit).
fn polish_rate<F, D>(seed: f64, f: &mut F, derivative: &mut D) -> Result<f64, WorksheetErrorCode>
where
F: FnMut(f64) -> Result<f64, WorksheetErrorCode>,
D: FnMut(f64) -> Result<f64, WorksheetErrorCode>,
{
let mut rate = seed;
let mut last_step = f64::INFINITY;
for _ in 0..ROOT_POLISH_ITERATIONS {
let value = f(rate)?;
let deriv = match derivative(rate) {
// Currency units do not decide whether a nonzero slope can polish a rate.
Ok(d) if d != 0.0 => d,
_ => break,
};
let next = rate - value / deriv;
if !next.is_finite() || next <= MIN_VALID_RATE {
break;
}
if next.to_bits() == rate.to_bits() {
break; // bit-level fixpoint
}
let step = (next - rate).abs();
if step >= last_step {
// No longer contracting (rounding noise near the root): keep whichever
// of the adjacent candidates has the smaller residual.
if f(next)?.abs() <= value.abs() {
rate = next;
}
break;
}
last_step = step;
rate = next;
}
Ok(rate)
}
Used by 2 functions.
map_cashflow_rate_error_to_ws · 6 lines · calc-func/src/ported/cashflow_rate_family.rs on GitHubpub fn map_cashflow_rate_error_to_ws(error: &CashflowRateEvalError) -> WorksheetErrorCode {
match error {
CashflowRateEvalError::ArityMismatch { .. } => WorksheetErrorCode::Value,
CashflowRateEvalError::Domain(code) => *code,
}
}
Used by 3 functions.