Skip to content

Commit c3802db

Browse files
authored
Fixes precision issue with to_f64 with some numbers without fractions (paupino#625)
* Fixes precision issue with to_f64 with some numbers without fractions * Sign should not be applied to early for to_f64
1 parent 1c80137 commit c3802db

File tree

3 files changed

+44
-5
lines changed

3 files changed

+44
-5
lines changed

src/decimal.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2354,7 +2354,7 @@ impl ToPrimitive for Decimal {
23542354
let integer = self.to_i128();
23552355
integer.map(|i| i as f64)
23562356
} else {
2357-
let sign: f64 = if self.is_sign_negative() { -1.0 } else { 1.0 };
2357+
let neg = self.is_sign_negative();
23582358
let mut mantissa: u128 = self.lo.into();
23592359
mantissa |= (self.mid as u128) << 32;
23602360
mantissa |= (self.hi as u128) << 64;
@@ -2364,9 +2364,24 @@ impl ToPrimitive for Decimal {
23642364
let integral_part = mantissa / precision;
23652365
let frac_part = mantissa % precision;
23662366
let frac_f64 = (frac_part as f64) / (precision as f64);
2367-
let value = sign * ((integral_part as f64) + frac_f64);
2367+
let integral = integral_part as f64;
2368+
// If there is a fractional component then we will need to add that and remove any
2369+
// inaccuracies that creep in during addition. Otherwise, if the fractional component
2370+
// is zero we can exit early.
2371+
if frac_f64.is_zero() {
2372+
if neg {
2373+
return Some(-integral);
2374+
}
2375+
return Some(integral);
2376+
}
2377+
let value = integral + frac_f64;
23682378
let round_to = 10f64.powi(self.scale() as i32);
2369-
Some((value * round_to).round() / round_to)
2379+
let rounded = (value * round_to).round() / round_to;
2380+
if neg {
2381+
Some(-rounded)
2382+
} else {
2383+
Some(rounded)
2384+
}
23702385
}
23712386
}
23722387
}

src/serde.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ mod test {
658658

659659
#[test]
660660
#[cfg(all(feature = "serde-str", not(feature = "serde-float")))]
661-
fn bincode_serialization() {
661+
fn bincode_serialization_not_float() {
662662
use bincode::{deserialize, serialize};
663663

664664
let data = [
@@ -682,7 +682,7 @@ mod test {
682682

683683
#[test]
684684
#[cfg(all(feature = "serde-str", feature = "serde-float"))]
685-
fn bincode_serialization() {
685+
fn bincode_serialization_serde_float() {
686686
use bincode::{deserialize, serialize};
687687

688688
let data = [

tests/decimal_tests.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,6 +2842,12 @@ fn it_converts_to_f64() {
28422842
("2.2238", Some(2.2238_f64)),
28432843
("2.2238123", Some(2.2238123_f64)),
28442844
("22238", Some(22238_f64)),
2845+
("1000000", Some(1000000_f64)),
2846+
("1000000.000000000000000000", Some(1000000_f64)),
2847+
("10000", Some(10000_f64)),
2848+
("10000.000000000000000000", Some(10000_f64)),
2849+
("100000", Some(100000_f64)),
2850+
("100000.000000000000000000", Some(100000_f64)),
28452851
];
28462852
for &(value, expected) in tests {
28472853
let value = Decimal::from_str(value).unwrap().to_f64();
@@ -4747,6 +4753,24 @@ mod issues {
47474753
assert_eq!("-429391.87200000000002327170816", c.unwrap().to_string())
47484754
}
47494755

4756+
#[test]
4757+
fn issue_624_to_f64_precision() {
4758+
let tests = [
4759+
("1000000.000000000000000000", 1000000.0f64),
4760+
("10000.000000000000000000", 10000.0f64),
4761+
("100000.000000000000000000", 100000.0f64), // Problematic value
4762+
];
4763+
for (index, (test, expected)) in tests.iter().enumerate() {
4764+
let decimal = Decimal::from_str_exact(test).unwrap();
4765+
assert_eq!(
4766+
f64::try_from(decimal).unwrap(),
4767+
*expected,
4768+
"Test index {} failed",
4769+
index
4770+
);
4771+
}
4772+
}
4773+
47504774
#[test]
47514775
#[cfg(not(feature = "legacy-ops"))] // I will deprecate this feature/behavior in an upcoming release
47524776
fn issue_618_rescaling_overflow() {

0 commit comments

Comments
 (0)