这也可能被认为是一个数学问题,但我想专门使用 Rust 来演示。
例如,0.02 是二进制的
1.010001111010111000010100011110101110...
。我曾开发过其他财务软件,并且通常使用整数和基点(例如 200 而不是 0.02)以避免计算机在处理小数值时出现问题。
我已经编写了以下代码,并且两个测试都通过了:
use std::f64::consts::E;
pub fn get_compounded_value_using_ints(
initial_amount: u64,
elapsed_periods: u64,
interest_rate_basis_points: u64,
) -> u64 {
let multiplier =
E.powf(interest_rate_basis_points as f64 * elapsed_periods as f64 / 10_000 as f64);
(initial_amount as f64 * multiplier) as u64
}
pub fn get_compounded_value_using_floats(
initial_amount: u64,
elapsed_periods: u64,
interest_rate: f64,
) -> u64 {
let multiplier = E.powf(interest_rate * elapsed_periods as f64);
(initial_amount as f64 * multiplier) as u64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_compounded_value_using_ints() {
// Start with 100_000, 12 periods, 2% interest
assert_eq!(get_compounded_value_using_ints(100_000, 12, 200), 127_124);
}
#[test]
fn test_get_compounded_value_using_floats() {
// Start with 100_000, 12 periods, 2% interest
assert_eq!(
get_compounded_value_using_floats(100_000, 12, 0.02),
127_124
);
}
}
由于两项测试都通过了,这使得人们似乎可以愉快地使用浮点数来表示百分比值。我想知道测试是否有问题,并且在某些时候浮动版本将开始不精确。
使用浮点数与整数作为百分比值会失去小数精度吗?
花车对于金钱来说有几个弱点:
rust_decimal::Decimal
。
这是一个展示差异的类似测试。通过在许多离散时间进行复合,浮点数很难产生准确的答案。我评论了一些发生浮动相关问题的地方。
use rust_decimal::{Decimal, RoundingStrategy};
pub fn get_compounded_value_using_decimals(
initial_amount: Decimal,
elapsed_periods: u64,
interest_rate: Decimal,
) -> Decimal {
let mut amount = initial_amount;
for _ in 0..elapsed_periods {
amount = amount
.checked_add(
amount
.checked_mul(interest_rate)
.unwrap()
.round_dp_with_strategy(2, RoundingStrategy::MidpointNearestEven),
)
.unwrap();
}
amount
}
pub fn get_compounded_value_using_floats(
initial_amount: f64,
elapsed_periods: u64,
interest_rate: f64,
) -> f64 {
let mut amount = initial_amount;
// CON: Floats build up rounding error with each iteration.
for _ in 0..elapsed_periods {
// CON: Rounding is trickier.
amount += (amount * interest_rate * 100.0).round_ties_even() * 0.01;
}
amount
}
fn compare(initial: u64, periods: u64, percent: u64) {
let with_decimals = get_compounded_value_using_decimals(
Decimal::from(initial),
periods,
// CON: 0.02 isn't precisely representable by `f64`, instead becoming 0.0199999995529651641845703125.
Decimal::from(percent) / Decimal::from(100),
);
let with_floats =
get_compounded_value_using_floats(initial as f64, periods, percent as f64 / 100.0);
println!("${initial}, {periods} periods, {percent}%");
println!("decimals ${with_decimals}");
// CON: added cent-precision numbers and got rounding errors, need to truncate.
println!("floats ${with_floats:.2}");
println!();
}
pub fn main() {
// $0.01 difference
// decimals $121899.43
// floats $121899.44
compare(100_000, 10, 2);
// higher initial -> $0.01 difference
// decimals $121899441999475.71
// floats $121899441999475.72
compare(100_000_000_000_000, 10, 2);
// more periods -> $28 difference
// decimals $19502.56
// floats $19530.56
compare(1, 1000, 1);
}