diff options
-rw-r--r-- | lib/pure/parseutils.nim | 94 | ||||
-rw-r--r-- | tests/float/tfloat4.nim | 42 |
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 |