Browse Source

Added temporary fix for unstable std from_str_radix method

mitchmindtree 10 years ago
parent
commit
63d2e79c1e
1 changed files with 150 additions and 4 deletions
  1. 150 4
      src/traits.rs

+ 150 - 4
src/traits.rs

@@ -43,14 +43,160 @@ macro_rules! int_trait_impl {
     )*)
 }
 
+// FIXME: Temporary replacements for unstable ::std::num::ParseFloatError and
+// ::std::num::FloatErrorKind. These can be removed once the std float implementation of
+// from_str_radix stabilises.
+pub enum FloatErrorKind { Empty, Invalid }
+pub struct ParseFloatError { pub kind: FloatErrorKind }
+
+// FIXME: This should be removed and replaced with the std implementation of from_str_radix once
+// it is stabilised.
 macro_rules! float_trait_impl {
     ($name:ident for $($t:ty)*) => ($(
         impl $name for $t {
-            type FromStrRadixErr = ::std::num::ParseFloatError;
-            fn from_str_radix(s: &str, radix: u32)
-                              -> Result<Self, ::std::num::ParseFloatError>
+            type FromStrRadixErr = ParseFloatError;
+            fn from_str_radix(src: &str, radix: u32)
+                              -> Result<Self, ParseFloatError>
             {
-                Num::from_str_radix(s, radix)
+                use self::FloatErrorKind::*;
+                use self::ParseFloatError as PFE;
+
+                // Special values
+                match src {
+                    "inf"   => return Ok(Float::infinity()),
+                    "-inf"  => return Ok(Float::neg_infinity()),
+                    "NaN"   => return Ok(Float::nan()),
+                    _       => {},
+                }
+
+                fn slice_shift_char(src: &str) -> Option<(char, &str)> {
+                    src.chars().nth(0).map(|ch| (ch, &src[1..]))
+                }
+
+                let (is_positive, src) =  match slice_shift_char(src) {
+                    None             => return Err(PFE { kind: Empty }),
+                    Some(('-', ""))  => return Err(PFE { kind: Empty }),
+                    Some(('-', src)) => (false, src),
+                    Some((_, _))     => (true,  src),
+                };
+
+                // The significand to accumulate
+                let mut sig = if is_positive { 0.0 } else { -0.0 };
+                // Necessary to detect overflow
+                let mut prev_sig = sig;
+                let mut cs = src.chars().enumerate();
+                // Exponent prefix and exponent index offset
+                let mut exp_info = None::<(char, usize)>;
+
+                // Parse the integer part of the significand
+                for (i, c) in cs.by_ref() {
+                    match c.to_digit(radix) {
+                        Some(digit) => {
+                            // shift significand one digit left
+                            sig = sig * (radix as $t);
+
+                            // add/subtract current digit depending on sign
+                            if is_positive {
+                                sig = sig + ((digit as isize) as $t);
+                            } else {
+                                sig = sig - ((digit as isize) as $t);
+                            }
+
+                            // Detect overflow by comparing to last value, except
+                            // if we've not seen any non-zero digits.
+                            if prev_sig != 0.0 {
+                                if is_positive && sig <= prev_sig
+                                    { return Ok(Float::infinity()); }
+                                if !is_positive && sig >= prev_sig
+                                    { return Ok(Float::neg_infinity()); }
+
+                                // Detect overflow by reversing the shift-and-add process
+                                if is_positive && (prev_sig != (sig - digit as $t) / radix as $t)
+                                    { return Ok(Float::infinity()); }
+                                if !is_positive && (prev_sig != (sig + digit as $t) / radix as $t)
+                                    { return Ok(Float::neg_infinity()); }
+                            }
+                            prev_sig = sig;
+                        },
+                        None => match c {
+                            'e' | 'E' | 'p' | 'P' => {
+                                exp_info = Some((c, i + 1));
+                                break;  // start of exponent
+                            },
+                            '.' => {
+                                break;  // start of fractional part
+                            },
+                            _ => {
+                                return Err(PFE { kind: Invalid });
+                            },
+                        },
+                    }
+                }
+
+                // If we are not yet at the exponent parse the fractional
+                // part of the significand
+                if exp_info.is_none() {
+                    let mut power = 1.0;
+                    for (i, c) in cs.by_ref() {
+                        match c.to_digit(radix) {
+                            Some(digit) => {
+                                // Decrease power one order of magnitude
+                                power = power / (radix as $t);
+                                // add/subtract current digit depending on sign
+                                sig = if is_positive {
+                                    sig + (digit as $t) * power
+                                } else {
+                                    sig - (digit as $t) * power
+                                };
+                                // Detect overflow by comparing to last value
+                                if is_positive && sig < prev_sig
+                                    { return Ok(Float::infinity()); }
+                                if !is_positive && sig > prev_sig
+                                    { return Ok(Float::neg_infinity()); }
+                                prev_sig = sig;
+                            },
+                            None => match c {
+                                'e' | 'E' | 'p' | 'P' => {
+                                    exp_info = Some((c, i + 1));
+                                    break; // start of exponent
+                                },
+                                _ => {
+                                    return Err(PFE { kind: Invalid });
+                                },
+                            },
+                        }
+                    }
+                }
+
+                // Parse and calculate the exponent
+                let exp = match exp_info {
+                    Some((c, offset)) => {
+                        let base = match c {
+                            'E' | 'e' if radix == 10 => 10.0,
+                            'P' | 'p' if radix == 16 => 2.0,
+                            _ => return Err(PFE { kind: Invalid }),
+                        };
+
+                        // Parse the exponent as decimal integer
+                        let src = &src[offset..];
+                        let (is_positive, exp) = match slice_shift_char(src) {
+                            Some(('-', src)) => (false, src.parse::<usize>()),
+                            Some(('+', src)) => (true,  src.parse::<usize>()),
+                            Some((_, _))     => (true,  src.parse::<usize>()),
+                            None             => return Err(PFE { kind: Invalid }),
+                        };
+
+                        match (is_positive, exp) {
+                            (true,  Ok(exp)) => base.powi(exp as i32),
+                            (false, Ok(exp)) => 1.0 / base.powi(exp as i32),
+                            (_, Err(_))      => return Err(PFE { kind: Invalid }),
+                        }
+                    },
+                    None => 1.0, // no exponent
+                };
+
+                Ok(sig * exp)
+
             }
         }
     )*)