bigdecimal/
impl_num.rs
1use 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"))]
21fn 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 #[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 let (base_part, exponent_value) = match s.find(exp_separator) {
47 None => (s, 0),
49
50 Some(loc) => {
52 let (base, e_exp) = s.split_at(loc);
54 (base, i128::from_str(&e_exp[1..])?)
55 }
56 };
57
58 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 let (digits, decimal_offset) = match base_part.find('.') {
69 None => (base_part, 0),
71 Some(loc) if loc == last_digit_loc => {
73 (&base_part[..last_digit_loc], 0)
74 }
75 Some(loc) => {
77 let (lead, trail) = (&base_part[..loc], &base_part[loc + 1..]);
79
80 digit_buffer.reserve(lead.len() + trail.len());
81 digit_buffer.push_str(lead);
83 digit_buffer.push_str(trail);
85
86 let trail_digits = trail.chars().filter(|c| *c != '_').count();
88
89 (digit_buffer.as_str(), trail_digits as i128)
90 }
91 };
92
93 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 let (mut int_cow, mut scale) = self.to_cow_biguint_and_scale();
191
192 let digit_count = ((int_cow.bits() + 1) as f64 * stdlib::f64::consts::LOG10_2).floor() as u64;
194
195 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 let f = int_cow.to_f64().map(copy_sign_to_float)?;
211 (f * powi(10.0, pow)).into()
212 }
213 Some(exp) => {
214 #[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 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 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}