]> Cypherpunks repositories - gostls13.git/commitdiff
strconv: make Eisel-Lemire handle long mantissas
authorRémy Oudompheng <remyoudompheng@gmail.com>
Fri, 23 Oct 2020 20:23:21 +0000 (22:23 +0200)
committerNigel Tao <nigeltao@golang.org>
Thu, 29 Oct 2020 22:44:49 +0000 (22:44 +0000)
In many cases, it is not necessary to parse long
decimal mantissas entirely to produce the correctly
rounded floating-point number. It is enough to parse
the short, rounded lower and upper bounds and in most cases
they round to the same floating point number because uint64
can hold 19 digits.

Previously this case was handled by the extFloat code path
(Grisu3 algorithm).

name                      old time/op  new time/op  delta
Atof64Big-4               1.07µs ± 2%  0.11µs ± 2%  -89.61%  (p=0.000 n=10+9)
Atof64RandomLongFloats-4  8.03µs ± 2%  0.14µs ± 7%  -98.24%  (p=0.000 n=10+10)
Atof32RandomLong-4         760ns ± 1%   156ns ± 0%  -79.46%  (p=0.000 n=10+8)

Benchmarks versus extFloat:

name                      old time/op  new time/op  delta
Atof64Big-4                121ns ± 3%   111ns ± 2%   -7.93%  (p=0.000 n=10+9)
Atof64RandomLongFloats-4   144ns ± 1%   142ns ± 7%     ~     (p=0.167 n=10+10)
Atof32RandomLong-4         129ns ± 1%   156ns ± 0%  +21.12%  (p=0.000 n=10+8)

Change-Id: Id734b8c11e74b49a444fda67ee72870ae9422e60
Reviewed-on: https://go-review.googlesource.com/c/go/+/264677
Trust: Emmanuel Odeke <emmanuel@orijtech.com>
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
src/strconv/atof.go
src/strconv/atof_test.go

index c0385170cb3e1baa4b0558d95ff542c9a16f69d9..9010a66ca8910bff4f3f886f3649aaab9edbcb59 100644 (file)
@@ -576,14 +576,26 @@ func atof32(s string) (f float32, n int, err error) {
                return float32(f), n, err
        }
 
-       if optimize && !trunc {
+       if optimize {
                // Try pure floating-point arithmetic conversion, and if that fails,
                // the Eisel-Lemire algorithm.
-               if f, ok := atof32exact(mantissa, exp, neg); ok {
-                       return f, n, nil
+               if !trunc {
+                       if f, ok := atof32exact(mantissa, exp, neg); ok {
+                               return f, n, nil
+                       }
                }
-               if f, ok := eiselLemire32(mantissa, exp, neg); ok {
-                       return f, n, nil
+               f, ok := eiselLemire32(mantissa, exp, neg)
+               if ok {
+                       if !trunc {
+                               return f, n, nil
+                       }
+                       // Even if the mantissa was truncated, we may
+                       // have found the correct result. Confirm by
+                       // converting the upper mantissa bound.
+                       fUp, ok := eiselLemire32(mantissa+1, exp, neg)
+                       if ok && f == fUp {
+                               return f, n, nil
+                       }
                }
        }
 
@@ -615,14 +627,26 @@ func atof64(s string) (f float64, n int, err error) {
                return f, n, err
        }
 
-       if optimize && !trunc {
+       if optimize {
                // Try pure floating-point arithmetic conversion, and if that fails,
                // the Eisel-Lemire algorithm.
-               if f, ok := atof64exact(mantissa, exp, neg); ok {
-                       return f, n, nil
+               if !trunc {
+                       if f, ok := atof64exact(mantissa, exp, neg); ok {
+                               return f, n, nil
+                       }
                }
-               if f, ok := eiselLemire64(mantissa, exp, neg); ok {
-                       return f, n, nil
+               f, ok := eiselLemire64(mantissa, exp, neg)
+               if ok {
+                       if !trunc {
+                               return f, n, nil
+                       }
+                       // Even if the mantissa was truncated, we may
+                       // have found the correct result. Confirm by
+                       // converting the upper mantissa bound.
+                       fUp, ok := eiselLemire64(mantissa+1, exp, neg)
+                       if ok && f == fUp {
+                               return f, n, nil
+                       }
                }
        }
 
index 25ec1a9a514de5a065370df62ea777c3d785f309..5a6fec8d3ba0601550a50c94cb8411cdbda4b802 100644 (file)
@@ -674,6 +674,23 @@ func BenchmarkAtof64RandomFloats(b *testing.B) {
        }
 }
 
+func BenchmarkAtof64RandomLongFloats(b *testing.B) {
+       initAtof()
+       samples := make([]string, len(atofRandomTests))
+       for i, t := range atofRandomTests {
+               samples[i] = FormatFloat(t.x, 'g', 20, 64)
+       }
+       b.ResetTimer()
+       idx := 0
+       for i := 0; i < b.N; i++ {
+               ParseFloat(samples[idx], 64)
+               idx++
+               if idx == len(samples) {
+                       idx = 0
+               }
+       }
+}
+
 func BenchmarkAtof32Decimal(b *testing.B) {
        for i := 0; i < b.N; i++ {
                ParseFloat("33909", 32)
@@ -692,10 +709,9 @@ func BenchmarkAtof32FloatExp(b *testing.B) {
        }
 }
 
-var float32strings [4096]string
-
 func BenchmarkAtof32Random(b *testing.B) {
        n := uint32(997)
+       var float32strings [4096]string
        for i := range float32strings {
                n = (99991*n + 42) % (0xff << 23)
                float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32)
@@ -705,3 +721,16 @@ func BenchmarkAtof32Random(b *testing.B) {
                ParseFloat(float32strings[i%4096], 32)
        }
 }
+
+func BenchmarkAtof32RandomLong(b *testing.B) {
+       n := uint32(997)
+       var float32strings [4096]string
+       for i := range float32strings {
+               n = (99991*n + 42) % (0xff << 23)
+               float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32)
+       }
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               ParseFloat(float32strings[i%4096], 32)
+       }
+}