rust_fixed_point_decimal/
from_str.rs

1// ---------------------------------------------------------------------------
2// Copyright:   (c) 2021 ff. Michael Amrhein (michael@adrhinum.de)
3// License:     This program is part of a larger application. For license
4//              details please read the file LICENSE.TXT provided together
5//              with the application.
6// ---------------------------------------------------------------------------
7// $Source: src/from_str.rs $
8// $Revision: 2021-10-29T12:14:58+02:00 $
9
10use std::{cmp::Ordering, convert::TryFrom, str::FromStr};
11
12use rust_fixed_point_decimal_core::{checked_mul_pow_ten, dec_repr_from_str};
13
14use crate::{Decimal, ParseDecimalError, PrecLimitCheck, True, MAX_PREC};
15
16impl<const P: u8> FromStr for Decimal<P>
17where
18    PrecLimitCheck<{ P <= MAX_PREC }>: True,
19{
20    type Err = ParseDecimalError;
21
22    /// Convert a number literal into a `Decimal<P>`.
23    ///
24    /// The literal must be in the form
25    ///
26    /// `[+|-]<int>[.<frac>][<e|E>[+|-]<exp>]`
27    ///
28    /// or
29    ///
30    /// `[+|-].<frac>[<e|E>[+|-]<exp>]`.
31    ///
32    /// The function returns an error in these cases:
33    ///
34    /// * An empty string has been given as `lit` -> `ParseDecimalError::Empty`
35    /// * `lit` does not fit one of the two forms given above ->
36    ///   `ParseDecimalError::Invalid`
37    /// * The number of fractional digits in `lit` minus the value of the signed
38    ///   exponent in `lit` exceeds the type parameter `P` ->
39    ///   `ParseDecimalError::PrecLimitExceeded`
40    /// * The given decimal literal exceeds the maximum value representable by
41    ///   the type -> ParseDecimalError::MaxValueExceeded
42    ///
43    /// # Examples:
44    ///
45    /// ```rust
46    /// # #![allow(incomplete_features)]
47    /// # #![feature(generic_const_exprs)]
48    /// # use rust_fixed_point_decimal::{Decimal, ParseDecimalError};
49    /// # use std::str::FromStr;
50    /// # fn main() -> Result<(), ParseDecimalError> {
51    /// let d = Decimal::<4>::from_str("38.207")?;
52    /// assert_eq!(d.to_string(), "38.2070");
53    /// let d = Decimal::<7>::from_str("-132.0207e-2")?;
54    /// assert_eq!(d.to_string(), "-1.3202070");
55    /// # Ok(()) }
56    /// ```
57    fn from_str(lit: &str) -> Result<Self, Self::Err> {
58        let prec = P as isize;
59        let (coeff, exponent) = dec_repr_from_str(lit)?;
60        if exponent > 0 {
61            let shift = prec + exponent;
62            if shift > 38 {
63                // 10 ^ 39 > int128::MAX
64                return Result::Err(ParseDecimalError::MaxValueExceeded);
65            }
66            match checked_mul_pow_ten(coeff, shift as u8) {
67                None => Result::Err(ParseDecimalError::MaxValueExceeded),
68                Some(coeff) => Ok(Self::new_raw(coeff)),
69            }
70        } else {
71            let n_frac_digits = -exponent;
72            match n_frac_digits.cmp(&prec) {
73                Ordering::Equal => Ok(Self::new_raw(coeff)),
74                Ordering::Less => {
75                    let shift = prec - n_frac_digits;
76                    match checked_mul_pow_ten(coeff, shift as u8) {
77                        None => {
78                            Result::Err(ParseDecimalError::MaxValueExceeded)
79                        }
80                        Some(coeff) => Ok(Self::new_raw(coeff)),
81                    }
82                }
83                Ordering::Greater => {
84                    Result::Err(ParseDecimalError::PrecLimitExceeded)
85                }
86            }
87        }
88    }
89}
90
91impl<const P: u8> TryFrom<&str> for Decimal<P>
92where
93    PrecLimitCheck<{ P <= MAX_PREC }>: True,
94{
95    type Error = ParseDecimalError;
96
97    #[inline]
98    fn try_from(lit: &str) -> Result<Self, Self::Error> {
99        Self::from_str(lit)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use std::{convert::TryFrom, str::FromStr};
106
107    use rust_fixed_point_decimal_core::ParseDecimalError;
108
109    use crate::Decimal;
110
111    #[test]
112    fn test_from_int_lit() {
113        let d = Decimal::<4>::from_str("1957945").unwrap();
114        assert_eq!(d.coeff, 19579450000);
115    }
116
117    #[test]
118    fn test_from_int_lit_no_prec() {
119        let d = Decimal::<0>::from_str("1957945").unwrap();
120        assert_eq!(d.coeff, 1957945);
121    }
122
123    #[test]
124    fn test_from_dec_lit() {
125        let d = Decimal::<2>::from_str("-17.5").unwrap();
126        assert_eq!(d.coeff, -1750);
127    }
128
129    #[test]
130    fn test_from_frac_only_lit() {
131        let d = Decimal::<7>::from_str("+.75").unwrap();
132        assert_eq!(d.coeff, 7500000);
133    }
134
135    #[test]
136    fn test_from_int_lit_neg_exp() {
137        let d = Decimal::<5>::from_str("17e-5").unwrap();
138        assert_eq!(d.coeff, 17);
139    }
140
141    #[test]
142    fn test_from_int_lit_pos_exp() {
143        let d = Decimal::<1>::from_str("+217e3").unwrap();
144        assert_eq!(d.coeff, 2170000);
145    }
146
147    #[test]
148    fn test_from_dec_lit_neg_exp() {
149        let d = Decimal::<3>::from_str("-533.7e-2").unwrap();
150        assert_eq!(d.coeff, -5337);
151    }
152
153    #[test]
154    fn test_from_dec_lit_pos_exp() {
155        let d = Decimal::<1>::from_str("700004.002E13").unwrap();
156        assert_eq!(d.coeff, 70000400200000000000);
157    }
158
159    #[test]
160    fn test_err_empty_str() {
161        let res = Decimal::<2>::from_str("");
162        assert!(res.is_err());
163        let err = res.unwrap_err();
164        assert_eq!(err, ParseDecimalError::Empty);
165    }
166
167    #[test]
168    fn test_err_invalid_lit() {
169        let lits = [" ", "+", "-4.33.2", "2.87 e3", "+e3", ".4e3 "];
170        for lit in lits {
171            let res = Decimal::<2>::from_str(lit);
172            assert!(res.is_err());
173            let err = res.unwrap_err();
174            assert_eq!(err, ParseDecimalError::Invalid);
175        }
176    }
177
178    #[test]
179    fn test_prec_limit_exceeded() {
180        let res = Decimal::<2>::from_str("17.295");
181        assert!(res.is_err());
182        let err = res.unwrap_err();
183        assert_eq!(err, ParseDecimalError::PrecLimitExceeded);
184    }
185
186    #[test]
187    fn test_prec_limit_exceeded_with_exp() {
188        let res = Decimal::<3>::from_str("17.4e-3");
189        assert!(res.is_err());
190        let err = res.unwrap_err();
191        assert_eq!(err, ParseDecimalError::PrecLimitExceeded);
192    }
193
194    #[test]
195    fn test_int_lit_max_val_dec0_exceeded() {
196        let i = i128::MIN;
197        let mut s = format!("{}", i);
198        s.remove(0);
199        let res = Decimal::<0>::from_str(&s);
200        assert!(res.is_err());
201        let err = res.unwrap_err();
202        assert_eq!(err, ParseDecimalError::MaxValueExceeded);
203    }
204
205    #[test]
206    fn test_int_lit_max_val_dec2_exceeded() {
207        let i = i128::MAX / 100 + 1;
208        let s = format!("{}", i);
209        let res = Decimal::<2>::from_str(&s);
210        assert!(res.is_err());
211        let err = res.unwrap_err();
212        assert_eq!(err, ParseDecimalError::MaxValueExceeded);
213    }
214
215    #[test]
216    fn test_dec_lit_max_val_exceeded() {
217        let s = "123456789012345678901234567890123.4567890";
218        let res = Decimal::<7>::from_str(&s);
219        assert!(res.is_err());
220        let err = res.unwrap_err();
221        assert_eq!(err, ParseDecimalError::MaxValueExceeded);
222    }
223
224    #[test]
225    fn test_parse() {
226        let s = "+28.700";
227        let res = s.parse::<Decimal<3>>();
228        assert!(!res.is_err());
229        let dec = res.unwrap();
230        assert_eq!(dec.coeff, 28700);
231    }
232
233    #[test]
234    fn test_parse_prec_limit_exceeded() {
235        let s = "+28.7005";
236        let res = s.parse::<Decimal<3>>();
237        assert!(res.is_err());
238        let err = res.unwrap_err();
239        assert_eq!(err, ParseDecimalError::PrecLimitExceeded);
240    }
241
242    #[test]
243    fn test_try_from() {
244        let s = "-534000.708";
245        let res = Decimal::<4>::try_from(s);
246        assert!(!res.is_err());
247        let dec = res.unwrap();
248        assert_eq!(dec.coeff, -5340007080);
249    }
250
251    #[test]
252    fn test_try_from_prec_limit_exceeded() {
253        let s = "+28.700500";
254        let res = Decimal::<5>::try_from(s);
255        assert!(res.is_err());
256        let err = res.unwrap_err();
257        assert_eq!(err, ParseDecimalError::PrecLimitExceeded);
258    }
259}