1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Validating and decomposing a decimal string of the form:
13 //! `(digits | digits? '.'? digits?) (('e' | 'E') ('+' | '-')? digits)?`
15 //! In other words, standard floating-point syntax, with two exceptions: No sign, and no
16 //! handling of "inf" and "NaN". These are handled by the driver function (super::dec2flt).
18 //! Although recognizing valid inputs is relatively easy, this module also has to reject the
19 //! countless invalid variations, never panic, and perform numerous checks that the other
20 //! modules rely on to not panic (or overflow) in turn.
21 //! To make matters worse, all that happens in a single pass over the input.
22 //! So, be careful when modifying anything, and double-check with the other modules.
24 use self::ParseResult
::{Valid, ShortcutToInf, ShortcutToZero, Invalid}
;
32 #[derive(Debug, PartialEq, Eq)]
33 /// The interesting parts of a decimal string.
34 pub struct Decimal
<'a
> {
35 pub integral
: &'a
[u8],
36 pub fractional
: &'a
[u8],
37 /// The decimal exponent, guaranteed to have fewer than 18 decimal digits.
41 impl<'a
> Decimal
<'a
> {
42 pub fn new(integral
: &'a
[u8], fractional
: &'a
[u8], exp
: i64) -> Decimal
<'a
> {
43 Decimal { integral: integral, fractional: fractional, exp: exp }
47 #[derive(Debug, PartialEq, Eq)]
48 pub enum ParseResult
<'a
> {
55 /// Check if the input string is a valid floating point number and if so, locate the integral
56 /// part, the fractional part, and the exponent in it. Does not handle signs.
57 pub fn parse_decimal(s
: &str) -> ParseResult
{
63 let (integral
, s
) = eat_digits(s
);
66 None
=> Valid(Decimal
::new(integral
, b
"", 0)),
67 Some(&b'e'
) | Some(&b'E'
) => {
68 if integral
.is_empty() {
69 return Invalid
; // No digits before 'e'
72 parse_exp(integral
, b
"", &s
[1..])
75 let (fractional
, s
) = eat_digits(&s
[1..]);
76 if integral
.is_empty() && fractional
.is_empty() && s
.is_empty() {
81 None
=> Valid(Decimal
::new(integral
, fractional
, 0)),
82 Some(&b'e'
) | Some(&b'E'
) => parse_exp(integral
, fractional
, &s
[1..]),
83 _
=> Invalid
, // Trailing junk after fractional part
86 _
=> Invalid
, // Trailing junk after first digit string
90 /// Carve off decimal digits up to the first non-digit character.
91 fn eat_digits(s
: &[u8]) -> (&[u8], &[u8]) {
93 while i
< s
.len() && b'
0'
<= s
[i
] && s
[i
] <= b'
9'
{
99 /// Exponent extraction and error checking.
100 fn parse_exp
<'a
>(integral
: &'a
[u8], fractional
: &'a
[u8], rest
: &'a
[u8]) -> ParseResult
<'a
> {
101 let (sign
, rest
) = match rest
.first() {
102 Some(&b'
-'
) => (Sign
::Negative
, &rest
[1..]),
103 Some(&b'
+'
) => (Sign
::Positive
, &rest
[1..]),
104 _
=> (Sign
::Positive
, rest
),
106 let (mut number
, trailing
) = eat_digits(rest
);
107 if !trailing
.is_empty() {
108 return Invalid
; // Trailing junk after exponent
110 if number
.is_empty() {
111 return Invalid
; // Empty exponent
113 // At this point, we certainly have a valid string of digits. It may be too long to put into
114 // an `i64`, but if it's that huge, the input is certainly zero or infinity. Since each zero
115 // in the decimal digits only adjusts the exponent by +/- 1, at exp = 10^18 the input would
116 // have to be 17 exabyte (!) of zeros to get even remotely close to being finite.
117 // This is not exactly a use case we need to cater to.
118 while number
.first() == Some(&b'
0'
) {
119 number
= &number
[1..];
121 if number
.len() >= 18 {
123 Sign
::Positive
=> ShortcutToInf
,
124 Sign
::Negative
=> ShortcutToZero
,
127 let abs_exp
= num
::from_str_unchecked(number
);
129 Sign
::Positive
=> abs_exp
as i64,
130 Sign
::Negative
=> -(abs_exp
as i64),
132 Valid(Decimal
::new(integral
, fractional
, e
))