fcla/num/
decimal.rs

1use super::{Decimal, Digit, Digits, Sign, UnsignedDecimal};
2use num::{BigInt, BigUint, Zero as _};
3
4pub type Parent = String;
5
6pub fn from_arg(mut arg: Parent) -> Option<Decimal> {
7    use UnsignedDecimal::*;
8
9    let mut exponent = take_exponent(&mut arg)?;
10    let mut arg = arg.into_bytes();
11    let sign = take_sign(&mut arg);
12    let shift = take_radix(&mut arg);
13    if arg.is_empty() {
14        return None;
15    }
16    let trailing = remove_trailing_zeros(&mut arg);
17    let magnitude = if arg.is_empty() {
18        Zero
19    } else {
20        remove_leading_zeros(&mut arg);
21        let adjust = trailing as isize - shift as isize;
22        exponent += adjust;
23        let digits = into_digits_inner(arg.into_boxed_slice())?;
24        NonZero {
25            base: Digits(digits),
26            exponent,
27        }
28    };
29    Some(Decimal { sign, magnitude })
30}
31
32fn take_exponent(arg: &mut String) -> Option<BigInt> {
33    let (left, right) = match arg.split_once(['E', 'e']) {
34        None => return Some(BigInt::zero()),
35        Some(parts) => parts,
36    };
37    let exponent = parse_exponent(right)?;
38    arg.truncate(left.len());
39    Some(exponent)
40}
41
42fn parse_exponent(exponent: &str) -> Option<BigInt> {
43    use num::bigint::Sign::*;
44
45    let sign = if exponent.starts_with('-') {
46        Minus
47    } else {
48        Plus
49    };
50    let magnitude = exponent.strip_prefix(['+', '-']).unwrap_or(exponent);
51    if magnitude.is_empty() {
52        return None;
53    }
54    let mut sum = BigUint::zero();
55    for byte in magnitude.bytes() {
56        let digit = match byte {
57            b'0'..=b'9' => byte - b'0',
58            _ => return None,
59        };
60        sum = 10u32 * sum + digit;
61    }
62    let magnitude = sum;
63    let exponent = BigInt::from_biguint(sign, magnitude);
64    Some(exponent)
65}
66
67fn take_sign(arg: &mut Vec<u8>) -> Sign {
68    use Sign::*;
69
70    let sign = match **arg {
71        [b'+', ..] => Some(Positive),
72        [b'-', ..] => Some(Negative),
73        _ => None,
74    };
75    if sign.is_some() {
76        arg.remove(0);
77    }
78    sign.unwrap_or(Positive)
79}
80
81fn take_radix(arg: &mut Vec<u8>) -> usize {
82    let radix = match arg.iter().position(|byte| *byte == b'.') {
83        None => return 0,
84        Some(index) => index,
85    };
86    arg.remove(radix);
87    arg.len() - radix
88}
89
90fn remove_trailing_zeros(arg: &mut Vec<u8>) -> usize {
91    let length = arg.len();
92    let last = arg
93        .iter()
94        .rposition(|byte| *byte != b'0')
95        .map(|index| index + 1)
96        .unwrap_or(0);
97    arg.truncate(last);
98    length - last
99}
100
101fn remove_leading_zeros(arg: &mut Vec<u8>) {
102    let first = arg.iter().position(|byte| *byte != b'0').unwrap_or(0);
103    let truncate = arg[first..].len();
104    arg.copy_within(first.., 0);
105    arg.truncate(truncate);
106}
107
108fn into_digits_inner(mut arg: Box<[u8]>) -> Option<Box<[Digit]>> {
109    for byte in arg.iter_mut() {
110        match byte {
111            b'0'..=b'9' => *byte -= b'0',
112            _ => return None,
113        }
114    }
115    Some(unsafe { Box::from_raw(Box::into_raw(arg) as *mut [Digit]) })
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use Digit::*;
122
123    pub fn parse<A: ToString, const LEN: usize>(args: [A; LEN]) -> crate::ParseResult<Decimal> {
124        crate::internal::parse(args)
125    }
126
127    #[test]
128    fn empty() {
129        parse([""]).unwrap_err();
130    }
131
132    #[test]
133    fn plus() {
134        parse(["+"]).unwrap_err();
135    }
136
137    #[test]
138    fn minus() {
139        parse(["-"]).unwrap_err();
140    }
141
142    #[test]
143    fn period() {
144        parse(["."]).unwrap_err();
145    }
146
147    #[test]
148    fn plus_period() {
149        parse(["+."]).unwrap_err();
150    }
151
152    #[test]
153    fn minus_period() {
154        parse(["-."]).unwrap_err();
155    }
156
157    #[test]
158    fn e() {
159        parse(["e"]).unwrap_err();
160    }
161
162    #[test]
163    fn zero() {
164        let Decimal { sign, magnitude } = parse(["0"]).unwrap();
165        assert!(sign.is_positive());
166        match magnitude {
167            UnsignedDecimal::Zero => (),
168            UnsignedDecimal::NonZero { .. } => panic!("`magnitude` should be zero"),
169        }
170    }
171
172    #[test]
173    fn plus_one() {
174        let Decimal { sign, magnitude } = parse(["+1"]).unwrap();
175        assert!(sign.is_positive());
176        let (base, exponent) = match magnitude {
177            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
178            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
179        };
180        assert_eq!(*base.into_inner(), [D1]);
181        assert_eq!(exponent, BigInt::from(0));
182    }
183
184    #[test]
185    fn minus_one() {
186        let Decimal { sign, magnitude } = parse(["-1"]).unwrap();
187        assert!(sign.is_negative());
188        let (base, exponent) = match magnitude {
189            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
190            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
191        };
192        assert_eq!(*base.into_inner(), [D1]);
193        assert_eq!(exponent, BigInt::from(0));
194    }
195
196    #[test]
197    fn radix_one() {
198        let Decimal { sign, magnitude } = parse([".1"]).unwrap();
199        assert!(sign.is_positive());
200        let (base, exponent) = match magnitude {
201            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
202            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
203        };
204        assert_eq!(*base.into_inner(), [D1]);
205        assert_eq!(exponent, BigInt::from(-1));
206    }
207
208    #[test]
209    fn radix_plus_one() {
210        parse([".+1"]).unwrap_err();
211    }
212
213    #[test]
214    fn radix_minus_one() {
215        parse([".-1"]).unwrap_err();
216    }
217
218    #[test]
219    fn one_e_one() {
220        let Decimal { sign, magnitude } = parse(["1e1"]).unwrap();
221        assert!(sign.is_positive());
222        let (base, exponent) = match magnitude {
223            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
224            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
225        };
226        assert_eq!(*base.into_inner(), [D1]);
227        assert_eq!(exponent, BigInt::from(1));
228    }
229
230    #[test]
231    fn one_e_plus_one() {
232        let Decimal { sign, magnitude } = parse(["1e+1"]).unwrap();
233        assert!(sign.is_positive());
234        let (base, exponent) = match magnitude {
235            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
236            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
237        };
238        assert_eq!(*base.into_inner(), [D1]);
239        assert_eq!(exponent, BigInt::from(1));
240    }
241
242    #[test]
243    fn one_e_minus_one() {
244        let Decimal { sign, magnitude } = parse(["1e-1"]).unwrap();
245        assert!(sign.is_positive());
246        let (base, exponent) = match magnitude {
247            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
248            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
249        };
250        assert_eq!(*base.into_inner(), [D1]);
251        assert_eq!(exponent, BigInt::from(-1));
252    }
253
254    #[test]
255    fn leading_zeros() {
256        let Decimal { sign, magnitude } = parse(["+0001"]).unwrap();
257        assert!(sign.is_positive());
258        let (base, exponent) = match magnitude {
259            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
260            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
261        };
262        assert_eq!(*base.into_inner(), [D1]);
263        assert_eq!(exponent, BigInt::from(0));
264    }
265
266    #[test]
267    fn trailing_zeros() {
268        let Decimal { sign, magnitude } = parse(["1000.000e000"]).unwrap();
269        assert!(sign.is_positive());
270        let (base, exponent) = match magnitude {
271            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
272            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
273        };
274        assert_eq!(*base.into_inner(), [D1]);
275        assert_eq!(exponent, BigInt::from(3));
276    }
277
278    #[test]
279    fn typical() {
280        let Decimal { sign, magnitude } = parse(["123.456e789"]).unwrap();
281        assert!(sign.is_positive());
282        let (base, exponent) = match magnitude {
283            UnsignedDecimal::Zero => panic!("`magnitude` should be non-zero"),
284            UnsignedDecimal::NonZero { base, exponent } => (base, exponent),
285        };
286        assert_eq!(*base.into_inner(), [D1, D2, D3, D4, D5, D6]);
287        assert_eq!(exponent, BigInt::from(786));
288    }
289}