1
0
mirror of https://github.com/golang/go synced 2024-10-03 00:21:22 -06:00

strconv: extend fast parsing algorithm to ParseFloat(s, 32)

benchmark                  old ns/op    new ns/op    delta
BenchmarkAtof32Decimal           215           73  -65.72%
BenchmarkAtof32Float             233           83  -64.21%
BenchmarkAtof32FloatExp         3351          209  -93.76%
BenchmarkAtof32Random           1939          260  -86.59%

R=rsc
CC=golang-dev, remy
https://golang.org/cl/6294071
This commit is contained in:
Rémy Oudompheng 2012-06-13 23:52:00 +02:00
parent 10b88888f6
commit 5468d16467
3 changed files with 172 additions and 33 deletions

View File

@ -411,33 +411,35 @@ func atof64exact(mantissa uint64, exp int, neg bool) (f float64, ok bool) {
return return
} }
// If possible to convert decimal d to 32-bit float f exactly, // If possible to compute mantissa*10^exp to 32-bit float f exactly,
// entirely in floating-point math, do so, avoiding the machinery above. // entirely in floating-point math, do so, avoiding the machinery above.
func (d *decimal) atof32() (f float32, ok bool) { func atof32exact(mantissa uint64, exp int, neg bool) (f float32, ok bool) {
// Exact integers are <= 10^7. if mantissa>>float32info.mantbits != 0 {
// Exact powers of ten are <= 10^10.
if d.nd > 7 {
return return
} }
f = float32(mantissa)
if neg {
f = -f
}
switch { switch {
case d.dp == d.nd: // int case exp == 0:
f := d.atof32int()
return f, true return f, true
// Exact integers are <= 10^7.
case d.dp > d.nd && d.dp <= 7+10: // int * 10^k // Exact powers of ten are <= 10^10.
f := d.atof32int() case exp > 0 && exp <= 7+10: // int * 10^k
k := d.dp - d.nd
// If exponent is big but number of digits is not, // If exponent is big but number of digits is not,
// can move a few zeros into the integer part. // can move a few zeros into the integer part.
if k > 10 { if exp > 10 {
f *= float32pow10[k-10] f *= float32pow10[exp-10]
k = 10 exp = 10
} }
return f * float32pow10[k], true if f > 1e7 || f < -1e7 {
// the exponent was really too large.
case d.dp < d.nd && d.nd-d.dp <= 10: // int / 10^k return
f := d.atof32int() }
return f / float32pow10[d.nd-d.dp], true return f * float32pow10[exp], true
case exp < 0 && exp >= -10: // int / 10^k
return f / float32pow10[-exp], true
} }
return return
} }
@ -449,15 +451,32 @@ func atof32(s string) (f float32, err error) {
return float32(val), nil return float32(val), nil
} }
if optimize {
// Parse mantissa and exponent.
mantissa, exp, neg, trunc, ok := readFloat(s)
if ok {
// Try pure floating-point arithmetic conversion.
if !trunc {
if f, ok := atof32exact(mantissa, exp, neg); ok {
return f, nil
}
}
// Try another fast path.
ext := new(extFloat)
if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float32info); ok {
b, ovf := ext.floatBits(&float32info)
f = math.Float32frombits(uint32(b))
if ovf {
err = rangeError(fnParseFloat, s)
}
return f, err
}
}
}
var d decimal var d decimal
if !d.set(s) { if !d.set(s) {
return 0, syntaxError(fnParseFloat, s) return 0, syntaxError(fnParseFloat, s)
} }
if optimize {
if f, ok := d.atof32(); ok {
return f, nil
}
}
b, ovf := d.floatBits(&float32info) b, ovf := d.floatBits(&float32info)
f = math.Float32frombits(uint32(b)) f = math.Float32frombits(uint32(b))
if ovf { if ovf {
@ -483,8 +502,8 @@ func atof64(s string) (f float64, err error) {
} }
// Try another fast path. // Try another fast path.
ext := new(extFloat) ext := new(extFloat)
if ok := ext.AssignDecimal(mantissa, exp, neg, trunc); ok { if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float64info); ok {
b, ovf := ext.floatBits() b, ovf := ext.floatBits(&float64info)
f = math.Float64frombits(b) f = math.Float64frombits(b)
if ovf { if ovf {
err = rangeError(fnParseFloat, s) err = rangeError(fnParseFloat, s)

View File

@ -134,6 +134,46 @@ var atoftests = []atofTest{
{"1.00000000000000011102230246251565404236316680908203125" + strings.Repeat("0", 10000) + "1", "1.0000000000000002", nil}, {"1.00000000000000011102230246251565404236316680908203125" + strings.Repeat("0", 10000) + "1", "1.0000000000000002", nil},
} }
var atof32tests = []atofTest{
// Exactly halfway between 1 and the next float32.
// Round to even (down).
{"1.000000059604644775390625", "1", nil},
// Slightly lower.
{"1.000000059604644775390624", "1", nil},
// Slightly higher.
{"1.000000059604644775390626", "1.0000001", nil},
// Slightly higher, but you have to read all the way to the end.
{"1.000000059604644775390625" + strings.Repeat("0", 10000) + "1", "1.0000001", nil},
// largest float32: (1<<128) * (1 - 2^-24)
{"340282346638528859811704183484516925440", "3.4028235e+38", nil},
{"-340282346638528859811704183484516925440", "-3.4028235e+38", nil},
// next float32 - too large
{"3.4028236e38", "+Inf", ErrRange},
{"-3.4028236e38", "-Inf", ErrRange},
// the border is 3.40282356779...e+38
// borderline - okay
{"3.402823567e38", "3.4028235e+38", nil},
{"-3.402823567e38", "-3.4028235e+38", nil},
// borderline - too large
{"3.4028235678e38", "+Inf", ErrRange},
{"-3.4028235678e38", "-Inf", ErrRange},
// Denormals: less than 2^-126
{"1e-38", "1e-38", nil},
{"1e-39", "1e-39", nil},
{"1e-40", "1e-40", nil},
{"1e-41", "1e-41", nil},
{"1e-42", "1e-42", nil},
{"1e-43", "1e-43", nil},
{"1e-44", "1e-44", nil},
{"6e-45", "6e-45", nil}, // 4p-149 = 5.6e-45
{"5e-45", "6e-45", nil},
// Smallest denormal
{"1e-45", "1e-45", nil}, // 1p-149 = 1.4e-45
{"2e-45", "1e-45", nil},
}
type atofSimpleTest struct { type atofSimpleTest struct {
x float64 x float64
s string s string
@ -154,6 +194,12 @@ func init() {
test.err = &NumError{"ParseFloat", test.in, test.err} test.err = &NumError{"ParseFloat", test.in, test.err}
} }
} }
for i := range atof32tests {
test := &atof32tests[i]
if test.err != nil {
test.err = &NumError{"ParseFloat", test.in, test.err}
}
}
// Generate random inputs for tests and benchmarks // Generate random inputs for tests and benchmarks
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
@ -206,6 +252,19 @@ func testAtof(t *testing.T, opt bool) {
} }
} }
} }
for _, test := range atof32tests {
out, err := ParseFloat(test.in, 32)
out32 := float32(out)
if float64(out32) != out {
t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32))
continue
}
outs := FormatFloat(float64(out32), 'g', -1, 32)
if outs != test.out || !reflect.DeepEqual(err, test.err) {
t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v",
test.in, out32, err, test.out, test.err, out)
}
}
SetOptimize(oldopt) SetOptimize(oldopt)
} }
@ -264,6 +323,35 @@ func TestRoundTrip(t *testing.T) {
} }
} }
// TestRoundTrip32 tries a fraction of all finite positive float32 values.
func TestRoundTrip32(t *testing.T) {
step := uint32(997)
if testing.Short() {
step = 99991
}
count := 0
for i := uint32(0); i < 0xff<<23; i += step {
f := math.Float32frombits(i)
if i&1 == 1 {
f = -f // negative
}
s := FormatFloat(float64(f), 'g', -1, 32)
parsed, err := ParseFloat(s, 32)
parsed32 := float32(parsed)
switch {
case err != nil:
t.Errorf("ParseFloat(%q, 32) gave error %s", s, err)
case float64(parsed32) != parsed:
t.Errorf("ParseFloat(%q, 32) = %v, not a float32 (nearest is %v)", s, parsed, parsed32)
case parsed32 != f:
t.Errorf("ParseFloat(%q, 32) = %b (expected %b)", s, parsed32, f)
}
count++
}
t.Logf("tested %d float32's", count)
}
func BenchmarkAtof64Decimal(b *testing.B) { func BenchmarkAtof64Decimal(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
ParseFloat("33909", 64) ParseFloat("33909", 64)
@ -299,3 +387,35 @@ func BenchmarkAtof64RandomFloats(b *testing.B) {
ParseFloat(benchmarksRandomNormal[i%1024], 64) ParseFloat(benchmarksRandomNormal[i%1024], 64)
} }
} }
func BenchmarkAtof32Decimal(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseFloat("33909", 32)
}
}
func BenchmarkAtof32Float(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseFloat("339.778", 32)
}
}
func BenchmarkAtof32FloatExp(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseFloat("12.3456e32", 32)
}
}
var float32strings [4096]string
func BenchmarkAtof32Random(b *testing.B) {
n := uint32(997)
for i := range float32strings {
n = (99991*n + 42) % (0xff << 23)
float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
ParseFloat(float32strings[i%4096], 32)
}
}

View File

@ -127,8 +127,7 @@ var powersOfTen = [...]extFloat{
// floatBits returns the bits of the float64 that best approximates // floatBits returns the bits of the float64 that best approximates
// the extFloat passed as receiver. Overflow is set to true if // the extFloat passed as receiver. Overflow is set to true if
// the resulting float64 is ±Inf. // the resulting float64 is ±Inf.
func (f *extFloat) floatBits() (bits uint64, overflow bool) { func (f *extFloat) floatBits(flt *floatInfo) (bits uint64, overflow bool) {
flt := &float64info
f.Normalize() f.Normalize()
exp := f.exp + 63 exp := f.exp + 63
@ -140,7 +139,7 @@ func (f *extFloat) floatBits() (bits uint64, overflow bool) {
exp += n exp += n
} }
// Extract 1+flt.mantbits bits. // Extract 1+flt.mantbits bits from the 64-bit mantissa.
mant := f.mant >> (63 - flt.mantbits) mant := f.mant >> (63 - flt.mantbits)
if f.mant&(1<<(62-flt.mantbits)) != 0 { if f.mant&(1<<(62-flt.mantbits)) != 0 {
// Round up. // Round up.
@ -266,8 +265,9 @@ var uint64pow10 = [...]uint64{
// AssignDecimal sets f to an approximate value mantissa*10^exp. It // AssignDecimal sets f to an approximate value mantissa*10^exp. It
// returns true if the value represented by f is guaranteed to be the // returns true if the value represented by f is guaranteed to be the
// best approximation of d after being rounded to a float64. // best approximation of d after being rounded to a float64 or
func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool) (ok bool) { // float32 depending on flt.
func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool, flt *floatInfo) (ok bool) {
const uint64digits = 19 const uint64digits = 19
const errorscale = 8 const errorscale = 8
errors := 0 // An upper bound for error, computed in errorscale*ulp. errors := 0 // An upper bound for error, computed in errorscale*ulp.
@ -315,10 +315,10 @@ func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc boo
// The 64 bits mantissa is 1 + 52 bits for float64 + 11 extra bits. // The 64 bits mantissa is 1 + 52 bits for float64 + 11 extra bits.
// //
// In many cases the approximation will be good enough. // In many cases the approximation will be good enough.
const denormalExp = -1023 - 63 denormalExp := flt.bias - 63
flt := &float64info
var extrabits uint var extrabits uint
if f.exp <= denormalExp { if f.exp <= denormalExp {
// f.mant * 2^f.exp is smaller than 2^(flt.bias+1).
extrabits = uint(63 - flt.mantbits + 1 + uint(denormalExp-f.exp)) extrabits = uint(63 - flt.mantbits + 1 + uint(denormalExp-f.exp))
} else { } else {
extrabits = uint(63 - flt.mantbits) extrabits = uint(63 - flt.mantbits)