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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy