///|
fn value_as_number_opt_financial(value : FormulaValue) -> Double? {
match normalize_scalar(value) {
Number(num) => Some(num)
Bool(flag) => Some(if flag { 1.0 } else { 0.0 })
Empty => Some(0.0)
String(text) => parse_double_opt(text)
Error(_) => Some(0.0)
List(_) => None
}
}
///|
fn value_as_number_financial(value : FormulaValue) -> Double {
match value_as_number_opt_financial(value) {
Some(num) => num
None => 0.0
}
}
///|
fn validate_frequency(freq : Double) -> Bool {
freq == 1.0 || freq == 2.0 || freq == 4.0
}
///|
fn get_year_days(year : Int, basis : Int) -> Int {
match basis {
1 => if is_leap_year(year) { 366 } else { 365 }
3 => 365
_ => 360
}
}
///|
fn is_30_basis_method(basis : Int) -> Bool {
basis == 0 || basis == 4
}
///|
fn get_days_in_month_range(from_month : Int, to_month : Int) -> Int {
if from_month > to_month {
return 0
}
(to_month - from_month + 1) * 30
}
///|
fn get_day_on_basis(year : Int, month : Int, day : Int, basis : Int) -> Int {
if !is_30_basis_method(basis) {
return day
}
let dim = days_in_month(year, month)
if day > 30 || day >= dim {
30
} else {
day
}
}
///|
fn coupdays_between(
from_serial : Double,
to_serial : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Double {
let (from_year, from_month, from_day_raw) = match
date_parts_from_serial(from_serial, use_1904_dates~) {
Some(parts) => parts
None => return 0.0
}
let (to_year, to_month, to_day_raw) = match
date_parts_from_serial(to_serial, use_1904_dates~) {
Some(parts) => parts
None => return 0.0
}
let mut from_day = get_day_on_basis(
from_year, from_month, from_day_raw, basis,
)
let mut to_day = get_day_on_basis(to_year, to_month, to_day_raw, basis)
if !is_30_basis_method(basis) {
let from_adj = match
excel_serial_from_date(from_year, from_month, from_day, use_1904_dates~) {
Some(serial) => serial
None => return 0.0
}
let to_adj = match
excel_serial_from_date(to_year, to_month, to_day, use_1904_dates~) {
Some(serial) => serial
None => return 0.0
}
return to_adj - from_adj
}
if basis == 0 {
if (from_month == 2 || from_day < 30) && to_day_raw == 31 {
to_day = 31
}
} else {
if from_month == 2 && from_day == 30 {
from_day = days_in_month(from_year, 2)
}
if to_month == 2 && to_day == 30 {
to_day = days_in_month(to_year, 2)
}
}
let mut days = 0
if from_year < to_year || (from_year == to_year && from_month < to_month) {
days = 30 - from_day + 1
from_day = 1
let mut year = from_year
let mut month = from_month + 1
if month > 12 {
month = 1
year = year + 1
}
if year < to_year {
days = days + get_days_in_month_range(month, 12)
year = year + 1
month = 1
}
days = days + get_days_in_month_range(month, to_month - 1)
}
days = days + to_day - from_day
if days > 0 {
Double::from_int(days)
} else {
0.0
}
}
///|
fn coupon_date_serial(
name : String,
settlement_serial : Double,
maturity_serial : Double,
frequency : Double,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
let (settle_year, settle_month, settle_day) = match
date_parts_from_serial(settlement_serial, use_1904_dates~) {
Some(parts) => parts
None => return Err(Error(formula_error_value))
}
let (mat_year, mat_month, mat_day) = match
date_parts_from_serial(maturity_serial, use_1904_dates~) {
Some(parts) => parts
None => return Err(Error(formula_error_value))
}
let maturity_months = (mat_year - settle_year) * 12 +
(mat_month - settle_month)
let coupon = 12 / Double::to_int(trunc_double(frequency))
let mod_months = maturity_months % coupon
let mut year = settle_year
let mut month = settle_month
if mod_months == 0 && settle_day >= mat_day {
month = month + coupon
} else {
month = month + mod_months
}
if name != "COUPNCD" {
month = month - coupon
}
let (year_norm, month_norm) = normalize_year_month(year, month)
year = year_norm
month = month_norm
let mut day = mat_day
let days = days_in_month(year, month)
if days_in_month(mat_year, mat_month) == mat_day {
day = days
} else if day > 27 && day > days {
day = days
}
match excel_serial_from_date(year, month, day, use_1904_dates~) {
Some(serial) => Ok(serial)
None => Err(Error(formula_error_num))
}
}
///|
fn prepare_coupon_args(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> Result[(Double, Double, Double, Int), FormulaValue] {
if values.length() != 3 && values.length() != 4 {
return Err(Error(formula_error_value))
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
if settlement >= maturity {
return Err(Error(formula_error_num))
}
let frequency = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return Err(err)
}
if !validate_frequency(frequency) {
return Err(Error(formula_error_num))
}
let basis = if values.length() == 4 {
match value_as_number(values[3]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return Err(err)
}
} else {
0
}
Ok((settlement, maturity, frequency, basis))
}
///|
fn coupdaybs_value(
settlement : Double,
maturity : Double,
frequency : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
let pcd = match
coupon_date_serial(
"COUPPCD",
settlement,
maturity,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return Err(err)
}
Ok(coupdays_between(pcd, settlement, basis, use_1904_dates~))
}
///|
fn coupdays_value(
settlement : Double,
maturity : Double,
frequency : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
if basis == 1 {
let pcd = match
coupon_date_serial(
"COUPPCD",
settlement,
maturity,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return Err(err)
}
let months = 12 / Double::to_int(trunc_double(frequency))
let next = match date_parts_from_serial(pcd, use_1904_dates~) {
Some((year, month, day)) => {
let (new_year, new_month, new_day) = normalize_date_parts(
year,
month + months,
day,
)
match
excel_serial_from_date(new_year, new_month, new_day, use_1904_dates~) {
Some(serial) => serial
None => return Err(Error(formula_error_num))
}
}
None => return Err(Error(formula_error_value))
}
return Ok(coupdays_between(pcd, next, basis, use_1904_dates~))
}
let year_days = get_year_days(0, basis)
Ok(Double::from_int(year_days) / frequency)
}
///|
fn coupdaysnc_value(
settlement : Double,
maturity : Double,
frequency : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
let ncd = match
coupon_date_serial(
"COUPNCD",
settlement,
maturity,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return Err(err)
}
Ok(coupdays_between(settlement, ncd, basis, use_1904_dates~))
}
///|
fn coupnum_value(
settlement : Double,
maturity : Double,
frequency : Double,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
match yearfrac_value(settlement, maturity, 0, use_1904_dates~) {
Number(num) => Ok(Double::ceil(num * frequency))
Error(err) => Err(Error(err))
_ => Err(Error(formula_error_value))
}
}
///|
fn yearfrac_number(
start_serial : Double,
end_serial : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
match yearfrac_value(start_serial, end_serial, basis, use_1904_dates~) {
Number(num) => Ok(num)
Error(err) => Err(Error(err))
_ => Err(Error(formula_error_value))
}
}
///|
fn round_half_up(value : Double) -> Double {
if value < 0.0 {
-Double::from_int(Double::to_int(trunc_double(-value + 0.5)))
} else {
Double::from_int(Double::to_int(trunc_double(value + 0.5)))
}
}
///|
fn amor_date_serial(
value : FormulaValue,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
let normalized = normalize_scalar(value)
match normalized {
String(text) => {
let trimmed = text.trim().to_owned()
if trimmed == "" {
Err(Error(formula_error_value))
} else {
value_as_date_serial(String(trimmed), use_1904_dates~)
}
}
_ => value_as_date_serial(normalized, use_1904_dates~)
}
}
///|
priv struct AmorArgs {
cost : Double
date_purchased : Double
first_period : Double
salvage : Double
period : Int
rate : Double
basis : Int
}
///|
fn prepare_amor_args(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> Result[AmorArgs, FormulaValue] {
if values.length() != 6 && values.length() != 7 {
return Err(Error(formula_error_value))
}
let cost = match value_as_number(values[0]) {
Ok(num) => num
Err(_) => return Err(Error(formula_error_value))
}
if cost < 0.0 {
return Err(Error(formula_error_value))
}
let date_purchased = match amor_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let first_period = match amor_date_serial(values[2], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
if first_period < date_purchased {
return Err(Error(formula_error_num))
}
let salvage = match value_as_number(values[3]) {
Ok(num) => num
Err(_) => return Err(Error(formula_error_num))
}
if salvage < 0.0 || salvage > cost {
return Err(Error(formula_error_num))
}
let period_value = match value_as_number(values[4]) {
Ok(num) => num
Err(_) => return Err(Error(formula_error_num))
}
if period_value < 0.0 {
return Err(Error(formula_error_num))
}
let rate = match value_as_number(values[5]) {
Ok(num) => num
Err(_) => return Err(Error(formula_error_num))
}
if rate < 0.0 {
return Err(Error(formula_error_num))
}
let basis_value = if values.length() == 7 {
match value_as_number(values[6]) {
Ok(num) => num
Err(_) => return Err(Error(formula_error_num))
}
} else {
0.0
}
Ok({
cost,
date_purchased,
first_period,
salvage,
period: Double::to_int(trunc_double(period_value)),
rate,
basis: Double::to_int(trunc_double(basis_value)),
})
}
///|
fn amordegrc_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let args = match prepare_amor_args(values, use_1904_dates~) {
Ok(value) => value
Err(err) => return err
}
if args.rate >= 0.5 {
return Error(formula_error_num)
}
let assets_life = 1.0 / args.rate
let mut amor_coeff = 2.5
if assets_life < 3.0 {
amor_coeff = 1.0
} else if assets_life < 5.0 {
amor_coeff = 1.5
} else if assets_life <= 6.0 {
amor_coeff = 2.0
}
let rate = args.rate * amor_coeff
let frac = match
yearfrac_number(
args.date_purchased,
args.first_period,
args.basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
let mut cost = args.cost
let mut n_rate = round_half_up(frac * cost * rate)
cost = cost - n_rate
if args.period == 0 {
return number_or_num_error(round_significant_digits(n_rate, 15))
}
let mut rest = cost - args.salvage
for idx in 0.. FormulaValue {
let args = match prepare_amor_args(values, use_1904_dates~) {
Ok(value) => value
Err(err) => return err
}
let frac = match
yearfrac_number(
args.date_purchased,
args.first_period,
args.basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
let rate1 = frac * args.cost * args.rate
if args.period == 0 {
return number_or_num_error(round_significant_digits(rate1, 15))
}
let rate2 = args.cost * args.rate
if rate2 == 0.0 {
return Number(0.0)
}
let delta = args.cost - args.salvage
let periods = Double::to_int(trunc_double((delta - rate1) / rate2))
if args.period <= periods {
return number_or_num_error(round_significant_digits(rate2, 15))
}
if args.period - 1 == periods {
let result = delta - rate2 * Double::from_int(periods) - rate1
return number_or_num_error(round_significant_digits(result, 15))
}
Number(0.0)
}
///|
priv struct ConversionUnit {
group : Int
allow_prefix : Bool
}
///|
let conversion_units : Map[String, ConversionUnit] = {
"g": { group: category_weight_and_mass, allow_prefix: true },
"sg": { group: category_weight_and_mass, allow_prefix: false },
"lbm": { group: category_weight_and_mass, allow_prefix: false },
"u": { group: category_weight_and_mass, allow_prefix: true },
"ozm": { group: category_weight_and_mass, allow_prefix: false },
"grain": { group: category_weight_and_mass, allow_prefix: false },
"cwt": { group: category_weight_and_mass, allow_prefix: false },
"shweight": { group: category_weight_and_mass, allow_prefix: false },
"uk_cwt": { group: category_weight_and_mass, allow_prefix: false },
"lcwt": { group: category_weight_and_mass, allow_prefix: false },
"hweight": { group: category_weight_and_mass, allow_prefix: false },
"stone": { group: category_weight_and_mass, allow_prefix: false },
"ton": { group: category_weight_and_mass, allow_prefix: false },
"uk_ton": { group: category_weight_and_mass, allow_prefix: false },
"LTON": { group: category_weight_and_mass, allow_prefix: false },
"brton": { group: category_weight_and_mass, allow_prefix: false },
"m": { group: category_distance, allow_prefix: true },
"mi": { group: category_distance, allow_prefix: false },
"Nmi": { group: category_distance, allow_prefix: false },
"in": { group: category_distance, allow_prefix: false },
"ft": { group: category_distance, allow_prefix: false },
"yd": { group: category_distance, allow_prefix: false },
"ang": { group: category_distance, allow_prefix: true },
"ell": { group: category_distance, allow_prefix: false },
"ly": { group: category_distance, allow_prefix: false },
"parsec": { group: category_distance, allow_prefix: false },
"pc": { group: category_distance, allow_prefix: false },
"Pica": { group: category_distance, allow_prefix: false },
"Picapt": { group: category_distance, allow_prefix: false },
"pica": { group: category_distance, allow_prefix: false },
"survey_mi": { group: category_distance, allow_prefix: false },
"yr": { group: category_time, allow_prefix: false },
"day": { group: category_time, allow_prefix: false },
"d": { group: category_time, allow_prefix: false },
"hr": { group: category_time, allow_prefix: false },
"mn": { group: category_time, allow_prefix: false },
"min": { group: category_time, allow_prefix: false },
"sec": { group: category_time, allow_prefix: true },
"s": { group: category_time, allow_prefix: true },
"Pa": { group: category_pressure, allow_prefix: true },
"p": { group: category_pressure, allow_prefix: true },
"atm": { group: category_pressure, allow_prefix: true },
"at": { group: category_pressure, allow_prefix: true },
"mmHg": { group: category_pressure, allow_prefix: true },
"psi": { group: category_pressure, allow_prefix: true },
"Torr": { group: category_pressure, allow_prefix: true },
"N": { group: category_force, allow_prefix: true },
"dyn": { group: category_force, allow_prefix: true },
"dy": { group: category_force, allow_prefix: true },
"lbf": { group: category_force, allow_prefix: false },
"pond": { group: category_force, allow_prefix: true },
"J": { group: category_energy, allow_prefix: true },
"e": { group: category_energy, allow_prefix: true },
"c": { group: category_energy, allow_prefix: true },
"cal": { group: category_energy, allow_prefix: true },
"eV": { group: category_energy, allow_prefix: true },
"ev": { group: category_energy, allow_prefix: true },
"HPh": { group: category_energy, allow_prefix: false },
"hh": { group: category_energy, allow_prefix: false },
"Wh": { group: category_energy, allow_prefix: true },
"wh": { group: category_energy, allow_prefix: true },
"flb": { group: category_energy, allow_prefix: false },
"BTU": { group: category_energy, allow_prefix: false },
"btu": { group: category_energy, allow_prefix: false },
"HP": { group: category_power, allow_prefix: false },
"h": { group: category_power, allow_prefix: false },
"W": { group: category_power, allow_prefix: true },
"w": { group: category_power, allow_prefix: true },
"PS": { group: category_power, allow_prefix: false },
"T": { group: category_magnetism, allow_prefix: true },
"ga": { group: category_magnetism, allow_prefix: true },
"C": { group: category_temperature, allow_prefix: false },
"cel": { group: category_temperature, allow_prefix: false },
"F": { group: category_temperature, allow_prefix: false },
"fah": { group: category_temperature, allow_prefix: false },
"K": { group: category_temperature, allow_prefix: false },
"kel": { group: category_temperature, allow_prefix: false },
"Rank": { group: category_temperature, allow_prefix: false },
"Reau": { group: category_temperature, allow_prefix: false },
"l": { group: category_volume_and_liquid_measure, allow_prefix: true },
"L": { group: category_volume_and_liquid_measure, allow_prefix: true },
"lt": { group: category_volume_and_liquid_measure, allow_prefix: true },
"tsp": { group: category_volume_and_liquid_measure, allow_prefix: false },
"tspm": { group: category_volume_and_liquid_measure, allow_prefix: false },
"tbs": { group: category_volume_and_liquid_measure, allow_prefix: false },
"oz": { group: category_volume_and_liquid_measure, allow_prefix: false },
"cup": { group: category_volume_and_liquid_measure, allow_prefix: false },
"pt": { group: category_volume_and_liquid_measure, allow_prefix: false },
"us_pt": { group: category_volume_and_liquid_measure, allow_prefix: false },
"uk_pt": { group: category_volume_and_liquid_measure, allow_prefix: false },
"qt": { group: category_volume_and_liquid_measure, allow_prefix: false },
"uk_qt": { group: category_volume_and_liquid_measure, allow_prefix: false },
"gal": { group: category_volume_and_liquid_measure, allow_prefix: false },
"uk_gal": { group: category_volume_and_liquid_measure, allow_prefix: false },
"ang3": { group: category_volume_and_liquid_measure, allow_prefix: true },
"ang^3": { group: category_volume_and_liquid_measure, allow_prefix: true },
"barrel": { group: category_volume_and_liquid_measure, allow_prefix: false },
"bushel": { group: category_volume_and_liquid_measure, allow_prefix: false },
"in3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"in^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"ft3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"ft^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"ly3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"ly^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"m3": { group: category_volume_and_liquid_measure, allow_prefix: true },
"m^3": { group: category_volume_and_liquid_measure, allow_prefix: true },
"mi3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"mi^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"yd3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"yd^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"Nmi3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"Nmi^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"Pica3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"Pica^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"Picapt3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"Picapt^3": { group: category_volume_and_liquid_measure, allow_prefix: false },
"GRT": { group: category_volume_and_liquid_measure, allow_prefix: false },
"regton": { group: category_volume_and_liquid_measure, allow_prefix: false },
"MTON": { group: category_volume_and_liquid_measure, allow_prefix: false },
"ha": { group: category_area, allow_prefix: true },
"uk_acre": { group: category_area, allow_prefix: false },
"us_acre": { group: category_area, allow_prefix: false },
"ang2": { group: category_area, allow_prefix: true },
"ang^2": { group: category_area, allow_prefix: true },
"ar": { group: category_area, allow_prefix: true },
"ft2": { group: category_area, allow_prefix: false },
"ft^2": { group: category_area, allow_prefix: false },
"in2": { group: category_area, allow_prefix: false },
"in^2": { group: category_area, allow_prefix: false },
"ly2": { group: category_area, allow_prefix: false },
"ly^2": { group: category_area, allow_prefix: false },
"m2": { group: category_area, allow_prefix: true },
"m^2": { group: category_area, allow_prefix: true },
"Morgen": { group: category_area, allow_prefix: false },
"mi2": { group: category_area, allow_prefix: false },
"mi^2": { group: category_area, allow_prefix: false },
"Nmi2": { group: category_area, allow_prefix: false },
"Nmi^2": { group: category_area, allow_prefix: false },
"Pica2": { group: category_area, allow_prefix: false },
"Pica^2": { group: category_area, allow_prefix: false },
"Picapt2": { group: category_area, allow_prefix: false },
"Picapt^2": { group: category_area, allow_prefix: false },
"yd2": { group: category_area, allow_prefix: false },
"yd^2": { group: category_area, allow_prefix: false },
"byte": { group: category_information, allow_prefix: true },
"bit": { group: category_information, allow_prefix: true },
"m/s": { group: category_speed, allow_prefix: true },
"m/sec": { group: category_speed, allow_prefix: true },
"m/h": { group: category_speed, allow_prefix: true },
"m/hr": { group: category_speed, allow_prefix: true },
"mph": { group: category_speed, allow_prefix: false },
"admkn": { group: category_speed, allow_prefix: false },
"kn": { group: category_speed, allow_prefix: false },
}
///|
let conversion_multipliers : Map[String, Double] = {
"Y": 1.0e24,
"Z": 1.0e21,
"E": 1.0e18,
"P": 1.0e15,
"T": 1.0e12,
"G": 1.0e9,
"M": 1.0e6,
"k": 1.0e3,
"h": 1.0e2,
"e": 1.0e1,
"da": 1.0e1,
"d": 1.0e-1,
"c": 1.0e-2,
"m": 1.0e-3,
"u": 1.0e-6,
"n": 1.0e-9,
"p": 1.0e-12,
"f": 1.0e-15,
"a": 1.0e-18,
"z": 1.0e-21,
"y": 1.0e-24,
"Yi": @math.pow(2.0, 80.0),
"Zi": @math.pow(2.0, 70.0),
"Ei": @math.pow(2.0, 60.0),
"Pi": @math.pow(2.0, 50.0),
"Ti": @math.pow(2.0, 40.0),
"Gi": @math.pow(2.0, 30.0),
"Mi": @math.pow(2.0, 20.0),
"ki": @math.pow(2.0, 10.0),
}
///|
fn build_unit_conversions() -> Map[Int, Map[String, Double]] {
let conversions : Map[Int, Map[String, Double]] = Map([])
conversions[category_weight_and_mass] = {
"g": 1.0,
"sg": 6.85217658567918e-05,
"lbm": 2.20462262184878e-03,
"u": 6.02214179421676e+23,
"ozm": 3.52739619495804e-02,
"grain": 1.54323583529414e+01,
"cwt": 2.20462262184878e-05,
"shweight": 2.20462262184878e-05,
"uk_cwt": 1.96841305522212e-05,
"lcwt": 1.96841305522212e-05,
"hweight": 1.96841305522212e-05,
"stone": 1.57473044417770e-04,
"ton": 1.10231131092439e-06,
"uk_ton": 9.84206527611061e-07,
"LTON": 9.84206527611061e-07,
"brton": 9.84206527611061e-07,
}
conversions[category_distance] = {
"m": 1.0,
"mi": 6.21371192237334e-04,
"Nmi": 5.39956803455724e-04,
"in": 3.93700787401575e+01,
"ft": 3.28083989501312e+00,
"yd": 1.09361329833771e+00,
"ang": 1.0e+10,
"ell": 8.74890638670166e-01,
"ly": 1.05700083402462e-16,
"parsec": 3.24077928966473e-17,
"pc": 3.24077928966473e-17,
"Pica": 2.83464566929134e+03,
"Picapt": 2.83464566929134e+03,
"pica": 2.36220472440945e+02,
"survey_mi": 6.21369949494950e-04,
}
conversions[category_time] = {
"yr": 3.16880878140289e-08,
"day": 1.15740740740741e-05,
"d": 1.15740740740741e-05,
"hr": 2.77777777777778e-04,
"mn": 1.66666666666667e-02,
"min": 1.66666666666667e-02,
"sec": 1.0,
"s": 1.0,
}
conversions[category_pressure] = {
"Pa": 1.0,
"p": 1.0,
"atm": 9.86923266716013e-06,
"at": 9.86923266716013e-06,
"mmHg": 7.50063755419211e-03,
"psi": 1.45037737730209e-04,
"Torr": 7.50061682704170e-03,
}
conversions[category_force] = {
"N": 1.0,
"dyn": 1.0e+5,
"dy": 1.0e+5,
"lbf": 2.24808923655339e-01,
"pond": 1.01971621297793e+02,
}
conversions[category_energy] = {
"J": 1.0,
"e": 9.99999519343231e+06,
"c": 2.39006249473467e-01,
"cal": 2.38846190642017e-01,
"eV": 6.24145700000000e+18,
"ev": 6.24145700000000e+18,
"HPh": 3.72506430801000e-07,
"hh": 3.72506430801000e-07,
"Wh": 2.77777916238711e-04,
"wh": 2.77777916238711e-04,
"flb": 2.37304222192651e+01,
"BTU": 9.47815067349015e-04,
"btu": 9.47815067349015e-04,
}
conversions[category_power] = {
"HP": 1.0,
"h": 1.0,
"W": 7.45699871582270e+02,
"w": 7.45699871582270e+02,
"PS": 1.01386966542400e+00,
}
conversions[category_magnetism] = { "T": 1.0, "ga": 10000.0 }
conversions[category_volume_and_liquid_measure] = {
"l": 1.0,
"L": 1.0,
"lt": 1.0,
"tsp": 2.02884136211058e+02,
"tspm": 2.0e+02,
"tbs": 6.76280454036860e+01,
"oz": 3.38140227018430e+01,
"cup": 4.22675283773038e+00,
"pt": 2.11337641886519e+00,
"us_pt": 2.11337641886519e+00,
"uk_pt": 1.75975398639270e+00,
"qt": 1.05668820943259e+00,
"uk_qt": 8.79876993196351e-01,
"gal": 2.64172052358148e-01,
"uk_gal": 2.19969248299088e-01,
"ang3": 1.0e+27,
"ang^3": 1.0e+27,
"barrel": 6.28981077043211e-03,
"bushel": 2.83775932584017e-02,
"in3": 6.10237440947323e+01,
"in^3": 6.10237440947323e+01,
"ft3": 3.53146667214886e-02,
"ft^3": 3.53146667214886e-02,
"ly3": 1.18093498844171e-51,
"ly^3": 1.18093498844171e-51,
"m3": 1.0e-03,
"m^3": 1.0e-03,
"mi3": 2.39912758578928e-13,
"mi^3": 2.39912758578928e-13,
"yd3": 1.30795061931439e-03,
"yd^3": 1.30795061931439e-03,
"Nmi3": 1.57426214685811e-13,
"Nmi^3": 1.57426214685811e-13,
"Pica3": 2.27769904358706e+07,
"Pica^3": 2.27769904358706e+07,
"Picapt3": 2.27769904358706e+07,
"Picapt^3": 2.27769904358706e+07,
"GRT": 3.53146667214886e-04,
"regton": 3.53146667214886e-04,
"MTON": 8.82866668037215e-04,
}
conversions[category_area] = {
"ha": 1.0,
"uk_acre": 2.47105381467165e+00,
"us_acre": 2.47104393046628e+00,
"ang2": 1.0e+24,
"ang^2": 1.0e+24,
"ar": 1.0e+02,
"ft2": 1.07639104167097e+05,
"ft^2": 1.07639104167097e+05,
"in2": 1.55000310000620e+07,
"in^2": 1.55000310000620e+07,
"ly2": 1.11725076312873e-28,
"ly^2": 1.11725076312873e-28,
"m2": 1.0e+04,
"m^2": 1.0e+04,
"Morgen": 4.0e+00,
"mi2": 3.86102158542446e-03,
"mi^2": 3.86102158542446e-03,
"Nmi2": 2.91553349598123e-03,
"Nmi^2": 2.91553349598123e-03,
"Pica2": 8.03521607043214e+10,
"Pica^2": 8.03521607043214e+10,
"Picapt2": 8.03521607043214e+10,
"Picapt^2": 8.03521607043214e+10,
"yd2": 1.19599004630108e+04,
"yd^2": 1.19599004630108e+04,
}
conversions[category_information] = { "bit": 1.0, "byte": 0.125 }
conversions[category_speed] = {
"m/s": 1.0,
"m/sec": 1.0,
"m/h": 3.60e+03,
"m/hr": 3.60e+03,
"mph": 2.23693629205440e+00,
"admkn": 1.94260256941567e+00,
"kn": 1.94384449244060e+00,
}
conversions
}
///|
let unit_conversions : Map[Int, Map[String, Double]] = build_unit_conversions()
///|
fn get_unit_details(uom_text : String) -> (String, Int, Double, Bool) {
if uom_text.length() == 0 {
return ("", 0, 0.0, false)
}
match conversion_units.get(uom_text) {
Some(unit) => return (uom_text, unit.group, 1.0, true)
None => ()
}
let mut uom = uom_text
let mut multiplier_type = uom.unsafe_substring(start=0, end=1)
uom = uom.unsafe_substring(start=1, end=uom.length())
match
(conversion_units.get(uom), conversion_multipliers.get(multiplier_type)) {
(Some(unit), Some(multiplier)) =>
if !unit.allow_prefix {
return ("", 0, 0.0, false)
} else {
return (uom, unit.group, multiplier, true)
}
_ => ()
}
if uom.length() > 0 {
multiplier_type = multiplier_type + uom.unsafe_substring(start=0, end=1)
uom = uom.unsafe_substring(start=1, end=uom.length())
}
match
(conversion_units.get(uom), conversion_multipliers.get(multiplier_type)) {
(Some(unit), Some(multiplier)) =>
if !unit.allow_prefix {
("", 0, 0.0, false)
} else {
(uom, unit.group, multiplier, true)
}
_ => ("", 0, 0.0, false)
}
}
///|
fn resolve_temperature_synonyms(uom : String) -> String {
match uom {
"fah" => "F"
"cel" => "C"
"kel" => "K"
_ => uom
}
}
///|
fn convert_temperature(
from_uom : String,
to_uom : String,
value : Double,
) -> Double {
let from_unit = resolve_temperature_synonyms(from_uom)
let to_unit = resolve_temperature_synonyms(to_uom)
if from_unit == to_unit {
return value
}
let mut result = value
match from_unit {
"F" => result = (result - 32.0) / 1.8 + 273.15
"C" => result = result + 273.15
"Rank" => result = result / 1.8
"Reau" => result = result * 1.25 + 273.15
_ => ()
}
match to_unit {
"F" => result = (result - 273.15) * 1.8 + 32.0
"C" => result = result - 273.15
"Rank" => result = result * 1.8
"Reau" => result = (result - 273.15) * 0.8
_ => ()
}
result
}
///|
fn convert_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let number = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let from_unit = match value_as_string(values[1]) {
Ok(value) => value
Err(err) => return err
}
let to_unit = match value_as_string(values[2]) {
Ok(value) => value
Err(err) => return err
}
let (from_uom, from_category, from_multiplier, ok1) = get_unit_details(
from_unit,
)
let (to_uom, to_category, to_multiplier, ok2) = get_unit_details(to_unit)
if !ok1 || !ok2 || from_category != to_category {
return Error(formula_error_na)
}
let value = number * from_multiplier
let result = if from_uom == to_uom && from_multiplier == to_multiplier {
value / from_multiplier
} else if from_uom == to_uom {
value / to_multiplier
} else if from_category == category_temperature {
convert_temperature(from_uom, to_uom, value)
} else {
let conversions = match unit_conversions.get(from_category) {
Some(map) => map
None => return Error(formula_error_na)
}
let from_conversion = match conversions.get(from_uom) {
Some(num) => num
None => return Error(formula_error_na)
}
let to_conversion = match conversions.get(to_uom) {
Some(num) => num
None => return Error(formula_error_na)
}
let base_value = value * (1.0 / from_conversion)
base_value * to_conversion / to_multiplier
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn euroconvert_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 3 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let number = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let source_currency = match value_as_string(values[1]) {
Ok(text) => text.to_upper()
Err(err) => return err
}
let target_currency = match value_as_string(values[2]) {
Ok(text) => text.to_upper()
Err(err) => return err
}
let mut full_precision = false
if values.length() >= 4 {
full_precision = match value_as_bool(values[3]) {
Ok(flag) => flag
Err(err) => return err
}
}
let mut triangulation_precision = 0.0
if values.length() == 5 {
triangulation_precision = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
}
let convert_table : Map[String, (Double, Int)] = {
"EUR": (1.0, 2),
"ATS": (13.7603, 2),
"BEF": (40.3399, 0),
"DEM": (1.95583, 2),
"ESP": (166.386, 0),
"FIM": (5.94573, 2),
"FRF": (6.55957, 2),
"IEP": (0.787564, 2),
"ITL": (1936.27, 0),
"LUF": (40.3399, 0),
"NLG": (2.20371, 2),
"PTE": (200.482, 2),
"GRD": (340.750, 2),
"SIT": (239.640, 2),
"MTL": (0.429300, 2),
"CYP": (0.585274, 2),
"SKK": (30.1260, 2),
"EEK": (15.6466, 2),
"LVL": (0.702804, 2),
"LTL": (3.45280, 2),
}
let (source_rate, _source_decimals) = match
convert_table.get(source_currency) {
Some(value) => value
None => return Error(formula_error_value)
}
let (target_rate, target_decimals) = match
convert_table.get(target_currency) {
Some(value) => value
None => return Error(formula_error_value)
}
if source_currency == target_currency {
return Number(number)
}
let mut result = if source_currency == "EUR" {
number * target_rate
} else {
let mut intermediate = number / source_rate
if triangulation_precision != 0.0 {
let ratio = @math.pow(10.0, triangulation_precision)
intermediate = Double::round(intermediate * ratio) / ratio
}
intermediate * target_rate
}
if !full_precision {
let ratio = @math.pow(10.0, Double::from_int(target_decimals))
result = Double::round(result * ratio) / ratio
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn fv_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 3 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let nper = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let pmt = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let pv = if values.length() >= 4 {
match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let typ = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
if typ != 0.0 && typ != 1.0 {
return Error(formula_error_na)
}
let result = if rate != 0.0 {
let factor = @math.pow(1.0 + rate, nper)
-pv * factor - pmt * (1.0 + rate * typ) * (factor - 1.0) / rate
} else {
-pv - pmt * nper
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn fvschedule_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 2 {
return Error(formula_error_value)
}
let principal = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let schedule = flatten_values([values[1]])
let mut result = principal
for value in schedule {
match normalize_scalar(value) {
Empty => ()
Error(_) => ()
String(text) =>
if text == "" {
()
} else {
match parse_double_opt(text) {
Some(num) => result = result * (1.0 + num)
None => return Error(formula_error_value)
}
}
_ =>
match value_as_number(value) {
Ok(num) => result = result * (1.0 + num)
Err(err) => return err
}
}
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn calc_npv(rate : Double, values : ArrayView[FormulaValue]) -> Double {
let mut result = 0.0
let mut period = 1
for value in values {
match value_as_number_opt_financial(value) {
Some(num) => {
result = result + num / @math.pow(1.0 + rate, Double::from_int(period))
period = period + 1
}
None => ()
}
}
result
}
///|
fn npv_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 2 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let mut result = 0.0
let mut period = 1
for idx in 1.. {
result = result + num / @math.pow(1.0 + rate, Double::from_int(period))
period = period + 1
}
None => ()
}
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn pv_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 3 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let nper = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let pmt = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let fv = if values.length() >= 4 {
match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let typ = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let result = if rate == 0.0 {
-pmt * nper - fv
} else {
let factor = @math.pow(1.0 + rate, nper)
((1.0 - factor) / rate * pmt * (1.0 + rate * typ) - fv) / factor
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn calc_rate(
nper : Double,
pmt : Double,
pv : Double,
fv : Double,
typ : Double,
guess : Double,
) -> Double {
let mut rate = guess
let mut iter = 0
let mut is_close = false
while iter < rate_max_iterations && !is_close {
let t1 = @math.pow(rate + 1.0, nper)
let t2 = @math.pow(rate + 1.0, nper - 1.0)
let rt = rate * typ + 1.0
let p0 = pmt * (t1 - 1.0)
let f1 = fv + t1 * pv + p0 * rt / rate
let n1 = nper * t2 * pv
let n2 = p0 * rt / @math.pow(rate, 2.0)
let f2 = n1 - n2
let f3 = (nper * pmt * t2 * rt + p0 * typ) / rate
let delta = f1 / (f2 + f3)
if Double::abs(delta) < rate_precision {
is_close = true
}
iter = iter + 1
rate = rate - delta
}
rate
}
///|
fn rate_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 3 {
return Error(formula_error_value)
}
if values.length() > 6 {
return Error(formula_error_value)
}
let nper = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let pmt = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let fv = if values.length() >= 4 {
match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let mut typ = 0.0
if values.length() >= 5 {
match value_as_number(values[4]) {
Ok(num) => if num != 0.0 { typ = 1.0 }
Err(err) => return err
}
}
let guess = if values.length() == 6 {
match value_as_number(values[5]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.1
}
let result = calc_rate(nper, pmt, pv, fv, typ, guess)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn irr_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 1 {
return Error(formula_error_value)
}
if values.length() > 2 {
return Error(formula_error_value)
}
let list_values = flatten_values([values[0]])
let guess = if values.length() == 2 {
match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.1
}
let mut x1 = 0.0
let mut x2 = guess
let mut f1 = calc_npv(x1, list_values)
let mut f2 = calc_npv(x2, list_values)
for _ in 0.. 0.0 {
return Error(formula_error_num)
}
let f = calc_npv(x1, list_values)
let mut rtb = 0.0
let mut dx = 0.0
if f < 0.0 {
rtb = x1
dx = x2 - x1
} else {
rtb = x2
dx = x1 - x2
}
let mut x_mid = rtb
for _ in 0.. FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let list_values = flatten_values([values[0]])
let finance_rate = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let reinvest_rate = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let n = list_values.length()
let fr = 1.0 + finance_rate
let rr = 1.0 + reinvest_rate
let mut npv_pos = 0.0
let mut npv_neg = 0.0
for idx in 0..= 0.0 {
npv_pos = npv_pos + value / @math.pow(rr, Double::from_int(idx))
} else {
npv_neg = npv_neg + value / @math.pow(fr, Double::from_int(idx))
}
}
if npv_neg == 0.0 || npv_pos == 0.0 || reinvest_rate <= -1.0 {
return Error(formula_error_div)
}
let result = @math.pow(
-npv_pos * @math.pow(rr, Double::from_int(n)) / (npv_neg * rr),
1.0 / (Double::from_int(n) - 1.0),
) -
1.0
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn prepare_x_args(
values : Array[FormulaValue],
dates : Array[FormulaValue],
) -> Result[(Array[Double], Array[Double]), FormulaValue] {
let value_args : Array[Double] = []
for value in values {
match value_as_number(value) {
Ok(num) => value_args.push(num)
Err(err) => return Err(err)
}
}
if value_args.length() < 2 {
return Err(Error(formula_error_num))
}
let date_args : Array[Double] = []
let mut last_date = 0.0
for date in dates {
match normalize_scalar(date) {
Empty => return Err(Error(formula_error_value))
_ =>
match value_as_number(date) {
Ok(num) => {
if num < last_date {
return Err(Error(formula_error_value))
}
date_args.push(num)
last_date = num
}
Err(err) => return Err(err)
}
}
}
if value_args.length() != date_args.length() {
return Err(Error(formula_error_num))
}
Ok((value_args, date_args))
}
///|
fn xirr_part1(
values : Array[Double],
dates : Array[Double],
rate : Double,
) -> Double {
let r = rate + 1.0
let mut result = values[0]
let first_date = dates[0]
for idx in 1.. Double {
let r = rate + 1.0
let mut result = 0.0
let first_date = dates[0]
for idx in 1.. FormulaValue {
let mut has_positive = false
let mut has_negative = false
for value in values {
if value > 0.0 {
has_positive = true
}
if value < 0.0 {
has_negative = true
}
}
if !has_positive || !has_negative {
return Error(formula_error_num)
}
let mut result = guess
let mut count = 0
let mut failed = false
while true {
let result_value = xirr_part1(values, dates, result)
let new_rate = result - result_value / xirr_part2(values, dates, result)
let eps_rate = Double::abs(new_rate - result)
result = new_rate
count = count + 1
if eps_rate <= xirr_precision || Double::abs(result_value) <= xirr_precision {
break
}
if count > xirr_max_iterations {
failed = true
break
}
}
if failed || Double::is_nan(result) || Double::is_inf(result) {
return Error(formula_error_num)
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn xirr_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 2 && values.length() != 3 {
return Error(formula_error_value)
}
let raw_values = flatten_values([values[0]])
let raw_dates = flatten_values([values[1]])
let (value_args, date_args) = match prepare_x_args(raw_values, raw_dates) {
Ok(result) => result
Err(err) => return err
}
let mut guess = 0.0
if values.length() == 3 {
match value_as_number(values[2]) {
Ok(num) => {
if num <= -1.0 {
return Error(formula_error_value)
}
guess = num
}
Err(_) => return Error(formula_error_num)
}
}
xirr_compute(value_args, date_args, guess)
}
///|
fn xnpv_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
if rate <= 0.0 {
return Error(formula_error_value)
}
let raw_values = flatten_values([values[1]])
let raw_dates = flatten_values([values[2]])
let (value_args, date_args) = match prepare_x_args(raw_values, raw_dates) {
Ok(result) => result
Err(err) => return err
}
let date1 = date_args[0]
let mut result = 0.0
for idx in 0.. FormulaValue {
if values.length() == 0 {
return Error(formula_error_value)
}
if values.length() > 2 {
return Error(formula_error_value)
}
let number = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let mut decimals = 2
let mut rounded = number
if values.length() == 2 {
let decimals_value = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
if decimals_value >= 128.0 {
return Error(formula_error_value)
}
let digits = Double::to_int(decimals_value)
if digits < 0 {
rounded = round_down_with_digits(number, digits)
decimals = 0
} else {
decimals = digits
}
}
let symbol = "$"
let abs_value = abs_double(rounded)
let formatted = format_fixed_number(abs_value, decimals, true, 1)
if rounded < 0.0 {
String("(\{symbol}\{formatted})")
} else {
String("\{symbol}\{formatted}")
}
}
///|
fn dollar_fraction_values(
name : String,
values : ArrayView[FormulaValue],
) -> FormulaValue {
if values.length() != 2 {
return Error(formula_error_value)
}
let dollar = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let frac = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
if frac < 0.0 {
return Error(formula_error_num)
}
if frac == 0.0 {
return Error(formula_error_div)
}
let (_whole, cents_raw) = modf_double(dollar)
let mut cents = cents_raw
let exponent = Double::ceil(@math.log10(frac))
if name == "DOLLARDE" {
cents = cents / frac
cents = cents * @math.pow(10.0, exponent)
} else {
cents = cents * frac
cents = cents * @math.pow(10.0, -exponent)
}
let result = Double::floor(dollar) + cents
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn effect_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 2 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let npery = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
if rate <= 0.0 || npery < 1.0 {
return Error(formula_error_num)
}
let result = @math.pow(1.0 + rate / npery, npery) - 1.0
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn nominal_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 2 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let npery = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
if rate <= 0.0 || npery < 1.0 {
return Error(formula_error_num)
}
let result = npery * (@math.pow(rate + 1.0, 1.0 / npery) - 1.0)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn nper_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 3 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let pmt = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let fv = if values.length() >= 4 {
match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let typ = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
if typ != 0.0 && typ != 1.0 {
return Error(formula_error_na)
}
if pmt == 0.0 {
return Error(formula_error_num)
}
let result = if rate != 0.0 {
let term = pmt * (1.0 + rate * typ) / rate
@math.ln((term - fv) / (pv + term)) / @math.ln(1.0 + rate)
} else {
(-pv - fv) / pmt
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn calc_pmt(
rate : Double,
nper : Double,
pv : Double,
fv : Double,
typ : Double,
) -> Double {
if rate != 0.0 {
let factor = @math.pow(1.0 + rate, nper)
(-fv - pv * factor) / (1.0 + rate * typ) / ((factor - 1.0) / rate)
} else {
(-pv - fv) / nper
}
}
///|
fn pmt_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 3 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let nper = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let fv = if values.length() >= 4 {
match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let typ = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
if typ != 0.0 && typ != 1.0 {
return Error(formula_error_na)
}
let result = calc_pmt(rate, nper, pv, fv, typ)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn calc_ipmt(
typ : Double,
per : Double,
pmt : Double,
pv : Double,
rate : Double,
) -> (Double, Double) {
let mut capital = pv
let mut interest = 0.0
let mut principal = 0.0
let per_int = Double::to_int(per)
let mut i = 1
while i <= per_int {
if typ != 0.0 && i == 1 {
interest = 0.0
} else {
interest = -capital * rate
}
principal = pmt - interest
capital = capital + principal
i = i + 1
}
(interest, principal)
}
///|
fn ipmt_values(name : String, values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 4 {
return Error(formula_error_value)
}
if values.length() > 6 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let per = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let nper = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let fv = if values.length() >= 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
let typ = if values.length() == 6 {
match value_as_number(values[5]) {
Ok(num) => num
Err(err) => return err
}
} else {
0.0
}
if typ != 0.0 && typ != 1.0 {
return Error(formula_error_na)
}
if per <= 0.0 || per > nper {
return Error(formula_error_na)
}
let payment = calc_pmt(rate, nper, pv, fv, typ)
let (interest, principal) = calc_ipmt(typ, per, payment, pv, rate)
let value = if name == "IPMT" { interest } else { principal }
number_or_num_error(round_significant_digits(value, 15))
}
///|
fn cumip_values(
name : String,
values : ArrayView[FormulaValue],
) -> FormulaValue {
if values.length() != 6 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let nper = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let start = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let end = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
let typ = match value_as_number(values[5]) {
Ok(num) => num
Err(err) => return err
}
if typ != 0.0 && typ != 1.0 {
return Error(formula_error_na)
}
if start < 1.0 || start > end {
return Error(formula_error_na)
}
let mut total = 0.0
let mut per = start
while per <= end {
let args : Array[FormulaValue] = [
Number(rate),
Number(per),
Number(nper),
Number(pv),
Number(0.0),
Number(typ),
]
let result = ipmt_values(
if name == "CUMIPMT" {
"IPMT"
} else {
"PPMT"
},
args,
)
match result {
Number(num) => total = total + num
Error(err) => return Error(err)
_ => return Error(formula_error_value)
}
per = per + 1.0
}
number_or_num_error(round_significant_digits(total, 15))
}
///|
fn ispmt_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 4 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let per = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let nper = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let mut pr = pv
let payment = pv / nper
let mut num = 0.0
let per_int = Double::to_int(per)
let nper_int = Double::to_int(nper)
let mut i = 0
while i <= per_int {
num = rate * pr * -1.0
pr = pr - payment
if i == nper_int {
num = 0.0
}
i = i + 1
}
number_or_num_error(round_significant_digits(num, 15))
}
///|
fn rri_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let nper = match value_as_number(values[0]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let pv = match value_as_number(values[1]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let fv = match value_as_number(values[2]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
if nper <= 0.0 || pv <= 0.0 || fv < 0.0 {
return Error(formula_error_num)
}
let result = @math.pow(fv / pv, 1.0 / nper) - 1.0
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn sln_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let cost = match value_as_number(values[0]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let salvage = match value_as_number(values[1]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let life = match value_as_number(values[2]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
if life <= 0.0 {
return Error(formula_error_num)
}
let result = (cost - salvage) / life
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn syd_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 4 {
return Error(formula_error_value)
}
let cost = match value_as_number(values[0]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let salvage = match value_as_number(values[1]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let life = match value_as_number(values[2]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
let per = match value_as_number(values[3]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
if life <= 0.0 || per <= 0.0 {
return Error(formula_error_num)
}
if per > life {
return Error(formula_error_num)
}
let result = (cost - salvage) *
(life - per + 1.0) *
2.0 /
(life * (life + 1.0))
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn db_args_invalid(
cost : Double,
salvage : Double,
life : Double,
period : Double,
) -> Bool {
cost <= 0.0 || salvage / cost < 0.0 || life <= 0.0 || period < 1.0
}
///|
fn db_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 4 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let cost = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let salvage = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let life = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let period = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let month = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
12.0
}
if cost == 0.0 {
return Number(0.0)
}
if db_args_invalid(cost, salvage, life, period) || month < 1.0 {
return Error(formula_error_na)
}
let dr_raw = 1.0 - @math.pow(salvage / cost, 1.0 / life)
let dr = Double::round(dr_raw * 1000.0) / 1000.0
let mut pd = 0.0
let mut depreciation = 0.0
let period_int = Double::to_int(trunc_double(period))
let life_limit = Double::to_int(trunc_double(life + 1.0))
for per in 1..<=period_int {
if per == 1 {
depreciation = cost * dr * month / 12.0
} else if per == life_limit {
depreciation = (cost - pd) * dr * (12.0 - month) / 12.0
} else {
depreciation = (cost - pd) * dr
}
pd = pd + depreciation
}
number_or_num_error(round_significant_digits(depreciation, 15))
}
///|
fn ddb_double(
cost : Double,
salvage : Double,
life : Double,
period : Double,
factor : Double,
) -> Double {
let mut pd = 0.0
let mut depreciation = 0.0
let period_int = Double::to_int(trunc_double(period))
for _ in 1..<=period_int {
let term = (cost - pd) * (factor / life)
let cap = cost - salvage - pd
depreciation = if term < cap { term } else { cap }
pd = pd + depreciation
}
depreciation
}
///|
fn ddb_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 4 {
return Error(formula_error_value)
}
if values.length() > 5 {
return Error(formula_error_value)
}
let cost = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let salvage = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let life = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let period = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let factor = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
} else {
2.0
}
if cost == 0.0 {
return Number(0.0)
}
if db_args_invalid(cost, salvage, life, period) ||
factor <= 0.0 ||
period > life {
return Error(formula_error_na)
}
let depreciation = ddb_double(cost, salvage, life, period, factor)
number_or_num_error(round_significant_digits(depreciation, 15))
}
///|
fn prepare_vdb_args(values : ArrayView[FormulaValue]) -> FormulaValue {
let cost = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
if cost < 0.0 {
return Error(formula_error_num)
}
let salvage = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
if salvage < 0.0 {
return Error(formula_error_num)
}
let life = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if life <= 0.0 {
return Error(formula_error_num)
}
let start_period = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if start_period < 0.0 {
return Error(formula_error_num)
}
let end_period = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
if start_period > end_period {
return Error(formula_error_num)
}
if end_period > life {
return Error(formula_error_num)
}
let factor = if values.length() > 5 {
match value_as_number(values[5]) {
Ok(num) => num
Err(_) => return Error(formula_error_num)
}
} else {
2.0
}
if factor < 0.0 {
return Error(formula_error_value)
}
List([
Number(cost),
Number(salvage),
Number(life),
Number(start_period),
Number(end_period),
Number(factor),
])
}
///|
fn vdb_partial(
cost : Double,
salvage : Double,
life : Double,
life1 : Double,
period : Double,
factor : Double,
) -> Double {
let end_int = Double::ceil(period)
let mut cs = cost - salvage
let mut now_sln = false
let mut vdb_total = 0.0
let mut ddb = 0.0
let mut sln = 0.0
let mut term = 0.0
let mut i = 1.0
while i <= end_int {
if !now_sln {
ddb = ddb_double(cost, salvage, life, i, factor)
sln = cs / (life1 - i + 1.0)
if sln > ddb && i != end_int {
term = sln
now_sln = true
} else {
term = ddb
cs = cs - ddb
}
} else {
term = sln
}
if i == end_int {
term = term * (period + 1.0 - end_int)
}
vdb_total = vdb_total + term
i = i + 1.0
}
vdb_total
}
///|
fn vdb_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() < 5 || values.length() > 7 {
return Error(formula_error_value)
}
let prepared = prepare_vdb_args(values)
let list = match prepared {
List(items) => items
Error(err) => return Error(err)
_ => return Error(formula_error_value)
}
let cost = match list[0] {
Number(num) => num
_ => return Error(formula_error_value)
}
let salvage = match list[1] {
Number(num) => num
_ => return Error(formula_error_value)
}
let life = match list[2] {
Number(num) => num
_ => return Error(formula_error_value)
}
let start_period = match list[3] {
Number(num) => num
_ => return Error(formula_error_value)
}
let end_period = match list[4] {
Number(num) => num
_ => return Error(formula_error_value)
}
let factor = match list[5] {
Number(num) => num
_ => return Error(formula_error_value)
}
let no_switch = if values.length() > 6 {
match value_as_bool(values[6]) {
Ok(flag) => flag
Err(_) => return Error(formula_error_num)
}
} else {
false
}
let start_int = Double::floor(start_period)
let end_int = Double::ceil(end_period)
if no_switch {
let mut vdb_total = 0.0
let mut i = start_int + 1.0
while i <= end_int {
let mut term = ddb_double(cost, salvage, life, i, factor)
if i == start_int + 1.0 {
let upper = if end_period < start_int + 1.0 {
end_period
} else {
start_int + 1.0
}
term = term * (upper - start_period)
} else if i == end_int {
term = term * (end_period + 1.0 - end_int)
}
vdb_total = vdb_total + term
i = i + 1.0
}
return number_or_num_error(round_significant_digits(vdb_total, 15))
}
let adjusted_cost = cost -
vdb_partial(cost, salvage, life, life, start_period, factor)
let result = vdb_partial(
adjusted_cost,
salvage,
life,
life - start_period,
end_period - start_period,
factor,
)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn pduration_values(values : ArrayView[FormulaValue]) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let rate = match value_as_number(values[0]) {
Ok(num) => num
Err(err) => return err
}
let pv = match value_as_number(values[1]) {
Ok(num) => num
Err(err) => return err
}
let fv = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if rate <= 0.0 || pv <= 0.0 || fv <= 0.0 {
return Error(formula_error_num)
}
let result = (@math.ln(fv) - @math.ln(pv)) / @math.ln(1.0 + rate)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn accrint_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() < 6 || values.length() > 8 {
return Error(formula_error_value)
}
let issue = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let settlement = match value_as_date_serial(values[2], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let rate = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let par = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
let frequency = match value_as_number(values[5]) {
Ok(num) => num
Err(err) => return err
}
if !validate_frequency(frequency) {
return Error(formula_error_num)
}
let basis = if values.length() >= 7 {
match value_as_number(values[6]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
if values.length() == 8 {
match value_as_bool(values[7]) {
Ok(_) => ()
Err(err) => return err
}
}
let frac = match yearfrac_number(issue, settlement, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = par * rate * frac
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn accrintm_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 4 && values.length() != 5 {
return Error(formula_error_value)
}
let issue = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let settlement = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
if settlement < issue {
return Error(formula_error_num)
}
let rate = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let par = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if par <= 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
let frac = match yearfrac_number(issue, settlement, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = frac * rate * par
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn coupdaybs_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, frequency, basis) = match
prepare_coupon_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
coupdaybs_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
Number(result)
}
///|
fn coupdays_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, frequency, basis) = match
prepare_coupon_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
coupdays_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
Number(result)
}
///|
fn coupdaysnc_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, frequency, basis) = match
prepare_coupon_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
coupdaysnc_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
Number(result)
}
///|
fn coupncd_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, frequency, _basis) = match
prepare_coupon_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
coupon_date_serial(
"COUPNCD",
settlement,
maturity,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return err
}
Number(result)
}
///|
fn coupnum_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, frequency, _basis) = match
prepare_coupon_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
coupnum_value(settlement, maturity, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
Number(result)
}
///|
fn couppcd_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, frequency, _basis) = match
prepare_coupon_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
coupon_date_serial(
"COUPPCD",
settlement,
maturity,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return err
}
Number(result)
}
///|
fn disc_intrate_values(
name : String,
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 4 && values.length() != 5 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
if maturity <= settlement {
return Error(formula_error_num)
}
let pr_investment = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if pr_investment <= 0.0 {
return Error(formula_error_num)
}
let redemption = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if redemption <= 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
let frac = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = if name == "INTRATE" {
(redemption - pr_investment) / pr_investment / frac
} else {
(redemption - pr_investment) / redemption / frac
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn received_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 4 && values.length() != 5 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
if maturity <= settlement {
return Error(formula_error_num)
}
let investment = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let discount = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if discount <= 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(_) => return Error(formula_error_num)
}
} else {
0
}
let frac = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = investment / (1.0 - discount * frac)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn prepare_duration_args(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> Result[(Double, Double, Double, Double, Double, Int), FormulaValue] {
if values.length() != 5 && values.length() != 6 {
return Err(Error(formula_error_value))
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
if settlement >= maturity {
return Err(Error(formula_error_num))
}
let coupon = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return Err(err)
}
if coupon < 0.0 {
return Err(Error(formula_error_num))
}
let yld = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return Err(err)
}
if yld < 0.0 {
return Err(Error(formula_error_num))
}
let frequency = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return Err(err)
}
if !validate_frequency(frequency) {
return Err(Error(formula_error_num))
}
let basis = if values.length() == 6 {
match value_as_number(values[5]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return Err(err)
}
} else {
0
}
Ok((settlement, maturity, coupon, yld, frequency, basis))
}
///|
fn duration_calc(
settlement : Double,
maturity : Double,
coupon : Double,
yld : Double,
frequency : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
let frac = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let coups = match
coupnum_value(settlement, maturity, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let coupon_cash = coupon * 100.0 / frequency
let mut yld_factor = yld / frequency
yld_factor = yld_factor + 1.0
let diff = frac * frequency - coups
let mut duration = 0.0
let mut price = 0.0
let coups_int = Double::to_int(trunc_double(coups))
let mut t = 1
while t < coups_int {
let t_value = Double::from_int(t)
let t_diff = t_value + diff
let add = coupon_cash / @math.pow(yld_factor, t_diff)
price = price + add
duration = duration + t_diff * add
t = t + 1
}
let add = (coupon_cash + 100.0) / @math.pow(yld_factor, coups + diff)
price = price + add
duration = duration + (coups + diff) * add
Ok(duration / price / frequency)
}
///|
fn duration_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, coupon, yld, frequency, basis) = match
prepare_duration_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
duration_calc(
settlement,
maturity,
coupon,
yld,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn mduration_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
let (settlement, maturity, coupon, yld, frequency, basis) = match
prepare_duration_args(values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let duration = match
duration_calc(
settlement,
maturity,
coupon,
yld,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
let result = duration / (1.0 + yld / frequency)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn price_value(
settlement : Double,
maturity : Double,
rate : Double,
yld : Double,
redemption : Double,
frequency : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
if settlement >= maturity {
return Err(Error(formula_error_num))
}
if basis < 0 || basis > 4 {
return Err(Error(formula_error_num))
}
let e = match
coupdays_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let dsc = match
coupdaysnc_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num / e
Err(err) => return Err(err)
}
let n = match
coupnum_value(settlement, maturity, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let a = match
coupdaybs_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let rate_per = rate / frequency
let yld_per = yld / frequency
let mut result = 0.0
if n > 1.0 {
let p1 = 1.0 + yld_per
result = redemption / @math.pow(p1, n - 1.0 + dsc)
result = result - 100.0 * rate_per * a / e
let t1 = 100.0 * rate_per
let t2 = 1.0 + yld_per
let n_int = Double::to_int(trunc_double(n))
for k in 0.. FormulaValue {
if settlement >= maturity {
return Error(formula_error_num)
}
let mut yield1 = 0.0
let mut yield2 = 1.0
let mut price1 = match
price_value(
settlement,
maturity,
rate,
yield1,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
let mut price2 = match
price_value(
settlement,
maturity,
rate,
yield2,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
let mut yield_n = (yield2 - yield1) * 0.5
let mut price_n = 0.0
let mut iter = 0
while iter < 100 && price_n != pr {
price_n = match
price_value(
settlement,
maturity,
rate,
yield_n,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
if pr == price1 {
return number_or_num_error(round_significant_digits(yield1, 15))
} else if pr == price2 {
return number_or_num_error(round_significant_digits(yield2, 15))
} else if pr == price_n {
return number_or_num_error(round_significant_digits(yield_n, 15))
} else if pr < price2 {
yield2 = yield2 * 2.0
price2 = match
price_value(
settlement,
maturity,
rate,
yield2,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
yield_n = (yield2 - yield1) * 0.5
} else {
if pr < price_n {
yield1 = yield_n
price1 = price_n
} else {
yield2 = yield_n
price2 = price_n
}
let f1 = (yield2 - yield1) * ((pr - price2) / (price1 - price2))
yield_n = yield2 - f1
}
iter = iter + 1
}
number_or_num_error(round_significant_digits(yield_n, 15))
}
///|
fn check_price_yield_args(
name : String,
rate : Double,
pr_yld : Double,
redemption : Double,
frequency : Double,
) -> FormulaValue {
if rate < 0.0 {
return Error(formula_error_num)
}
if name == "PRICE" {
if pr_yld < 0.0 || redemption <= 0.0 {
return Error(formula_error_num)
}
} else if pr_yld <= 0.0 || redemption < 0.0 {
return Error(formula_error_num)
}
if !validate_frequency(frequency) {
return Error(formula_error_num)
}
Empty
}
///|
fn price_yield_values(
name : String,
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 6 && values.length() != 7 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let rate = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
let pr_yld = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
let redemption = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
let frequency = match value_as_number(values[5]) {
Ok(num) => num
Err(err) => return err
}
let check = check_price_yield_args(name, rate, pr_yld, redemption, frequency)
match check {
Empty => ()
_ => return check
}
let basis = if values.length() == 7 {
match value_as_number(values[6]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
if name == "PRICE" {
let result = match
price_value(
settlement,
maturity,
rate,
pr_yld,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
return number_or_num_error(round_significant_digits(result, 15))
}
yield_value(
settlement,
maturity,
rate,
pr_yld,
redemption,
frequency,
basis,
use_1904_dates~,
)
}
///|
fn pricedisc_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 4 && values.length() != 5 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
if maturity <= settlement {
return Error(formula_error_num)
}
let discount = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if discount <= 0.0 {
return Error(formula_error_num)
}
let redemption = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if redemption <= 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
let frac = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = redemption * (1.0 - discount * frac)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn pricemat_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 5 && values.length() != 6 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let issue = match value_as_date_serial(values[2], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
if settlement >= maturity {
return Error(formula_error_num)
}
if issue >= settlement {
return Error(formula_error_num)
}
let rate = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if rate < 0.0 {
return Error(formula_error_num)
}
let yld = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
if yld < 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 6 {
match value_as_number(values[5]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
let dsm = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dis = match yearfrac_number(issue, settlement, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dim = match yearfrac_number(issue, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = ((1.0 + dim * rate) / (1.0 + dsm * yld) - dis * rate) * 100.0
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn tbilleq_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dsm = maturity - settlement
if dsm > 365.0 || maturity <= settlement {
return Error(formula_error_num)
}
let discount = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if discount <= 0.0 {
return Error(formula_error_num)
}
let result = 365.0 * discount / (360.0 - discount * dsm)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn tbillprice_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dsm = maturity - settlement
if dsm > 365.0 || maturity <= settlement {
return Error(formula_error_num)
}
let discount = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if discount <= 0.0 {
return Error(formula_error_num)
}
let result = 100.0 * (1.0 - discount * dsm / 360.0)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn tbillyield_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 3 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dsm = maturity - settlement
if dsm > 365.0 || maturity <= settlement {
return Error(formula_error_num)
}
let pr = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if pr <= 0.0 {
return Error(formula_error_num)
}
let result = (100.0 - pr) / pr * (360.0 / dsm)
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn yielddisc_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 4 && values.length() != 5 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let pr = match value_as_number(values[2]) {
Ok(num) => num
Err(err) => return err
}
if pr <= 0.0 {
return Error(formula_error_num)
}
let redemption = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if redemption <= 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 5 {
match value_as_number(values[4]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
let frac = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let result = (redemption / pr - 1.0) / frac
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn yieldmat_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 5 && values.length() != 6 {
return Error(formula_error_value)
}
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let issue = match value_as_date_serial(values[2], use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
if issue >= settlement {
return Error(formula_error_num)
}
let rate = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return err
}
if rate < 0.0 {
return Error(formula_error_num)
}
let pr = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return err
}
if pr <= 0.0 {
return Error(formula_error_num)
}
let basis = if values.length() == 6 {
match value_as_number(values[5]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return err
}
} else {
0
}
let dim = match yearfrac_number(issue, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dis = match yearfrac_number(issue, settlement, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let dsm = match
yearfrac_number(settlement, maturity, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let numerator = 1.0 + dim * rate
let denominator = pr / 100.0 + dis * rate
let result = (numerator / denominator - 1.0) / dsm
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn aggr_between(
start_period : Double,
end_period : Double,
initial_value : Array[Double],
f : (Array[Double], Double) -> Array[Double],
) -> Array[Double] {
let mut value = initial_value
let start_int = Double::to_int(trunc_double(start_period))
let end_int = Double::to_int(trunc_double(end_period))
if start_int <= end_int {
let mut idx = start_int
while idx <= end_int {
value = f(value, Double::from_int(idx))
idx = idx + 1
}
} else {
let mut idx = start_int
while idx >= end_int {
value = f(value, Double::from_int(idx))
if idx == end_int {
break
}
idx = idx - 1
}
}
value
}
///|
fn change_month_serial(
serial : Double,
num_months : Double,
return_last_month : Bool,
use_1904_dates? : Bool = false,
) -> Double {
let (year, month, day) = match
date_parts_from_serial(serial, use_1904_dates~) {
Some(parts) => parts
None => return serial
}
let mut offset_day = 0
if return_last_month && day == days_in_month(year, month) {
offset_day = -1
}
let (new_year, new_month, new_day) = normalize_date_parts(
year,
month + Double::to_int(trunc_double(num_months)),
day + offset_day,
)
if return_last_month {
let last_day = days_in_month(new_year, new_month)
return match
excel_serial_from_date(new_year, new_month, last_day, use_1904_dates~) {
Some(serial_value) => serial_value
None => serial
}
}
match excel_serial_from_date(new_year, new_month, new_day, use_1904_dates~) {
Some(serial_value) => serial_value
None => serial
}
}
///|
fn dates_aggregate(
start_date : Double,
end_date : Double,
num_months : Double,
f : (Double, Double) -> Double,
acc : Double,
return_last_month : Bool,
use_1904_dates? : Bool = false,
) -> (Double, Double, Double) {
let mut front_date = start_date
let mut trailing_date = end_date
let mut stop = if num_months > 0.0 {
front_date >= end_date
} else {
end_date >= front_date
}
let mut result = acc
while !stop {
trailing_date = front_date
front_date = change_month_serial(
front_date,
num_months,
return_last_month,
use_1904_dates~,
)
result = result + f(front_date, trailing_date)
stop = if num_months > 0.0 {
front_date >= end_date
} else {
end_date >= front_date
}
}
(front_date, trailing_date, result)
}
///|
fn coup_number(
maturity : Double,
settlement : Double,
num_months : Double,
use_1904_dates? : Bool = false,
) -> Double {
let (mat_year, mat_month, mat_day) = match
date_parts_from_serial(maturity, use_1904_dates~) {
Some(parts) => parts
None => return 0.0
}
let (settle_year, settle_month, settle_day) = match
date_parts_from_serial(settlement, use_1904_dates~) {
Some(parts) => parts
None => return 0.0
}
let end_of_month_temp = days_in_month(mat_year, mat_month) == mat_day
let mut end_of_month = end_of_month_temp
if !end_of_month_temp &&
mat_month != 2 &&
mat_day > 28 &&
mat_day < days_in_month(mat_year, mat_month) {
end_of_month = days_in_month(settle_year, settle_month) == settle_day
}
let start_date = change_month_serial(
settlement,
0.0,
end_of_month,
use_1904_dates~,
)
let mut coupons = 0.0
if start_date > settlement {
coupons = coupons + 1.0
}
let date = change_month_serial(
start_date,
num_months,
end_of_month,
use_1904_dates~,
)
let f = fn(_pcd : Double, _ncd : Double) -> Double { 1.0 }
let (_, _, result) = dates_aggregate(
date,
maturity,
num_months,
f,
coupons,
end_of_month,
use_1904_dates~,
)
result
}
///|
fn prepare_odd_yld_or_pr_arg(
name : String,
value : FormulaValue,
) -> Result[Double, FormulaValue] {
let yld_or_pr = match value_as_number(value) {
Ok(num) => num
Err(err) => return Err(err)
}
if (name == "ODDFPRICE" || name == "ODDLPRICE") && yld_or_pr < 0.0 {
return Err(Error(formula_error_num))
}
if (name == "ODDFYIELD" || name == "ODDLYIELD") && yld_or_pr <= 0.0 {
return Err(Error(formula_error_num))
}
Ok(yld_or_pr)
}
///|
fn prepare_oddf_args(
name : String,
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> Result[
(Double, Double, Double, Double, Double, Double, Double, Double, Int),
FormulaValue,
] {
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let issue = match value_as_date_serial(values[2], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let first_coupon = match value_as_date_serial(values[3], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
if issue >= settlement {
return Err(Error(formula_error_num))
}
if settlement >= first_coupon {
return Err(Error(formula_error_num))
}
if first_coupon >= maturity {
return Err(Error(formula_error_num))
}
let rate = match value_as_number(values[4]) {
Ok(num) => num
Err(err) => return Err(err)
}
if rate < 0.0 {
return Err(Error(formula_error_num))
}
let yld_or_pr = match prepare_odd_yld_or_pr_arg(name, values[5]) {
Ok(num) => num
Err(err) => return Err(err)
}
let redemption = match value_as_number(values[6]) {
Ok(num) => num
Err(err) => return Err(err)
}
if redemption <= 0.0 {
return Err(Error(formula_error_num))
}
let frequency = match value_as_number(values[7]) {
Ok(num) => num
Err(err) => return Err(err)
}
if !validate_frequency(frequency) {
return Err(Error(formula_error_num))
}
let basis = if values.length() == 9 {
match value_as_number(values[8]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return Err(err)
}
} else {
0
}
Ok(
(
settlement, maturity, issue, first_coupon, rate, yld_or_pr, redemption, frequency,
basis,
),
)
}
///|
fn oddfprice_calc(
settlement : Double,
maturity : Double,
issue : Double,
first_coupon : Double,
rate : Double,
yld : Double,
redemption : Double,
frequency : Double,
basis : Int,
use_1904_dates? : Bool = false,
) -> Result[Double, FormulaValue] {
if basis < 0 || basis > 4 {
return Err(Error(formula_error_num))
}
let (mat_year, mat_month, mat_day) = match
date_parts_from_serial(maturity, use_1904_dates~) {
Some(parts) => parts
None => return Err(Error(formula_error_value))
}
let return_last_month = days_in_month(mat_year, mat_month) == mat_day
let num_months = 12.0 / frequency
let num_months_neg = -num_months
let mat = change_month_serial(
maturity,
num_months_neg,
return_last_month,
use_1904_dates~,
)
let f = fn(_d1 : Double, _d2 : Double) -> Double { 0.0 }
let (pcd, _, _) = dates_aggregate(
mat,
first_coupon,
num_months_neg,
f,
0.0,
return_last_month,
use_1904_dates~,
)
if pcd != first_coupon {
return Err(Error(formula_error_num))
}
let e = match
coupdays_value(settlement, maturity, frequency, basis, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let n = match
coupnum_value(settlement, maturity, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let dfc = coupdays_between(issue, first_coupon, basis, use_1904_dates~)
if dfc < e {
let dsc = coupdays_between(settlement, first_coupon, basis, use_1904_dates~)
let a = coupdays_between(issue, settlement, basis, use_1904_dates~)
let x = yld / frequency + 1.0
let y = dsc / e
let p3 = @math.pow(x, n - 1.0 + y)
let term1 = redemption / p3
let term2 = 100.0 * rate / frequency * dfc / e / @math.pow(x, y)
let term3 = aggr_between(2.0, Double::floor(n), [0.0], fn(
acc : Array[Double],
index : Double,
) -> Array[Double] {
[acc[0] + 100.0 * rate / frequency / @math.pow(x, index - 1.0 + y)]
})
let term4 = a / e * (rate / frequency) * 100.0
return Ok(term1 + term2 + term3[0] - term4)
}
let nc = match
coupnum_value(issue, first_coupon, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let mut last_coupon = first_coupon
let ag = aggr_between(Double::floor(nc), 1.0, [0.0, 0.0], fn(
acc : Array[Double],
index : Double,
) -> Array[Double] {
let (last_year, last_month, last_day) = match
date_parts_from_serial(last_coupon, use_1904_dates~) {
Some(parts) => parts
None => return acc
}
let (early_year, early_month, early_day) = normalize_date_parts(
last_year,
last_month + Double::to_int(trunc_double(num_months_neg)),
last_day,
)
let early_coupon = match
excel_serial_from_date(
early_year,
early_month,
early_day,
use_1904_dates~,
) {
Some(serial) => serial
None => return acc
}
let mut nl = e
if basis == 1 {
nl = coupdays_between(early_coupon, last_coupon, basis, use_1904_dates~)
}
let mut dci = coupdays_between(issue, last_coupon, basis, use_1904_dates~)
if index > 1.0 {
dci = nl
}
let start_date = if issue > early_coupon { issue } else { early_coupon }
let end_date = if settlement < last_coupon {
settlement
} else {
last_coupon
}
let a = coupdays_between(start_date, end_date, basis, use_1904_dates~)
last_coupon = early_coupon
let dcnl = acc[0] + dci / nl
let anl = acc[1] + a / nl
[dcnl, anl]
})
let dcnl = ag[0]
let anl = ag[1]
let dsc = if basis == 2 || basis == 3 {
let ncd = match
coupon_date_serial(
"COUPNCD",
settlement,
first_coupon,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return Err(err)
}
coupdays_between(settlement, ncd, basis, use_1904_dates~)
} else {
let pcd = match
coupon_date_serial(
"COUPPCD",
settlement,
first_coupon,
frequency,
use_1904_dates~,
) {
Ok(serial) => serial
Err(err) => return Err(err)
}
let a = coupdays_between(pcd, settlement, basis, use_1904_dates~)
e - a
}
let nq = coup_number(first_coupon, settlement, num_months, use_1904_dates~)
let n_final = match
coupnum_value(first_coupon, maturity, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let x = yld / frequency + 1.0
let y = dsc / e
let p3 = @math.pow(x, y + nq + n_final)
let term1 = redemption / p3
let term2 = 100.0 * rate / frequency * dcnl / @math.pow(x, nq + y)
let term3 = aggr_between(1.0, Double::floor(n_final), [0.0], fn(
acc : Array[Double],
index : Double,
) -> Array[Double] {
[acc[0] + 100.0 * rate / frequency / @math.pow(x, index + nq + y)]
})
let term4 = 100.0 * rate / frequency * anl
Ok(term1 + term2 + term3[0] - term4)
}
///|
fn oddfprice_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 8 && values.length() != 9 {
return Error(formula_error_value)
}
let (
settlement,
maturity,
issue,
first_coupon,
rate,
yld,
redemption,
frequency,
basis,
) = match prepare_oddf_args("ODDFPRICE", values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
let result = match
oddfprice_calc(
settlement,
maturity,
issue,
first_coupon,
rate,
yld,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(num) => num
Err(err) => return err
}
number_or_num_error(round_significant_digits(result, 15))
}
///|
fn get_oddfprice(
f : (Double) -> Double,
x : Double,
cnt : Double,
prec : Double,
) -> Double {
let max_cnt = 20.0
let d = (f(x + prec) - f(x - prec)) / (2.0 * prec)
let new_x = x - f(x) / d
if Double::abs(new_x - x) < prec {
new_x
} else if cnt > max_cnt {
new_x
} else {
get_oddfprice(f, new_x, cnt + 1.0, prec)
}
}
///|
fn oddfyield_values(
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 8 && values.length() != 9 {
return Error(formula_error_value)
}
let (
settlement,
maturity,
issue,
first_coupon,
rate,
pr,
redemption,
frequency,
basis,
) = match prepare_oddf_args("ODDFYIELD", values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
if basis < 0 || basis > 4 {
return Error(formula_error_num)
}
let years = coupdays_between(settlement, maturity, basis, use_1904_dates~)
let px = pr - 100.0
let num = rate * years * 100.0 - px
let denum = px / 4.0 + years * px / 2.0 + years * 100.0
let guess = num / denum
let f = fn(yld : Double) -> Double {
match
oddfprice_calc(
settlement,
maturity,
issue,
first_coupon,
rate,
yld,
redemption,
frequency,
basis,
use_1904_dates~,
) {
Ok(price) => pr - price
Err(_) => 0.0 / 0.0
}
}
let result = get_oddfprice(f, guess, 0.0, 1.0e-7)
if Double::is_nan(result) || Double::is_inf(result) {
Error(formula_error_num)
} else {
number_or_num_error(round_significant_digits(result, 15))
}
}
///|
fn prepare_oddl_args(
name : String,
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> Result[
(Double, Double, Double, Double, Double, Double, Double, Int),
FormulaValue,
] {
let settlement = match value_as_date_serial(values[0], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let maturity = match value_as_date_serial(values[1], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
let last_interest = match value_as_date_serial(values[2], use_1904_dates~) {
Ok(num) => num
Err(err) => return Err(err)
}
if last_interest >= settlement {
return Err(Error(formula_error_num))
}
if settlement >= maturity {
return Err(Error(formula_error_num))
}
let rate = match value_as_number(values[3]) {
Ok(num) => num
Err(err) => return Err(err)
}
if rate < 0.0 {
return Err(Error(formula_error_num))
}
let yld_or_pr = match prepare_odd_yld_or_pr_arg(name, values[4]) {
Ok(num) => num
Err(err) => return Err(err)
}
let redemption = match value_as_number(values[5]) {
Ok(num) => num
Err(err) => return Err(err)
}
if redemption <= 0.0 {
return Err(Error(formula_error_num))
}
let frequency = match value_as_number(values[6]) {
Ok(num) => num
Err(err) => return Err(err)
}
if !validate_frequency(frequency) {
return Err(Error(formula_error_num))
}
let basis = if values.length() == 8 {
match value_as_number(values[7]) {
Ok(num) => Double::to_int(trunc_double(num))
Err(err) => return Err(err)
}
} else {
0
}
Ok(
(
settlement, maturity, last_interest, rate, yld_or_pr, redemption, frequency,
basis,
),
)
}
///|
fn oddl_values(
name : String,
values : ArrayView[FormulaValue],
use_1904_dates? : Bool = false,
) -> FormulaValue {
if values.length() != 7 && values.length() != 8 {
return Error(formula_error_value)
}
let (
settlement,
maturity,
last_interest,
rate,
pr_or_yld,
redemption,
frequency,
basis,
) = match prepare_oddl_args(name, values, use_1904_dates~) {
Ok(args) => args
Err(err) => return err
}
if basis < 0 || basis > 4 {
return Error(formula_error_num)
}
let num_months = 12.0 / frequency
let nc = match
coupnum_value(last_interest, maturity, frequency, use_1904_dates~) {
Ok(num) => num
Err(err) => return err
}
let mut early_coupon = last_interest
let ag = aggr_between(1.0, Double::floor(nc), [0.0, 0.0, 0.0], fn(
acc : Array[Double],
index : Double,
) -> Array[Double] {
let late_coupon = change_month_serial(
early_coupon,
num_months,
false,
use_1904_dates~,
)
let nl = coupdays_between(early_coupon, late_coupon, basis, use_1904_dates~)
let mut dci = coupdays_between(
early_coupon,
maturity,
basis,
use_1904_dates~,
)
if index < nc {
dci = nl
}
let mut a = 0.0
if late_coupon < settlement {
a = dci
} else if early_coupon < settlement {
a = coupdays_between(early_coupon, settlement, basis, use_1904_dates~)
}
let start_date = if settlement > early_coupon {
settlement
} else {
early_coupon
}
let end_date = if maturity < late_coupon { maturity } else { late_coupon }
let dsc = coupdays_between(start_date, end_date, basis, use_1904_dates~)
early_coupon = late_coupon
let dcnl = acc[0] + dci / nl
let anl = acc[1] + a / nl
let dscnl = acc[2] + dsc / nl
[dcnl, anl, dscnl]
})
let dcnl = ag[0]
let anl = ag[1]
let dscnl = ag[2]
let x = 100.0 * rate / frequency
let term1 = dcnl * x + redemption
if name == "ODDLPRICE" {
let term2 = dscnl * pr_or_yld / frequency + 1.0
let term3 = anl * x
let result = term1 / term2 - term3
return number_or_num_error(round_significant_digits(result, 15))
}
let term2 = anl * x + pr_or_yld
let term3 = frequency / dscnl
let result = (term1 - term2) / term2 * term3
number_or_num_error(round_significant_digits(result, 15))
}
///|