Skip to content

Commit 7fe2a84

Browse files
committed
strconv: remove extfloat.go atof code path
Prior to this commit, strconv.ParseFloat (known in C as atof) takes the first of four algorithms to succeed: atof64exact, eiselLemire64, extFloat, fallback. The Eisel-Lemire implementation is a recent addition but, now that it exists, the extFloat implementation (based on the algorithm used by https://github.com/google/double-conversion) is largely redundant. This Go program: func parseOneMillionFloats(bitSize int, normallyDistributed bool) { rng := rand.New(rand.NewSource(1)) for i := 0; i < 1_000_000; { x := 0.0 if normallyDistributed { x = rng.NormFloat64() } else if bitSize == 32 { x = float64(math.Float32frombits(rng.Uint32())) } else { x = math.Float64frombits( uint64(rng.Uint32())<<32 | uint64(rng.Uint32())) } if math.IsInf(x, 0) { continue } s := strconv.FormatFloat(x, 'g', -1, bitSize) strconv.ParseFloat(s, bitSize) i++ } } triggers the four algorithms by these percentages: bitSize=32, normallyDistributed=false 07.4274% atof32exact 91.2982% eiselLemire32 00.8673% extFloat 00.0269% fallback bitSize=32, normallyDistributed=true 27.6356% atof32exact 72.3641% eiselLemire32 00.0003% extFloat 00.0000% fallback bitSize=64, normallyDistributed=false 01.2076% atof64exact 98.6216% eiselLemire64 00.1081% extFloat 00.0130% fallback bitSize=64, normallyDistributed=true 24.8826% atof64exact 75.1174% eiselLemire64 00.0000% extFloat 00.0000% fallback This commit removes the extfloat.go atof code (but keeps the extfloat.go ftoa code for now), reducing the number of atof algorithms from 4 to 3. The benchmarks (below) show some regressions but these are arguably largely artificial situations. Atof*RandomBits generates uniformly distributed uint32/uint64 values and reinterprets the bits as float32/float64 values. The change in headline numbers (arithmetic means) are primarily due to relatively large changes for relatively rare cases. Atof64Big parses a hard-coded "123456789123456789123456789". name old time/op new time/op delta Atof64Decimal-4 47.1ns ± 1% 47.4ns ± 2% ~ (p=0.516 n=5+5) Atof64Float-4 56.4ns ± 1% 55.9ns ± 2% ~ (p=0.206 n=5+5) Atof64FloatExp-4 68.8ns ± 0% 68.7ns ± 1% ~ (p=0.516 n=5+5) Atof64Big-4 157ns ± 2% 1528ns ± 2% +875.99% (p=0.008 n=5+5) Atof64RandomBits-4 156ns ± 1% 186ns ± 1% +19.49% (p=0.008 n=5+5) Atof64RandomFloats-4 144ns ± 0% 143ns ± 1% ~ (p=0.365 n=5+5) Atof32Decimal-4 47.6ns ± 1% 47.5ns ± 2% ~ (p=0.714 n=5+5) Atof32Float-4 54.3ns ± 2% 54.1ns ± 1% ~ (p=0.532 n=5+5) Atof32FloatExp-4 75.2ns ± 1% 75.7ns ± 3% ~ (p=0.794 n=5+5) Atof32Random-4 108ns ± 1% 120ns ± 1% +10.54% (p=0.008 n=5+5) Fixes #36657 Change-Id: Id3c4e1700f969f885b580be54c8892b4fe042a79 Reviewed-on: https://go-review.googlesource.com/c/go/+/264518 Reviewed-by: Robert Griesemer <gri@golang.org> Trust: Robert Griesemer <gri@golang.org> Trust: Nigel Tao <nigeltao@golang.org>
1 parent 308ec22 commit 7fe2a84

File tree

3 files changed

+18
-176
lines changed

3 files changed

+18
-176
lines changed

src/strconv/atof.go

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -576,24 +576,14 @@ func atof32(s string) (f float32, n int, err error) {
576576
return float32(f), n, err
577577
}
578578

579-
if optimize {
580-
// Try pure floating-point arithmetic conversion.
581-
if !trunc {
582-
if f, ok := atof32exact(mantissa, exp, neg); ok {
583-
return f, n, nil
584-
} else if f, ok = eiselLemire32(mantissa, exp, neg); ok {
585-
return f, n, nil
586-
}
579+
if optimize && !trunc {
580+
// Try pure floating-point arithmetic conversion, and if that fails,
581+
// the Eisel-Lemire algorithm.
582+
if f, ok := atof32exact(mantissa, exp, neg); ok {
583+
return f, n, nil
587584
}
588-
// Try another fast path.
589-
ext := new(extFloat)
590-
if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float32info); ok {
591-
b, ovf := ext.floatBits(&float32info)
592-
f = math.Float32frombits(uint32(b))
593-
if ovf {
594-
err = rangeError(fnParseFloat, s)
595-
}
596-
return f, n, err
585+
if f, ok := eiselLemire32(mantissa, exp, neg); ok {
586+
return f, n, nil
597587
}
598588
}
599589

@@ -625,25 +615,14 @@ func atof64(s string) (f float64, n int, err error) {
625615
return f, n, err
626616
}
627617

628-
if optimize {
618+
if optimize && !trunc {
629619
// Try pure floating-point arithmetic conversion, and if that fails,
630620
// the Eisel-Lemire algorithm.
631-
if !trunc {
632-
if f, ok := atof64exact(mantissa, exp, neg); ok {
633-
return f, n, nil
634-
} else if f, ok = eiselLemire64(mantissa, exp, neg); ok {
635-
return f, n, nil
636-
}
621+
if f, ok := atof64exact(mantissa, exp, neg); ok {
622+
return f, n, nil
637623
}
638-
// Try another fast path.
639-
ext := new(extFloat)
640-
if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float64info); ok {
641-
b, ovf := ext.floatBits(&float64info)
642-
f = math.Float64frombits(b)
643-
if ovf {
644-
err = rangeError(fnParseFloat, s)
645-
}
646-
return f, n, err
624+
if f, ok := eiselLemire64(mantissa, exp, neg); ok {
625+
return f, n, nil
647626
}
648627
}
649628

src/strconv/atof_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ var atoftests = []atofTest{
303303
{"1.00000000000000033306690738754696212708950042724609375", "1.0000000000000004", nil},
304304
{"0x1.00000000000018p0", "1.0000000000000004", nil},
305305

306+
// Halfway between 1090544144181609278303144771584 and 1090544144181609419040633126912
307+
// (15497564393479157p+46, should round to even 15497564393479156p+46, issue 36657)
308+
{"1090544144181609348671888949248", "1.0905441441816093e+30", nil},
309+
// slightly above, rounds up
310+
{"1090544144181609348835077142190", "1.0905441441816094e+30", nil},
311+
306312
// Underscores.
307313
{"1_23.50_0_0e+1_2", "1.235e+14", nil},
308314
{"-_123.5e+12", "0", ErrSyntax},

src/strconv/extfloat.go

Lines changed: 0 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -126,53 +126,6 @@ var powersOfTen = [...]extFloat{
126126
{0xaf87023b9bf0ee6b, 1066, false}, // 10^340
127127
}
128128

129-
// floatBits returns the bits of the float64 that best approximates
130-
// the extFloat passed as receiver. Overflow is set to true if
131-
// the resulting float64 is ±Inf.
132-
func (f *extFloat) floatBits(flt *floatInfo) (bits uint64, overflow bool) {
133-
f.Normalize()
134-
135-
exp := f.exp + 63
136-
137-
// Exponent too small.
138-
if exp < flt.bias+1 {
139-
n := flt.bias + 1 - exp
140-
f.mant >>= uint(n)
141-
exp += n
142-
}
143-
144-
// Extract 1+flt.mantbits bits from the 64-bit mantissa.
145-
mant := f.mant >> (63 - flt.mantbits)
146-
if f.mant&(1<<(62-flt.mantbits)) != 0 {
147-
// Round up.
148-
mant += 1
149-
}
150-
151-
// Rounding might have added a bit; shift down.
152-
if mant == 2<<flt.mantbits {
153-
mant >>= 1
154-
exp++
155-
}
156-
157-
// Infinities.
158-
if exp-flt.bias >= 1<<flt.expbits-1 {
159-
// ±Inf
160-
mant = 0
161-
exp = 1<<flt.expbits - 1 + flt.bias
162-
overflow = true
163-
} else if mant&(1<<flt.mantbits) == 0 {
164-
// Denormalized?
165-
exp = flt.bias
166-
}
167-
// Assemble bits.
168-
bits = mant & (uint64(1)<<flt.mantbits - 1)
169-
bits |= uint64((exp-flt.bias)&(1<<flt.expbits-1)) << flt.mantbits
170-
if f.neg {
171-
bits |= 1 << (flt.mantbits + flt.expbits)
172-
}
173-
return
174-
}
175-
176129
// AssignComputeBounds sets f to the floating point value
177130
// defined by mant, exp and precision given by flt. It returns
178131
// lower, upper such that any number in the closed interval
@@ -225,102 +178,6 @@ var uint64pow10 = [...]uint64{
225178
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
226179
}
227180

228-
// AssignDecimal sets f to an approximate value mantissa*10^exp. It
229-
// reports whether the value represented by f is guaranteed to be the
230-
// best approximation of d after being rounded to a float64 or
231-
// float32 depending on flt.
232-
func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool, flt *floatInfo) (ok bool) {
233-
const uint64digits = 19
234-
235-
// Errors (in the "numerical approximation" sense, not the "Go's error
236-
// type" sense) in this function are measured as multiples of 1/8 of a ULP,
237-
// so that "1/2 of a ULP" can be represented in integer arithmetic.
238-
//
239-
// The C++ double-conversion library also uses this 8x scaling factor:
240-
// https://github.com/google/double-conversion/blob/f4cb2384/double-conversion/strtod.cc#L291
241-
// but this Go implementation has a bug, where it forgets to scale other
242-
// calculations (further below in this function) by the same number. The
243-
// C++ implementation does not forget:
244-
// https://github.com/google/double-conversion/blob/f4cb2384/double-conversion/strtod.cc#L366
245-
//
246-
// Scaling the "errors" in the "is mant_extra in the range (halfway ±
247-
// errors)" check, but not scaling the other values, means that we return
248-
// ok=false (and fall back to a slower atof code path) more often than we
249-
// could. This affects performance but not correctness.
250-
//
251-
// Longer term, we could fix the forgot-to-scale bug (and look carefully
252-
// for correctness regressions; https://codereview.appspot.com/5494068
253-
// landed in 2011), or replace this atof algorithm with a faster one (e.g.
254-
// Ryu). Shorter term, this comment will suffice.
255-
const errorscale = 8
256-
257-
errors := 0 // An upper bound for error, computed in ULP/errorscale.
258-
if trunc {
259-
// the decimal number was truncated.
260-
errors += errorscale / 2
261-
}
262-
263-
f.mant = mantissa
264-
f.exp = 0
265-
f.neg = neg
266-
267-
// Multiply by powers of ten.
268-
i := (exp10 - firstPowerOfTen) / stepPowerOfTen
269-
if exp10 < firstPowerOfTen || i >= len(powersOfTen) {
270-
return false
271-
}
272-
adjExp := (exp10 - firstPowerOfTen) % stepPowerOfTen
273-
274-
// We multiply by exp%step
275-
if adjExp < uint64digits && mantissa < uint64pow10[uint64digits-adjExp] {
276-
// We can multiply the mantissa exactly.
277-
f.mant *= uint64pow10[adjExp]
278-
f.Normalize()
279-
} else {
280-
f.Normalize()
281-
f.Multiply(smallPowersOfTen[adjExp])
282-
errors += errorscale / 2
283-
}
284-
285-
// We multiply by 10 to the exp - exp%step.
286-
f.Multiply(powersOfTen[i])
287-
if errors > 0 {
288-
errors += 1
289-
}
290-
errors += errorscale / 2
291-
292-
// Normalize
293-
shift := f.Normalize()
294-
errors <<= shift
295-
296-
// Now f is a good approximation of the decimal.
297-
// Check whether the error is too large: that is, if the mantissa
298-
// is perturbated by the error, the resulting float64 will change.
299-
// The 64 bits mantissa is 1 + 52 bits for float64 + 11 extra bits.
300-
//
301-
// In many cases the approximation will be good enough.
302-
denormalExp := flt.bias - 63
303-
var extrabits uint
304-
if f.exp <= denormalExp {
305-
// f.mant * 2^f.exp is smaller than 2^(flt.bias+1).
306-
extrabits = 63 - flt.mantbits + 1 + uint(denormalExp-f.exp)
307-
} else {
308-
extrabits = 63 - flt.mantbits
309-
}
310-
311-
halfway := uint64(1) << (extrabits - 1)
312-
mant_extra := f.mant & (1<<extrabits - 1)
313-
314-
// Do a signed comparison here! If the error estimate could make
315-
// the mantissa round differently for the conversion to double,
316-
// then we can't give a definite answer.
317-
if int64(halfway)-int64(errors) < int64(mant_extra) &&
318-
int64(mant_extra) < int64(halfway)+int64(errors) {
319-
return false
320-
}
321-
return true
322-
}
323-
324181
// Frexp10 is an analogue of math.Frexp for decimal powers. It scales
325182
// f by an approximate power of ten 10^-exp, and returns exp10, so
326183
// that f*10^exp10 has the same value as the old f, up to an ulp,

0 commit comments

Comments
 (0)