bigdecimal/
parsing.rs

1//! Routines for parsing values into BigDecimals
2
3use super::{BigDecimal, ParseBigDecimalError};
4use stdlib::num::FpCategory;
5
6use stdlib::cmp::{self, Ordering};
7
8use num_bigint::{BigInt, BigUint, Sign};
9use num_traits::Zero;
10
11
12/// Try creating bigdecimal from f32
13///
14/// Non "normal" values will return Error case
15///
16pub(crate) fn try_parse_from_f32(n: f32) -> Result<BigDecimal, ParseBigDecimalError> {
17    use stdlib::num::FpCategory::*;
18    match n.classify() {
19        Nan => Err(ParseBigDecimalError::Other("NAN".into())),
20        Infinite => Err(ParseBigDecimalError::Other("Infinite".into())),
21        Subnormal => Ok(parse_from_f32_subnormal(n)),
22        Normal | Zero => Ok(parse_from_f32(n)),
23    }
24}
25
26
27/// Return mantissa, exponent, and sign of given floating point number
28///
29/// ```math
30/// f = frac * 2^pow
31/// ```
32///
33fn split_f32_into_parts(f: f32) -> (u32, i64, Sign) {
34    let bits = f.to_bits();
35    let frac = (bits & ((1 << 23) - 1)) + (1 << 23);
36    let exp = (bits >> 23) & 0xFF;
37
38    let pow = exp as i64 - 127 - 23;
39
40    let sign_bit = bits & (1 << 31);
41    let sign = if sign_bit == 0 {
42        Sign::Plus
43    } else {
44        Sign::Minus
45    };
46
47    (frac, pow, sign)
48}
49
50
51/// Create bigdecimal from f32
52///
53pub(crate) fn parse_from_f32(n: f32) -> BigDecimal {
54    if n.classify() == FpCategory::Subnormal {
55        return parse_from_f32_subnormal(n);
56    }
57    let bits = n.to_bits();
58
59    if (bits << 1) == 0 {
60        return Zero::zero();
61    }
62
63    // n = <sign> frac * 2^pow
64    let (frac, pow, sign) = split_f32_into_parts(n);
65
66    let result;
67    let scale;
68    match pow.cmp(&0) {
69        Ordering::Equal => {
70            result = BigUint::from(frac);
71            scale = 0;
72        }
73        Ordering::Less => {
74            let trailing_zeros = cmp::min(frac.trailing_zeros(), -pow as u32);
75
76            let reduced_frac = frac >> trailing_zeros;
77            let reduced_pow = pow + trailing_zeros as i64;
78            debug_assert!(reduced_pow <= 0);
79
80            let shift = BigUint::from(5u8).pow(-reduced_pow as u32);
81
82            result = reduced_frac * shift;
83            scale = -reduced_pow;
84        }
85        Ordering::Greater => {
86            let shift = BigUint::from(2u8).pow(pow.abs() as u32);
87
88            result = frac * shift;
89            scale = 0;
90        }
91    }
92
93    BigDecimal {
94        int_val: BigInt::from_biguint(sign, result),
95        scale: scale,
96    }
97}
98
99/// Create bigdecimal from subnormal f32
100pub(crate) fn parse_from_f32_subnormal(n: f32) -> BigDecimal {
101    debug_assert_eq!(n.classify(), FpCategory::Subnormal);
102    let bits = n.to_bits();
103
104    let sign_bit = bits >> 31;
105    debug_assert_eq!(bits >> 24, sign_bit << 7);
106
107    let frac = bits - (sign_bit << 31);
108
109    // 5^149 = 5^126 + 5^23  (f32-bit-bias=126, fraction-bits=23)
110    let five_to_149 = BigUint::from_slice(&[
111        1466336501, 2126633373, 2856417274, 1232167559, 2512314040, 1644054862,
112        3843013918, 3873995871, 858643596, 3706384338, 65604258
113    ]);
114
115    let sign = if sign_bit == 0 { Sign::Plus } else { Sign::Minus };
116    let magnitude = BigUint::from(frac) * five_to_149;
117    let scale = 149;
118    let result = BigDecimal::new(BigInt::from_biguint(sign, magnitude), scale);
119    return result;
120}
121
122
123#[cfg(test)]
124#[allow(non_snake_case)]
125mod test_parse_from_f32 {
126    use super::*;
127
128    include!("parsing.tests.parse_from_f32.rs");
129}
130
131
132/// Try creating bigdecimal from f64
133///
134/// Non "normal" values will return Error case
135///
136pub(crate) fn try_parse_from_f64(n: f64) -> Result<BigDecimal, ParseBigDecimalError> {
137    use stdlib::num::FpCategory::*;
138    match n.classify() {
139        Nan => Err(ParseBigDecimalError::Other("NAN".into())),
140        Infinite => Err(ParseBigDecimalError::Other("Infinite".into())),
141        Subnormal => Ok(parse_from_f64_subnormal(n)),
142        Normal | Zero => Ok(parse_from_f64(n)),
143    }
144}
145
146
147/// Return mantissa, exponent, and sign of given floating point number
148///
149/// ```math
150/// f = frac * 2^pow
151/// ```
152///
153fn split_f64_into_parts(f: f64) -> (u64, i64, Sign) {
154    let bits = f.to_bits();
155    let frac = (bits & ((1 << 52) - 1)) + (1 << 52);
156    let exp = (bits >> 52) & 0x7FF;
157
158    let pow = exp as i64 - 1023 - 52;
159
160    let sign_bit = bits & (1 << 63);
161    let sign = if sign_bit == 0 {
162        Sign::Plus
163    } else {
164        Sign::Minus
165    };
166
167    (frac, pow, sign)
168}
169
170/// Create bigdecimal from subnormal f64
171pub(crate) fn parse_from_f64_subnormal(n: f64) -> BigDecimal {
172    debug_assert_eq!(n.classify(), FpCategory::Subnormal);
173    let bits = n.to_bits();
174
175    let sign_bit = bits >> 63;
176    debug_assert_eq!(bits >> 52, sign_bit << 11);
177
178    // 5^1074 = 5^1022 + 5^52  (f64-bit-bias=1022, fraction-bits=52)
179    let five_to_1074 = BigUint::from_slice(&[
180        2993937753, 2678407619, 3969251600, 2340035423,  635686544, 3544357150, 2618749834,
181        3195461310, 2593678749, 4014721034, 2512738537, 1379014958, 2606506302, 1209795638,
182        3422246832, 2235398534, 2765471138, 3453720203, 3699786234, 1752628667, 3832472493,
183        2479745915, 4210941784, 2088904316, 4137646701, 3840319652, 3815898978, 2202136831,
184        1022273801, 1470939580, 2032173740, 4063736241, 2069243191, 4077145663, 4033014231,
185        1920904652, 4195885152, 3551517817, 4246423481, 2447790869, 1797774111,   11284306,
186         195273359, 3811183395, 4065514955, 3382133286, 1078447835, 2100087074, 3915378083,
187        1127077286, 1409634978, 2331452623, 1301118814, 3692061923, 2506161869, 4270519152,
188        1066095370,  212429084, 3729063602, 3175008277, 2075072468, 2136773221, 4247151843,
189        2395660055,  449096848, 2439918400, 1564416362, 3638689409, 3054795416, 1803373736,
190        1506581328, 2791252870, 3391180271, 1768177410, 3891987426, 3655546435, 3881223940,
191         903390128
192    ]);
193
194    let frac = bits - (sign_bit << 63);
195
196    let sign = if sign_bit == 0 { Sign::Plus } else { Sign::Minus };
197    let magnitude = BigUint::from(frac) * five_to_1074;
198    let scale = 1074;
199
200    return BigDecimal::new(BigInt::from_biguint(sign, magnitude), scale);
201}
202
203/// Create bigdecimal from f64
204///
205/// Non "normal" values is undefined behavior
206///
207pub(crate) fn parse_from_f64(n: f64) -> BigDecimal {
208    if n.classify() == FpCategory::Subnormal {
209        return parse_from_f64_subnormal(n);
210    }
211
212    let bits = n.to_bits();
213
214    // shift right by 1 bit to handle -0.0
215    if (bits << 1) == 0 {
216        return Zero::zero();
217    }
218
219    // n = <sign> frac * 2^pow
220    let (frac, pow, sign) = split_f64_into_parts(n);
221    debug_assert!(frac > 0);
222
223    let result;
224    let scale;
225    match pow.cmp(&0) {
226        Ordering::Equal => {
227            result = BigUint::from(frac);
228            scale = 0;
229        }
230        Ordering::Less => {
231            let trailing_zeros = cmp::min(frac.trailing_zeros(), -pow as u32);
232
233            let reduced_frac = frac >> trailing_zeros;
234            let reduced_pow = pow + trailing_zeros as i64;
235            debug_assert!(reduced_pow <= 0);
236
237            let shift = BigUint::from(5u8).pow(-reduced_pow as u32);
238
239            result = reduced_frac * shift;
240            scale = -reduced_pow;
241        }
242        Ordering::Greater => {
243            let shift = BigUint::from(2u8).pow(pow as u32);
244            result = frac * shift;
245            scale = 0;
246        }
247    }
248
249    BigDecimal {
250        int_val: BigInt::from_biguint(sign, result),
251        scale: scale,
252    }
253}
254
255#[cfg(test)]
256#[allow(non_snake_case)]
257mod test_parse_from_f64 {
258    use super::*;
259
260    include!("parsing.tests.parse_from_f64.rs");
261}