mirror of
https://github.com/golang/go
synced 2024-11-20 01:04:40 -07: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:
parent
10b88888f6
commit
5468d16467
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user