mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
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:
@@ -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 {.
|
||||
|
||||
42
tests/float/tfloat4.nim
Normal file
42
tests/float/tfloat4.nim
Normal 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
|
||||
Reference in New Issue
Block a user