`to_f64` panics when scale is large
wangxiaoying opened this issue · 3 comments
I have a decimal value 1e-130
and want to convert it into float using to_f64
. But it result in panicked at 'attempt to divide by zero'
at here. The reason is that since scale
is 130, precision
becomes 0
in this case.
I saw there is a comment saying that "scale is at most 28". What should we do if it is larger like 130? Which I think is within the valid range of float64 (please let me know if I understand incorrectly). Thanks!
Thank you for opening an issue! Do you have an isolated example that you provide to demonstrate this?
As a contrived example, this succeeds:
let value = Decimal::from_parts(1, 0, 0, false, 130);
assert_eq!(14, value.scale());
assert_eq!(Some(1e-14), value.to_f64());
Because we don't allow a scale above 28 - we effectively try to clamp it or error out. Most access calls error out however from_parts
causes undefined behavior.
I'd be curious to what the state of the decimal object is before calling to_f64
. My hunch is that the scale is out of bounds which is causing a non-panic in debug mode and wrapping.
Are you able to send the output of unpack
before calling to_f64
?
Hi @paupino , thanks for the reply!
Here is the result of unpack:
scale: 130 // val.scale()
precision: 0 // 10_u128.pow(val.scale())
unpack: UnpackedDecimal { negative: false, scale: 130, hi: 0, mid: 0, lo: 0 } // val.unpack()
Some more context
The decimal value I got is from postgres database with the db-postgres
feature enabled. Here is a minimal example:
postgres setup:
CREATE TABLE test_table (key int, value numeric);
INSERT INTO test_table VALUES (0, 1.3), (1, 0.004), (2, 1e-20), (3, 1e-130), (4, 1e-200);
dependencies
postgres = {version = "0.19"}
rust_decimal = {version = "1", features = ["db-postgres"]}
test code
use postgres::{Client, NoTls};
use rust_decimal::prelude::*;
fn main() {
let mut client = Client::connect(
"host=localhost user=postgres password=postgres port=5432 dbname=tpch",
NoTls,
)
.unwrap();
let query = "select value from test_table2 where key = 3";
for row in client.query(query, &[]).unwrap() {
let val: Decimal = row.try_get(0).unwrap();
let fval: f64 = val.to_f64().unwrap();
println!("get float {:?}", fval);
}
}
An interesting thing is that although the procedure is the same, the error of this example is attempt to multiply with overflow
instead of attempt to divide by zero
in my original project.
This is great thank you. I've issued a fix in the linked PR, however to note this will cause the behavior to return a floating point number of 0 for key 3
. This is because we are losing precision by converting to Rust Decimal, which has a maximum precision of 28 at this point in time. Because 1e-130
is such a small number we effectively round this to zero when representing in rust decimal. If you need to retain this precision then unfortunately, you may need to look at another library at this point in time (e.g. bigdecimal).
Version 2 of this library may allow larger scales, however that is a limitation of this library at present (i.e. it helps us provide some fast optimizations across 3 words).