Fixed parsing of float literals.

Float literals were not parsed properly when their fractional part
exceeded 53 significant bits. This affected in particular math.PI
and math.E. Rather than reinventing the wheel, this patch reuses
C's strtod() implementation, which already does the heavy lifting
with respect to correctness, though some caution is necessary to
keep float parsing locale-independent.
This commit is contained in:
Reimer Behrends 2014-05-23 11:27:39 +02:00
commit 8b8a21cb59
2 changed files with 92 additions and 44 deletions

View file

@ -231,33 +231,43 @@ proc parseInt*(s: string, number: var int, start = 0): int {.
else: else:
number = int(res) 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 {. proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
rtl, extern: "npuParseBiggestFloat", noSideEffect.} = rtl, extern: "npuParseBiggestFloat", noSideEffect.} =
## parses a float starting at `start` and stores the value into `number`. ## 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 ## Result is the number of processed chars or 0 if a parsing error
## 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 var
esign = 1.0
sign = 1.0
i = start i = start
exponent: int sign = 1.0
flags: int t = ""
number = 0.0 hasdigits = false
if s[i] == '+': inc(i)
elif s[i] == '-': # Sign?
if s[i] == '+' or s[i] == '-':
if s[i] == '-':
sign = -1.0 sign = -1.0
add(t, s[i])
inc(i) inc(i)
# NaN?
if s[i] == 'N' or s[i] == 'n': if s[i] == 'N' or s[i] == 'n':
if s[i+1] == 'A' or s[i+1] == 'a': if s[i+1] == 'A' or s[i+1] == 'a':
if s[i+2] == 'N' or s[i+2] == 'n': 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 number = NaN
return i+3 - start return i+3 - start
return 0 return 0
# Inf?
if s[i] == 'I' or s[i] == 'i': if s[i] == 'I' or s[i] == 'i':
if s[i+1] == 'N' or s[i+1] == 'n': if s[i+1] == 'N' or s[i+1] == 'n':
if s[i+2] == 'F' or s[i+2] == 'f': 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 number = Inf*sign
return i+3 - start return i+3 - start
return 0 return 0
# Integer part?
while s[i] in {'0'..'9'}: while s[i] in {'0'..'9'}:
# Read integer part hasdigits = true
flags = flags or 1 add(t, s[i])
number = number * 10.0 + toFloat(ord(s[i]) - ord('0'))
inc(i) inc(i)
while s[i] == '_': inc(i) while s[i] == '_': inc(i)
# Decimal?
# Fractional part?
if s[i] == '.': if s[i] == '.':
var hd = 1.0 add(t, localeconv().decimal_point)
inc(i) inc(i)
while s[i] in {'0'..'9'}: while s[i] in {'0'..'9'}:
# Read fractional part hasdigits = true
flags = flags or 2 add(t, s[i])
number = number * 10.0 + toFloat(ord(s[i]) - ord('0'))
hd = hd * 10.0
inc(i) inc(i)
while s[i] == '_': inc(i) while s[i] == '_': inc(i)
number = number / hd # this complicated way preserves precision if not hasdigits:
# Again, read integer and fractional part return 0
if flags == 0: return 0
# Exponent? # Exponent?
if s[i] in {'e', 'E'}: if s[i] in {'e', 'E'}:
add(t, s[i])
inc(i) inc(i)
if s[i] == '+': if s[i] in {'+', '-'}:
inc(i) add(t, s[i])
elif s[i] == '-':
esign = -1.0
inc(i) inc(i)
if s[i] notin {'0'..'9'}: if s[i] notin {'0'..'9'}:
return 0 return 0
while s[i] in {'0'..'9'}: while s[i] in {'0'..'9'}:
exponent = exponent * 10 + ord(s[i]) - ord('0') add(t, s[i])
inc(i) inc(i)
while s[i] == '_': inc(i) while s[i] == '_': inc(i)
# Calculate Exponent number = strtod(t, nil)
let hd = tenToThePowerOf(exponent)
if esign > 0.0: number = number * hd
else: number = number / hd
# evaluate sign
number = number * sign
result = i - start result = i - start
proc parseFloat*(s: string, number: var float, start = 0): int {. proc parseFloat*(s: string, number: var float, start = 0): int {.

42
tests/float/tfloat4.nim Normal file
View file

@ -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