]> git.lizzy.rs Git - rust.git/commitdiff
[Fix] float.rs: str_to_float reimplemented
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Wed, 12 Oct 2011 08:24:43 +0000 (10:24 +0200)
committerDavid Rajchenbach-Teller <dteller@mozilla.com>
Wed, 12 Oct 2011 08:56:05 +0000 (10:56 +0200)
src/lib/float.rs

index 9a241ba36cf36b8e411570ee9516e0a6fc9f5ed7..e961c34e66082abc0f4a1ae80b37fabcb374045c 100644 (file)
@@ -1,3 +1,7 @@
+/**
+ * String conversions
+ */
+
 fn float_to_str(num: float, digits: uint) -> str {
     let accum = if num < 0.0 { num = -num; "-" } else { "" };
     let trunc = num as uint;
@@ -15,21 +19,180 @@ fn float_to_str(num: float, digits: uint) -> str {
     ret accum;
 }
 
+/**
+ * Convert a string to a float
+ *
+ * This function accepts strings such as
+ * * "3.14"
+ * * "+3.14", equivalent to "3.14"
+ * * "-3.14"
+ * * "2.5E10", or equivalently, "2.5e10"
+ * * "2.5E-10"
+ * * "", or, equivalently, "." (understood as 0)
+ * * "5."
+ * * ".5", or, equivalently,  "0.5"
+ *
+ * @param num A string, possibly empty.
+ * @return [NaN] if the string did not represent a valid number.
+ * @return Otherwise, the floating-point number represented [num].
+ */
 fn str_to_float(num: str) -> float {
-    let digits = str::split(num, '.' as u8);
-    let total = int::from_str(digits[0]) as float;
-
-    fn dec_val(c: char) -> int { ret (c as int) - ('0' as int); }
-
-    let right = digits[1];
-    let len = str::char_len(digits[1]);
-    let i = 1u;
-    while (i < len) {
-        total += dec_val(str::pop_char(right)) as float /
-                 (int::pow(10, i) as float);
-        i += 1u;
-    }
-    ret total;
+   let pos = 0u;                  //Current byte position in the string.
+                                  //Used to walk the string in O(n).
+   let len = str::byte_len(num);  //Length of the string, in bytes.
+
+   if len == 0u { ret 0.; }
+   let total = 0f;                //Accumulated result
+   let c     = 'z';               //Latest char.
+
+   //Determine if first char is '-'/'+'. Set [pos] and [neg] accordingly.
+   let neg = false;               //Sign of the result
+   alt str::char_at(num, 0u) {
+      '-' {
+          neg = true;
+          pos = 1u;
+      }
+      '+' {
+          pos = 1u;
+      }
+      _ {}
+   }
+
+   //Examine the following chars until '.', 'e', 'E'
+   while(pos < len) {
+       let char_range = str::char_range_at(num, pos);
+       c   = char_range.ch;
+       pos = char_range.next;
+       alt c {
+          '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9'  {
+           total = total * 10f;
+           total += ((c as int) - ('0' as int)) as float;
+         }
+         _ {
+           break;
+         }
+       }
+   }
+
+   if c == '.' {//Examine decimal part
+      let decimal = 1.f;
+      while(pos < len) {
+         let char_range = str::char_range_at(num, pos);
+         c = char_range.ch;
+         pos = char_range.next;
+         alt c {
+            '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9'  {
+                 decimal /= 10.f;
+                 total += (((c as int) - ('0' as int)) as float)*decimal;
+             }
+             _ {
+                 break;
+             }
+         }
+      }
+   }
+
+   if (c == 'e') | (c == 'E') {//Examine exponent
+      let exponent = 0u;
+      let neg_exponent = false;
+      if(pos < len) {
+          let char_range = str::char_range_at(num, pos);
+          c   = char_range.ch;
+          alt c  {
+             '+' {
+                pos = char_range.next;
+             }
+             '-' {
+                pos = char_range.next;
+                neg_exponent = true;
+             }
+             _ {}
+          }
+          while(pos < len) {
+             let char_range = str::char_range_at(num, pos);
+             c = char_range.ch;
+             pos = char_range.next;
+             alt c {
+                 '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9' {
+                     exponent *= 10u;
+                     exponent += ((c as uint) - ('0' as uint));
+                 }
+                 _ {
+                     break;
+                 }
+             }
+          }
+          let multiplier = pow_uint_to_uint_as_float(10u, exponent);
+              //Note: not [int::pow], otherwise, we'll quickly
+              //end up with a nice overflow
+          if neg_exponent {
+             total = total / multiplier;
+          } else {
+             total = total * multiplier;
+          }
+      }
+   }
+
+   if(pos < len) {
+     ret NaN();
+   } else {
+     if(neg) {
+        total *= -1f;
+     }
+     ret total;
+   }
+}
+
+/**
+ * Arithmetics
+ */
+
+/**
+ * Compute the exponentiation of an integer by another integer as a float.
+ *
+ *
+ * @param x The base.
+ * @param pow The exponent.
+ * @return [NaN] of both [x] and [pow] are [0u], otherwise [x^pow].
+ */
+fn pow_uint_to_uint_as_float(x: uint, pow: uint) -> float {
+   if x == 0u {
+      if pow == 0u {
+        ret NaN();
+      }
+       ret 0.;
+   }
+   let my_pow     = pow;
+   let total      = 1f;
+   let multiplier = x as float;
+   while (my_pow > 0u) {
+     if my_pow % 2u == 1u {
+       total = total * multiplier;
+     }
+     my_pow     /= 2u;
+     multiplier *= multiplier;
+   }
+   ret total;
+}
+
+
+/**
+ * Constants
+ */
+
+//TODO: Once this is possible, replace the body of these functions
+//by an actual constant.
+
+fn NaN() -> float {
+   ret 0./0.;
+}
+
+fn infinity() -> float {
+   ret 1./0.;
+}
+
+fn neg_infinity() -> float {
+   ret -1./0.;
 }
 
 //