出于对 Rust 的兴趣,使用浮点数表示百分比而不是整数基点会失去精度吗?

问题描述 投票:0回答:1

这也可能被认为是一个数学问题,但我想专门使用 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
1个回答
0
投票
看起来您的两个测试最终都使用了浮点数,数字很小,并且四舍五入的位置有限。您的测试通过并不奇怪。

花车对于金钱来说有几个弱点:

    舍入误差可能会在多次迭代中累积
  • 二进制而不是十进制
    • 更难四舍五入到小数点后 N 位
    • 有些小数,如 0.02,是无法表示的
在这里查看更多内容:

为什么不使用 Double 或 Float 来表示货币?

幸运的是,浮点数与“整数”是一个错误的选择。对于金钱,您应该使用小数类型,例如

fpdec::Decimal

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); }
    
© www.soinside.com 2019 - 2024. All rights reserved.