paupino / rust-decimal

Decimal number implementation written in pure Rust suitable for financial and fixed-precision calculations.
https://docs.rs/rust_decimal/
MIT License
985 stars 179 forks source link

Confusing f64 → Decimal #548

Open loyd opened 2 years ago

loyd commented 2 years ago

Example:

let f = 1652185258.8058286;
let a: Decimal = f.to_string().parse().unwrap();
let b = Decimal::try_from(f).unwrap();
let c = Decimal::from_f64_retain(f).unwrap();
println!("try_from(f64 -> str): {}", a);
println!("try_from(f64):        {}", b);
println!("from_f64_retain(f64): {}", c);
try_from(f64 -> str): 1652185258.8058286
try_from(f64):        1652185258.805829
from_f64_retain(f64): 1652185258.8058285713195800781

Can a direct conversion be improved? Or, at least, it should be noted in the documentation about such problems. Maybe the crate should provide a special feature to have more expensive (but at least without allocation and with ryu) conversion by default.

paupino commented 2 years ago

Interesting. I'll need to look into it some more - the second result (b) is unexpected, however the rest are expected results.

Floats are effectively an approximation and the default parsing route tries to alleviate this by rounding to the known precision boundary (in contrast, the _retain function skips the final rounding). I would expect the parsing, in this case, to round - however it seems like it has rounded to one less decimal place than expected (i.e. since it extracted 1652185258.8058285713195800781 it should knowingly round to 1652185258.8058286).

I'll take a look into it.

paupino commented 2 years ago

I was slightly incorrect when I said it rounded to the known precision boundary - instead, it tries to round to what it deems as the floating point numbers "guaranteed" precision.

Getting this right is easier said than done. In the past, we were a little more relaxed when performing the rounding check however this allowed other issues to creep in: https://github.com/paupino/rust-decimal/pull/229

I'll need to do some research to see if there are alternative algorithms that could potentially help however unfortunately it isn't a trivial task. In lieu of this, I'll likely update the documentation to describe how parsing works as well as known limitations.