bigdecimal/
impl_num.rs

1//! Code for num_traits
2
3use num_traits::{Zero, Num, Signed, FromPrimitive, ToPrimitive, AsPrimitive};
4use num_bigint::{BigInt, Sign, ToBigInt};
5
6#[cfg(not(feature = "std"))]
7use num_traits::float::FloatCore;
8
9use crate::stdlib;
10use stdlib::str::FromStr;
11use stdlib::string::{String, ToString};
12use stdlib::convert::TryFrom;
13use stdlib::ops::Neg;
14use stdlib::cmp::Ordering;
15
16use crate::BigDecimal;
17use crate::BigDecimalRef;
18use crate::ParseBigDecimalError;
19
20#[cfg(not(feature = "std"))]
21// f64::powi is only available in std, no_std must use libm
22fn powi(x: f64, n: i32) -> f64 {
23    libm::pow(x, n as f64)
24}
25
26#[cfg(feature = "std")]
27fn powi(x: f64, n: i32) -> f64 {
28    x.powi(n)
29}
30
31impl Num for BigDecimal {
32    type FromStrRadixErr = ParseBigDecimalError;
33
34    /// Creates and initializes a BigDecimal.
35    #[inline]
36    fn from_str_radix(s: &str, radix: u32) -> Result<BigDecimal, ParseBigDecimalError> {
37        if radix != 10 {
38            return Err(ParseBigDecimalError::Other(String::from(
39                "The radix for decimal MUST be 10",
40            )));
41        }
42
43        let exp_separator: &[_] = &['e', 'E'];
44
45        // split slice into base and exponent parts
46        let (base_part, exponent_value) = match s.find(exp_separator) {
47            // exponent defaults to 0 if (e|E) not found
48            None => (s, 0),
49
50            // split and parse exponent field
51            Some(loc) => {
52                // slice up to `loc` and 1 after to skip the 'e' char
53                let (base, e_exp) = s.split_at(loc);
54                (base, i128::from_str(&e_exp[1..])?)
55            }
56        };
57
58        // TEMPORARY: Test for emptiness - remove once BigInt supports similar error
59        if base_part.is_empty() {
60            return Err(ParseBigDecimalError::Empty);
61        }
62
63        let mut digit_buffer = String::new();
64
65        let last_digit_loc = base_part.len() - 1;
66
67        // split decimal into a digit string and decimal-point offset
68        let (digits, decimal_offset) = match base_part.find('.') {
69            // No dot! pass directly to BigInt
70            None => (base_part, 0),
71            // dot at last digit, pass all preceding digits to BigInt
72            Some(loc) if loc == last_digit_loc => {
73                (&base_part[..last_digit_loc], 0)
74            }
75            // decimal point found - necessary copy into new string buffer
76            Some(loc) => {
77                // split into leading and trailing digits
78                let (lead, trail) = (&base_part[..loc], &base_part[loc + 1..]);
79
80                digit_buffer.reserve(lead.len() + trail.len());
81                // copy all leading characters into 'digits' string
82                digit_buffer.push_str(lead);
83                // copy all trailing characters after '.' into the digits string
84                digit_buffer.push_str(trail);
85
86                // count number of trailing digits
87                let trail_digits = trail.chars().filter(|c| *c != '_').count();
88
89                (digit_buffer.as_str(), trail_digits as i128)
90            }
91        };
92
93        // Calculate scale by subtracing the parsed exponential
94        // value from the number of decimal digits.
95        // Return error if anything overflows outside i64 boundary.
96        let scale = decimal_offset
97                    .checked_sub(exponent_value)
98                    .and_then(|scale| scale.to_i64())
99                    .ok_or_else(||
100                        ParseBigDecimalError::Other(
101                            format!("Exponent overflow when parsing '{}'", s))
102                    )?;
103
104        let big_int = BigInt::from_str_radix(digits, radix)?;
105
106        Ok(BigDecimal::new(big_int, scale))
107    }
108}
109
110
111impl ToPrimitive for BigDecimal {
112    fn to_i64(&self) -> Option<i64> {
113        self.to_ref().to_i64()
114    }
115    fn to_i128(&self) -> Option<i128> {
116        self.to_ref().to_i128()
117    }
118    fn to_u64(&self) -> Option<u64> {
119        self.to_ref().to_u64()
120    }
121    fn to_u128(&self) -> Option<u128> {
122        self.to_ref().to_u128()
123    }
124    fn to_f64(&self) -> Option<f64> {
125        self.to_ref().to_f64()
126    }
127}
128
129impl ToPrimitive for BigDecimalRef<'_> {
130    fn to_i64(&self) -> Option<i64> {
131        match self.sign() {
132            Sign::Plus if self.scale == 0 => self.digits.to_i64(),
133            Sign::Minus if self.scale == 0 => {
134                self.digits.to_u64().and_then(
135                    |d| match d.cmp(&(i64::MAX as u64 + 1)) {
136                        Ordering::Less => Some((d as i64).neg()),
137                        Ordering::Equal => Some(i64::MIN),
138                        Ordering::Greater => None,
139                    }
140                )
141            }
142            Sign::Plus | Sign::Minus => self.to_owned_with_scale(0).int_val.to_i64(),
143            Sign::NoSign => Some(0),
144        }
145    }
146    fn to_i128(&self) -> Option<i128> {
147        match self.sign() {
148            Sign::Plus if self.scale == 0 => self.digits.to_i128(),
149            Sign::Minus if self.scale == 0 => {
150                self.digits.to_u128().and_then(
151                    |d| match d.cmp(&(i128::MAX as u128 + 1)) {
152                        Ordering::Less => Some((d as i128).neg()),
153                        Ordering::Equal => Some(i128::MIN),
154                        Ordering::Greater => None,
155                    }
156                )
157            }
158            Sign::Plus | Sign::Minus => self.to_owned_with_scale(0).int_val.to_i128(),
159            Sign::NoSign => Some(0),
160        }
161    }
162    fn to_u64(&self) -> Option<u64> {
163        match self.sign() {
164            Sign::Plus if self.scale == 0 => self.digits.to_u64(),
165            Sign::Plus => self.to_owned_with_scale(0).int_val.to_u64(),
166            Sign::NoSign => Some(0),
167            Sign::Minus => None,
168        }
169    }
170    fn to_u128(&self) -> Option<u128> {
171        match self.sign() {
172            Sign::Plus if self.scale == 0 => self.digits.to_u128(),
173            Sign::Plus => self.to_owned_with_scale(0).int_val.to_u128(),
174            Sign::NoSign => Some(0),
175            Sign::Minus => None,
176        }
177    }
178
179    fn to_f64(&self) -> Option<f64> {
180        let copy_sign_to_float = |f: f64| if self.sign == Sign::Minus { f.neg() } else { f };
181
182        if self.digits.is_zero() {
183            return Some(0.0);
184        }
185        if self.scale == 0 {
186            return self.digits.to_f64().map(copy_sign_to_float);
187        }
188
189        // borrow bugint value
190        let (mut int_cow, mut scale) = self.to_cow_biguint_and_scale();
191
192        // approximate number of base-10 digits
193        let digit_count = ((int_cow.bits() + 1) as f64 * stdlib::f64::consts::LOG10_2).floor() as u64;
194
195        // trim trailing digits, 19 at a time, leaving about 25
196        // which should be more than accurate enough for direct
197        // conversion to f64
198        const N: u64 = 25;
199        let digits_to_remove = digit_count.saturating_sub(N);
200        let ten_to_19 = 10u64.pow(19);
201        let iter_count = digits_to_remove / 19;
202        for _ in 0..iter_count {
203            *int_cow.to_mut() /= ten_to_19;
204            scale -= 19;
205        }
206
207        match scale.to_i32().and_then(|x| x.checked_neg()) {
208            Some(pow) if 0 <= pow => {
209                // 'simple' integer case
210                let f = int_cow.to_f64().map(copy_sign_to_float)?;
211                (f * powi(10.0, pow)).into()
212            }
213            Some(exp) => {
214                // format decimal as floating point and let the default parser generate the f64
215                #[cfg(not(feature = "std"))]
216                {
217                    let s = format!("{}e{}", int_cow, exp);
218                    s.parse().map(copy_sign_to_float).ok()
219                }
220
221                #[cfg(feature = "std")]
222                {
223                    use std::io::Write;
224
225                    // save allocation of a String by using local buffer of bytes
226                    // since we know the size will be small
227                    //
228                    //   ~ 1 '-' + (N+19) digits + 1 'e' + 11 i32 digits = 32 + N
229                    // (plus a little extra for safety)
230                    let mut buf = [0u8; 50 + N as usize];
231                    write!(&mut buf[..], "{}e{}", int_cow, exp).ok()?;
232                    let i = buf.iter().position(|&c| c == 0)?;
233                    let s = stdlib::str::from_utf8(&buf[..i]).ok()?;
234                    s.parse().map(copy_sign_to_float).ok()
235                }
236            }
237            None => {
238                // exponenent too big for i32: return appropriate infinity
239                let result = if self.sign != Sign::Minus {
240                    f64::INFINITY
241                } else {
242                    f64::NEG_INFINITY
243                };
244                result.into()
245            }
246        }
247    }
248}
249
250
251impl FromPrimitive for BigDecimal {
252    #[inline]
253    fn from_i64(n: i64) -> Option<Self> {
254        Some(BigDecimal::from(n))
255    }
256
257    #[inline]
258    fn from_u64(n: u64) -> Option<Self> {
259        Some(BigDecimal::from(n))
260    }
261
262    #[inline]
263    fn from_i128(n: i128) -> Option<Self> {
264        Some(BigDecimal::from(n))
265    }
266
267    #[inline]
268    fn from_u128(n: u128) -> Option<Self> {
269        Some(BigDecimal::from(n))
270    }
271
272    #[inline]
273    fn from_f32(n: f32) -> Option<Self> {
274        BigDecimal::try_from(n).ok()
275    }
276
277    #[inline]
278    fn from_f64(n: f64) -> Option<Self> {
279        BigDecimal::try_from(n).ok()
280    }
281}
282
283impl ToBigInt for BigDecimal {
284    fn to_bigint(&self) -> Option<BigInt> {
285        Some(self.with_scale(0).int_val)
286    }
287}
288
289
290#[cfg(test)]
291mod test {
292    use super::*;
293
294    mod from_str_radix {
295        use super::*;
296
297        #[test]
298        fn out_of_bounds() {
299            let d = BigDecimal::from_str_radix("1e-9223372036854775808", 10);
300            assert_eq!(d.unwrap_err(), ParseBigDecimalError::Other("Exponent overflow when parsing '1e-9223372036854775808'".to_string()));
301
302        }
303    }
304
305    mod to_f64 {
306        use super::*;
307        use paste::paste;
308        use crate::stdlib;
309
310
311        macro_rules! impl_case {
312            ($name:ident: $f:expr) => {
313                #[test]
314                fn $name() {
315                    let f: f64 = $f;
316                    let s = format!("{}", f);
317                    let n: BigDecimal = s.parse().unwrap();
318                    let result = n.to_f64().unwrap();
319                    assert_eq!(result, f, "src='{}'", s);
320                }
321            };
322            ($name:ident: $src:literal => $expected:expr) => {
323                #[test]
324                fn $name() {
325                    let n: BigDecimal = $src.parse().unwrap();
326                    assert_eq!(n.to_f64().unwrap(), $expected);
327                }
328            };
329        }
330
331        impl_case!(case_zero: 0.0);
332        impl_case!(case_neg_zero: -0.0);
333        impl_case!(case_875en6: 0.000875);
334        impl_case!(case_f64_min: f64::MIN);
335        impl_case!(case_f64_max: f64::MAX);
336        impl_case!(case_f64_min_pos: f64::MIN_POSITIVE);
337        impl_case!(case_pi: stdlib::f64::consts::PI);
338        impl_case!(case_neg_e: -stdlib::f64::consts::E);
339        impl_case!(case_1en500: 1e-500);
340        impl_case!(case_3en310: 3e-310);
341        impl_case!(case_0d001: 0.001);
342
343        impl_case!(case_pos2_224en320: 2.224e-320);
344        impl_case!(case_neg2_224en320: -2.224e-320);
345
346        impl_case!(case_12d34: "12.34" => 12.34);
347        impl_case!(case_0d14: "0.14" => 0.14);
348        impl_case!(case_3d14: "3.14" => 3.14);
349        impl_case!(case_54e23: "54e23" => 54e23);
350        impl_case!(case_n54e23: "-54e23" => -54e23);
351        impl_case!(case_12en78: "12e-78" => 12e-78);
352        impl_case!(case_n12en78: "-12e-78" => -1.2e-77);
353        impl_case!(case_n1en320: "-1e-320" => -1e-320);
354        impl_case!(case_1d0001en920: "1.0001e-920" => 0.0);
355        impl_case!(case_50000d0000: "50000.0000" => 50000.0);
356
357        impl_case!(case_13100e4: "13100e4" => 131000000.0);
358
359        impl_case!(case_44223e9999: "44223e9999" => f64::INFINITY);
360        impl_case!(case_neg44223e9999: "-44223e9999" => f64::NEG_INFINITY);
361    }
362}
363
364
365#[cfg(all(test, property_tests))]
366mod proptests {
367    use super::*;
368    use paste::paste;
369    use proptest::prelude::*;
370    use proptest::num::f64::{NORMAL as NormalF64, SUBNORMAL as SubnormalF64};
371
372    proptest! {
373        #![proptest_config(ProptestConfig::with_cases(20_000))]
374
375        #[test]
376        fn to_f64_roundtrip(f in NormalF64 | SubnormalF64) {
377            let d = BigDecimal::from_f64(f).unwrap();
378            let v = d.to_f64();
379            prop_assert!(v.is_some());
380            prop_assert_eq!(f, v.unwrap());
381        }
382    }
383}