summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/parseutils.nim94
-rw-r--r--tests/float/tfloat4.nim42
2 files changed, 92 insertions, 44 deletions
diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim
index 484ba5184..9b3f79a74 100644
--- a/lib/pure/parseutils.nim
+++ b/lib/pure/parseutils.nim
@@ -231,33 +231,43 @@ proc parseInt*(s: string, number: var int, start = 0): int {.
   else:
     number = int(res)
 
-proc tenToThePowerOf(b: int): BiggestFloat =
-  var b = b
-  var a = 10.0
-  result = 1.0
-  while true:
-    if (b and 1) == 1:
-      result *= a
-    b = b shr 1
-    if b == 0: break
-    a *= a
-
 proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
   rtl, extern: "npuParseBiggestFloat", noSideEffect.} =
   ## parses a float starting at `start` and stores the value into `number`.
-  ## Result is the number of processed chars or 0 if there occured a parsing
-  ## error.
+  ## Result is the number of processed chars or 0 if a parsing error
+  ## occurred.
+
+  type struct_lconv {.importc: "struct lconv",header:"<locale.h>".} =
+    object
+      # Unneeded fields have been omitted.
+      decimal_point: cstring
+
+  proc localeconv(): ptr struct_lconv {.importc, header: "<locale.h>",
+    noSideEffect.}
+
+  proc strtod(buf: cstring, endptr: ptr cstring): float64 {.importc,
+    header: "<stdlib.h>", noSideEffect.}
+
+  # This routine leverages `strtod()` for the non-trivial task of
+  # parsing floating point numbers correctly. Because `strtod()` is
+  # locale-dependent with respect to the radix character, we create
+  # a copy where the decimal point is replaced with the locale's
+  # radix character.
+
   var
-    esign = 1.0
-    sign = 1.0
     i = start
-    exponent: int
-    flags: int
-  number = 0.0
-  if s[i] == '+': inc(i)
-  elif s[i] == '-':
-    sign = -1.0
+    sign = 1.0
+    t = ""
+    hasdigits = false
+  
+  # Sign?
+  if s[i] == '+' or s[i] == '-':
+    if s[i] == '-':
+      sign = -1.0
+    add(t, s[i])
     inc(i)
+
+  # NaN?
   if s[i] == 'N' or s[i] == 'n':
     if s[i+1] == 'A' or s[i+1] == 'a':
       if s[i+2] == 'N' or s[i+2] == 'n':
@@ -265,6 +275,8 @@ proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
           number = NaN
           return i+3 - start
     return 0
+
+  # Inf?
   if s[i] == 'I' or s[i] == 'i':
     if s[i+1] == 'N' or s[i+1] == 'n':
       if s[i+2] == 'F' or s[i+2] == 'f':
@@ -272,46 +284,40 @@ proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
           number = Inf*sign
           return i+3 - start
     return 0
+
+  # Integer part?
   while s[i] in {'0'..'9'}:
-    # Read integer part
-    flags = flags or 1
-    number = number * 10.0 + toFloat(ord(s[i]) - ord('0'))
+    hasdigits = true
+    add(t, s[i])
     inc(i)
     while s[i] == '_': inc(i)
-  # Decimal?
+
+  # Fractional part?
   if s[i] == '.':
-    var hd = 1.0
+    add(t, localeconv().decimal_point)
     inc(i)
     while s[i] in {'0'..'9'}:
-      # Read fractional part
-      flags = flags or 2
-      number = number * 10.0 + toFloat(ord(s[i]) - ord('0'))
-      hd = hd * 10.0
+      hasdigits = true
+      add(t, s[i])
       inc(i)
       while s[i] == '_': inc(i)
-    number = number / hd # this complicated way preserves precision
-  # Again, read integer and fractional part
-  if flags == 0: return 0
+  if not hasdigits:
+    return 0
+
   # Exponent?
   if s[i] in {'e', 'E'}:
+    add(t, s[i])
     inc(i)
-    if s[i] == '+':
-      inc(i)
-    elif s[i] == '-':
-      esign = -1.0
+    if s[i] in {'+', '-'}:
+      add(t, s[i])
       inc(i)
     if s[i] notin {'0'..'9'}:
       return 0
     while s[i] in {'0'..'9'}:
-      exponent = exponent * 10 + ord(s[i]) - ord('0')
+      add(t, s[i])
       inc(i)
       while s[i] == '_': inc(i)
-  # Calculate Exponent
-  let hd = tenToThePowerOf(exponent)
-  if esign > 0.0: number = number * hd
-  else:           number = number / hd
-  # evaluate sign
-  number = number * sign
+  number = strtod(t, nil)
   result = i - start
 
 proc parseFloat*(s: string, number: var float, start = 0): int {.
diff --git a/tests/float/tfloat4.nim b/tests/float/tfloat4.nim
new file mode 100644
index 000000000..960c4e5f7
--- /dev/null
+++ b/tests/float/tfloat4.nim
@@ -0,0 +1,42 @@
+import math, strutils
+
+proc c_sprintf(buf, fmt: cstring) {.importc:"sprintf", header: "<stdio.h>", varargs.}
+
+proc floatToStr(f: float64): string =
+  var buffer: array[128, char]
+  c_sprintf(buffer, "%.16e", f)
+  result = ""
+  for ch in buffer:
+    if ch == '\0':
+      return
+    add(result, ch)
+
+let testFloats = [
+  "0", "-1", "1", "1.", ".3", "3.3", "-.3", "-99.99",
+  "1.1e10", "-2e100", "1.234e-10", "1.234e+10",
+  "-inf", "inf", "+inf",
+  "3.14159265358979323846264338327950288",
+  "1.57079632679489661923132169163975144",
+  "0.785398163397448309615660845819875721",
+  "1.41421356237309504880168872420969808",
+  "0.707106781186547524400844362104849039",
+  "2.71828182845904523536028747135266250",
+  "0.00097656250000000021684043449710088680149056017398834228515625"
+]
+
+for num in testFloats:
+  assert num.parseFloat.floatToStr.parseFloat == num.parseFloat
+
+assert "0".parseFloat == 0.0
+assert "-.1".parseFloat == -0.1
+assert "2.5e1".parseFloat == 25.0
+assert "1e10".parseFloat == 10_000_000_000.0
+assert "0.000_005".parseFloat == 5.000_000e-6
+assert "1.234_567e+2".parseFloat == 123.4567
+assert "1e1_00".parseFloat == "1e100".parseFloat
+assert "3.1415926535897932384626433".parseFloat ==
+       3.1415926535897932384626433
+assert "2.71828182845904523536028747".parseFloat ==
+       2.71828182845904523536028747
+assert 0.00097656250000000021684043449710088680149056017398834228515625 ==
+     "0.00097656250000000021684043449710088680149056017398834228515625".parseFloat