mirror of
https://github.com/golang/go
synced 2024-11-19 23: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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (d *decimal) atof32() (f float32, ok bool) {
|
||||
// Exact integers are <= 10^7.
|
||||
// Exact powers of ten are <= 10^10.
|
||||
if d.nd > 7 {
|
||||
func atof32exact(mantissa uint64, exp int, neg bool) (f float32, ok bool) {
|
||||
if mantissa>>float32info.mantbits != 0 {
|
||||
return
|
||||
}
|
||||
f = float32(mantissa)
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
switch {
|
||||
case d.dp == d.nd: // int
|
||||
f := d.atof32int()
|
||||
case exp == 0:
|
||||
return f, true
|
||||
|
||||
case d.dp > d.nd && d.dp <= 7+10: // int * 10^k
|
||||
f := d.atof32int()
|
||||
k := d.dp - d.nd
|
||||
// Exact integers are <= 10^7.
|
||||
// Exact powers of ten are <= 10^10.
|
||||
case exp > 0 && exp <= 7+10: // int * 10^k
|
||||
// If exponent is big but number of digits is not,
|
||||
// can move a few zeros into the integer part.
|
||||
if k > 10 {
|
||||
f *= float32pow10[k-10]
|
||||
k = 10
|
||||
if exp > 10 {
|
||||
f *= float32pow10[exp-10]
|
||||
exp = 10
|
||||
}
|
||||
return f * float32pow10[k], true
|
||||
|
||||
case d.dp < d.nd && d.nd-d.dp <= 10: // int / 10^k
|
||||
f := d.atof32int()
|
||||
return f / float32pow10[d.nd-d.dp], true
|
||||
if f > 1e7 || f < -1e7 {
|
||||
// the exponent was really too large.
|
||||
return
|
||||
}
|
||||
return f * float32pow10[exp], true
|
||||
case exp < 0 && exp >= -10: // int / 10^k
|
||||
return f / float32pow10[-exp], true
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -449,15 +451,32 @@ func atof32(s string) (f float32, err error) {
|
||||
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
|
||||
if !d.set(s) {
|
||||
return 0, syntaxError(fnParseFloat, s)
|
||||
}
|
||||
if optimize {
|
||||
if f, ok := d.atof32(); ok {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
b, ovf := d.floatBits(&float32info)
|
||||
f = math.Float32frombits(uint32(b))
|
||||
if ovf {
|
||||
@ -483,8 +502,8 @@ func atof64(s string) (f float64, err error) {
|
||||
}
|
||||
// Try another fast path.
|
||||
ext := new(extFloat)
|
||||
if ok := ext.AssignDecimal(mantissa, exp, neg, trunc); ok {
|
||||
b, ovf := ext.floatBits()
|
||||
if ok := ext.AssignDecimal(mantissa, exp, neg, trunc, &float64info); ok {
|
||||
b, ovf := ext.floatBits(&float64info)
|
||||
f = math.Float64frombits(b)
|
||||
if ovf {
|
||||
err = rangeError(fnParseFloat, s)
|
||||
|
@ -134,6 +134,46 @@ var atoftests = []atofTest{
|
||||
{"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 {
|
||||
x float64
|
||||
s string
|
||||
@ -154,6 +194,12 @@ func init() {
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseFloat("33909", 64)
|
||||
@ -299,3 +387,35 @@ func BenchmarkAtof64RandomFloats(b *testing.B) {
|
||||
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
|
||||
// the extFloat passed as receiver. Overflow is set to true if
|
||||
// the resulting float64 is ±Inf.
|
||||
func (f *extFloat) floatBits() (bits uint64, overflow bool) {
|
||||
flt := &float64info
|
||||
func (f *extFloat) floatBits(flt *floatInfo) (bits uint64, overflow bool) {
|
||||
f.Normalize()
|
||||
|
||||
exp := f.exp + 63
|
||||
@ -140,7 +139,7 @@ func (f *extFloat) floatBits() (bits uint64, overflow bool) {
|
||||
exp += n
|
||||
}
|
||||
|
||||
// Extract 1+flt.mantbits bits.
|
||||
// Extract 1+flt.mantbits bits from the 64-bit mantissa.
|
||||
mant := f.mant >> (63 - flt.mantbits)
|
||||
if f.mant&(1<<(62-flt.mantbits)) != 0 {
|
||||
// Round up.
|
||||
@ -266,8 +265,9 @@ var uint64pow10 = [...]uint64{
|
||||
|
||||
// AssignDecimal sets f to an approximate value mantissa*10^exp. It
|
||||
// returns true if the value represented by f is guaranteed to be the
|
||||
// best approximation of d after being rounded to a float64.
|
||||
func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool) (ok bool) {
|
||||
// best approximation of d after being rounded to a float64 or
|
||||
// float32 depending on flt.
|
||||
func (f *extFloat) AssignDecimal(mantissa uint64, exp10 int, neg bool, trunc bool, flt *floatInfo) (ok bool) {
|
||||
const uint64digits = 19
|
||||
const errorscale = 8
|
||||
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.
|
||||
//
|
||||
// In many cases the approximation will be good enough.
|
||||
const denormalExp = -1023 - 63
|
||||
flt := &float64info
|
||||
denormalExp := flt.bias - 63
|
||||
var extrabits uint
|
||||
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))
|
||||
} else {
|
||||
extrabits = uint(63 - flt.mantbits)
|
||||
|
Loading…
Reference in New Issue
Block a user