Skip to content

Commit 1ff59f3

Browse files
committed
strconv: clean up powers-of-10 table, tests
Both Eisel-Lemire and Ryu depend on a table of truncated 128-bit mantissas of powers of 10, and so will Dragonbox. This CL: - Moves the table to a separate file, so it doesn't look tied to Eisel-Lemire. - Introduces a uint128 type in math.go for the table values, since .Hi and .Lo are clearer than [1] and [0]. - Generates the table from a standalone generator pow10gen.go. - Adds a new pow10 function in math.go to handle table access details. - Factors a 64x128->192-bit multiply into umul192 in math.go. - Moves multiplication by log₁₀ 2 and log₂ 10 into math.go. - Introduces an import_test.go to avoid having to type differently cased names in test code versus regular code. - Introduces named constants for the floating-point size parameters. Previously these were only in the floatInfo global variables. - Changes the BenchmarkAppendUintVarlen subtest names to be more useful. Change-Id: I9826ee5f41c5c19be3b6a7c3c5f277ec6c23b39a Reviewed-on: https://go-review.googlesource.com/c/go/+/712661 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com>
1 parent 7c9fa4d commit 1ff59f3

File tree

14 files changed

+1035
-844
lines changed

14 files changed

+1035
-844
lines changed

src/strconv/atof.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ func atof64exact(mantissa uint64, exp int, neg bool) (f float64, ok bool) {
460460
// If possible to compute mantissa*10^exp to 32-bit float f exactly,
461461
// entirely in floating-point math, do so, avoiding the machinery above.
462462
func atof32exact(mantissa uint64, exp int, neg bool) (f float32, ok bool) {
463-
if mantissa>>float32info.mantbits != 0 {
463+
if mantissa>>float32MantBits != 0 {
464464
return
465465
}
466466
f = float32(mantissa)

src/strconv/atoi_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,11 +501,11 @@ func TestAtoi(t *testing.T) {
501501
}
502502

503503
func bitSizeErrStub(name string, bitSize int) error {
504-
return BitSizeError(name, "0", bitSize)
504+
return bitSizeError(name, "0", bitSize)
505505
}
506506

507507
func baseErrStub(name string, base int) error {
508-
return BaseError(name, "0", base)
508+
return baseError(name, "0", base)
509509
}
510510

511511
func noErrStub(name string, arg int) error {

src/strconv/eisel_lemire.go

Lines changed: 12 additions & 729 deletions
Large diffs are not rendered by default.

src/strconv/export_test.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@
44

55
package strconv
66

7+
type Uint128 = uint128
8+
79
var (
8-
BitSizeError = bitSizeError
9-
BaseError = baseError
10+
BaseError = baseError
11+
BitSizeError = bitSizeError
12+
MulLog10_2 = mulLog10_2
13+
MulLog2_10 = mulLog2_10
14+
ParseFloatPrefix = parseFloatPrefix
15+
Pow10 = pow10
16+
Umul128 = umul128
17+
Umul192 = umul192
1018
)
19+
20+
func NewDecimal(i uint64) *decimal {
21+
d := new(decimal)
22+
d.Assign(i)
23+
return d
24+
}
25+
26+
func SetOptimize(b bool) bool {
27+
old := optimize
28+
optimize = b
29+
return old
30+
}

src/strconv/ftoa.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,25 @@ package strconv
1212

1313
import "math"
1414

15-
// TODO: move elsewhere?
1615
type floatInfo struct {
1716
mantbits uint
1817
expbits uint
1918
bias int
2019
}
2120

22-
var float32info = floatInfo{23, 8, -127}
23-
var float64info = floatInfo{52, 11, -1023}
21+
const (
22+
float32MantBits = 23
23+
float32ExpBits = 8
24+
float32Bias = -127
25+
float64MantBits = 52
26+
float64ExpBits = 11
27+
float64Bias = -1023
28+
)
29+
30+
var (
31+
float32info = floatInfo{float32MantBits, float32ExpBits, float32Bias}
32+
float64info = floatInfo{float64MantBits, float64ExpBits, float64Bias}
33+
)
2434

2535
// FormatFloat converts the floating-point number f to a string,
2636
// according to the format fmt and precision prec. It rounds the

src/strconv/ftoaryu.go

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ func ryuFtoaFixed32(d *decimalSlice, mant uint32, exp int, prec int) {
4040
// mant*(2^e2)*(10^q) >= 10^(prec-1)
4141
// Because mant >= 2^24, it is enough to choose:
4242
// 2^(e2+24) >= 10^(-q+prec-1)
43-
// or q = -mulByLog2Log10(e2+24) + prec - 1
44-
q := -mulByLog2Log10(e2+24) + prec - 1
43+
// or q = -mulLog10_2(e2+24) + prec - 1
44+
q := -mulLog10_2(e2+24) + prec - 1
4545

4646
// Now compute mant*(2^e2)*(10^q).
4747
// Is it an exact computation?
@@ -107,11 +107,11 @@ func ryuFtoaFixed64(d *decimalSlice, mant uint64, exp int, prec int) {
107107
// mant*(2^e2)*(10^q) >= 10^(prec-1)
108108
// Because mant >= 2^54, it is enough to choose:
109109
// 2^(e2+54) >= 10^(-q+prec-1)
110-
// or q = -mulByLog2Log10(e2+54) + prec - 1
110+
// or q = -mulLog10_2(e2+54) + prec - 1
111111
//
112-
// The minimal required exponent is -mulByLog2Log10(1025)+18 = -291
113-
// The maximal required exponent is mulByLog2Log10(1074)+18 = 342
114-
q := -mulByLog2Log10(e2+54) + prec - 1
112+
// The minimal required exponent is -mulLog10_2(1025)+18 = -291
113+
// The maximal required exponent is mulLog10_2(1074)+18 = 342
114+
q := -mulLog10_2(e2+54) + prec - 1
115115

116116
// Now compute mant*(2^e2)*(10^q).
117117
// Is it an exact computation?
@@ -242,7 +242,7 @@ func ryuFtoaShortest(d *decimalSlice, mant uint64, exp int, flt *floatInfo) {
242242
return
243243
}
244244
// Find 10^q *larger* than 2^-e2
245-
q := mulByLog2Log10(-e2) + 1
245+
q := mulLog10_2(-e2) + 1
246246

247247
// We are going to multiply by 10^q using 128-bit arithmetic.
248248
// The exponent is the same for all 3 numbers.
@@ -326,26 +326,6 @@ func ryuFtoaShortest(d *decimalSlice, mant uint64, exp int, flt *floatInfo) {
326326
d.dp -= q
327327
}
328328

329-
// mulByLog2Log10 returns math.Floor(x * log(2)/log(10)) for an integer x in
330-
// the range -1600 <= x && x <= +1600.
331-
//
332-
// The range restriction lets us work in faster integer arithmetic instead of
333-
// slower floating point arithmetic. Correctness is verified by unit tests.
334-
func mulByLog2Log10(x int) int {
335-
// log(2)/log(10) ≈ 0.30102999566 ≈ 78913 / 2^18
336-
return (x * 78913) >> 18
337-
}
338-
339-
// mulByLog10Log2 returns math.Floor(x * log(10)/log(2)) for an integer x in
340-
// the range -500 <= x && x <= +500.
341-
//
342-
// The range restriction lets us work in faster integer arithmetic instead of
343-
// slower floating point arithmetic. Correctness is verified by unit tests.
344-
func mulByLog10Log2(x int) int {
345-
// log(10)/log(2) ≈ 3.32192809489 ≈ 108853 / 2^15
346-
return (x * 108853) >> 15
347-
}
348-
349329
// computeBounds returns a floating-point vector (l, c, u)×2^e2
350330
// where the mantissas are 55-bit (or 26-bit) integers, describing the interval
351331
// represented by the input float64 or float32.
@@ -482,7 +462,7 @@ func ryuDigits32(d *decimalSlice, lower, central, upper uint32,
482462

483463
// mult64bitPow10 takes a floating-point input with a 25-bit
484464
// mantissa and multiplies it with 10^q. The resulting mantissa
485-
// is m*P >> 57 where P is a 64-bit element of the detailedPowersOfTen tables.
465+
// is m*P >> 57 where P is a 64-bit truncated power of 10.
486466
// It is typically 31 or 32-bit wide.
487467
// The returned boolean is true if all trimmed bits were zero.
488468
//
@@ -495,23 +475,23 @@ func mult64bitPow10(m uint32, e2, q int) (resM uint32, resE int, exact bool) {
495475
// P == 1<<63
496476
return m << 6, e2 - 6, true
497477
}
498-
if q < detailedPowersOfTenMinExp10 || detailedPowersOfTenMaxExp10 < q {
478+
pow, exp2, ok := pow10(q)
479+
if !ok {
499480
// This never happens due to the range of float32/float64 exponent
500481
panic("mult64bitPow10: power of 10 is out of range")
501482
}
502-
pow := detailedPowersOfTen[q-detailedPowersOfTenMinExp10][1]
503483
if q < 0 {
504484
// Inverse powers of ten must be rounded up.
505-
pow += 1
485+
pow.Hi++
506486
}
507-
hi, lo := bits.Mul64(uint64(m), pow)
508-
e2 += mulByLog10Log2(q) - 63 + 57
487+
hi, lo := bits.Mul64(uint64(m), pow.Hi)
488+
e2 += exp2 - 63 + 57
509489
return uint32(hi<<7 | lo>>57), e2, lo<<7 == 0
510490
}
511491

512492
// mult128bitPow10 takes a floating-point input with a 55-bit
513493
// mantissa and multiplies it with 10^q. The resulting mantissa
514-
// is m*P >> 119 where P is a 128-bit element of the detailedPowersOfTen tables.
494+
// is m*P >> 119 where P is a 128-bit truncated power of 10.
515495
// It is typically 63 or 64-bit wide.
516496
// The returned boolean is true is all trimmed bits were zero.
517497
//
@@ -524,23 +504,19 @@ func mult128bitPow10(m uint64, e2, q int) (resM uint64, resE int, exact bool) {
524504
// P == 1<<127
525505
return m << 8, e2 - 8, true
526506
}
527-
if q < detailedPowersOfTenMinExp10 || detailedPowersOfTenMaxExp10 < q {
507+
pow, exp2, ok := pow10(q)
508+
if !ok {
528509
// This never happens due to the range of float32/float64 exponent
529510
panic("mult128bitPow10: power of 10 is out of range")
530511
}
531-
pow := detailedPowersOfTen[q-detailedPowersOfTenMinExp10]
532512
if q < 0 {
533513
// Inverse powers of ten must be rounded up.
534-
pow[0] += 1
514+
pow.Lo++
535515
}
536-
e2 += mulByLog10Log2(q) - 127 + 119
516+
e2 += exp2 - 127 + 119
537517

538-
// long multiplication
539-
l1, l0 := bits.Mul64(m, pow[0])
540-
h1, h0 := bits.Mul64(m, pow[1])
541-
mid, carry := bits.Add64(l1, h0, 0)
542-
h1 += carry
543-
return h1<<9 | mid>>55, e2, mid<<9 == 0 && l0 == 0
518+
hi, mid, lo := umul192(m, pow)
519+
return hi<<9 | mid>>55, e2, mid<<9 == 0 && lo == 0
544520
}
545521

546522
func divisibleByPower5(m uint64, k int) bool {

src/strconv/ftoaryu_test.go

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/strconv/import_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package strconv_test
6+
7+
import . "strconv"
8+
9+
type uint128 = Uint128
10+
11+
var (
12+
baseError = BaseError
13+
bitSizeError = BitSizeError
14+
mulLog10_2 = MulLog10_2
15+
mulLog2_10 = MulLog2_10
16+
parseFloatPrefix = ParseFloatPrefix
17+
pow10 = Pow10
18+
umul128 = Umul128
19+
umul192 = Umul192
20+
)

src/strconv/internal_test.go

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/strconv/itoa_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package strconv_test
66

77
import (
8+
"fmt"
89
. "strconv"
910
"testing"
1011
)
@@ -230,7 +231,7 @@ func BenchmarkAppendIntSmall(b *testing.B) {
230231

231232
func BenchmarkAppendUintVarlen(b *testing.B) {
232233
for _, test := range varlenUints {
233-
b.Run(test.out, func(b *testing.B) {
234+
b.Run(fmt.Sprint("digits=", len(test.out)), func(b *testing.B) {
234235
dst := make([]byte, 0, 30)
235236
for j := 0; j < b.N; j++ {
236237
dst = AppendUint(dst[:0], test.in, 10)

0 commit comments

Comments
 (0)