Commit ff03482f authored by Rémy Oudompheng's avatar Rémy Oudompheng

strconv: speedup AppendFloat/FormatFloat.

The improvement is obtained by eliminating the zero
initialization of a large structure that is only
needed when the fast path fails.

Also add a missing roundtrip test for float32s.

benchmark                              old ns/op    new ns/op    delta
BenchmarkAppendFloatDecimal                  301          180  -40.20%
BenchmarkAppendFloat                         486          388  -20.16%
BenchmarkAppendFloatExp                      492          383  -22.15%
BenchmarkAppendFloatNegExp                   478          370  -22.59%
BenchmarkAppendFloatBig                      650          541  -16.77%
BenchmarkAppendFloat32Integer                308          180  -41.56%
BenchmarkAppendFloat32ExactFraction          449          333  -25.84%
BenchmarkAppendFloat32Point                  494          390  -21.05%
BenchmarkAppendFloat32Exp                    488          387  -20.70%
BenchmarkAppendFloat32NegExp                 488          378  -22.54%

R=r, rsc
CC=golang-dev, remy
https://golang.org/cl/6346081
parent 74db9d29
...@@ -172,6 +172,14 @@ var atof32tests = []atofTest{ ...@@ -172,6 +172,14 @@ var atof32tests = []atofTest{
// Smallest denormal // Smallest denormal
{"1e-45", "1e-45", nil}, // 1p-149 = 1.4e-45 {"1e-45", "1e-45", nil}, // 1p-149 = 1.4e-45
{"2e-45", "1e-45", nil}, {"2e-45", "1e-45", nil},
// 2^92 = 8388608p+69 = 4951760157141521099596496896 (4.9517602e27)
// is an exact power of two that needs 8 decimal digits to be correctly
// parsed back.
// The float32 before is 16777215p+68 = 4.95175986e+27
// The halfway is 4.951760009. A bad algorithm that thinks the previous
// float32 is 8388607p+69 will shorten incorrectly to 4.95176e+27.
{"4951760157141521099596496896", "4.9517602e+27", nil},
} }
type atofSimpleTest struct { type atofSimpleTest struct {
......
...@@ -396,7 +396,7 @@ func frexp10Many(expMin, expMax int, a, b, c *extFloat) (exp10 int) { ...@@ -396,7 +396,7 @@ func frexp10Many(expMin, expMax int, a, b, c *extFloat) (exp10 int) {
// which belongs to the open interval (lower, upper), where f is supposed // which belongs to the open interval (lower, upper), where f is supposed
// to lie. It returns false whenever the result is unsure. The implementation // to lie. It returns false whenever the result is unsure. The implementation
// uses the Grisu3 algorithm. // uses the Grisu3 algorithm.
func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool { func (f *extFloat) ShortestDecimal(d *decimalSlice, lower, upper *extFloat) bool {
if f.mant == 0 { if f.mant == 0 {
d.d[0] = '0' d.d[0] = '0'
d.nd = 1 d.nd = 1
...@@ -405,7 +405,26 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool { ...@@ -405,7 +405,26 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool {
} }
if f.exp == 0 && *lower == *f && *lower == *upper { if f.exp == 0 && *lower == *f && *lower == *upper {
// an exact integer. // an exact integer.
d.Assign(f.mant) var buf [24]byte
n := len(buf) - 1
for v := f.mant; v > 0; {
v1 := v / 10
v -= 10 * v1
buf[n] = byte(v + '0')
n--
v = v1
}
nd := len(buf) - n - 1
for i := 0; i < nd; i++ {
d.d[i] = buf[n+1+i]
}
d.nd, d.dp = nd, nd
for d.nd > 0 && d.d[d.nd-1] == '0' {
d.nd--
}
if d.nd == 0 {
d.dp = 0
}
d.neg = f.neg d.neg = f.neg
return true return true
} }
...@@ -491,7 +510,7 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool { ...@@ -491,7 +510,7 @@ func (f *extFloat) ShortestDecimal(d *decimal, lower, upper *extFloat) bool {
// d = x-targetDiff*ε, without becoming smaller than x-maxDiff*ε. // d = x-targetDiff*ε, without becoming smaller than x-maxDiff*ε.
// It assumes that a decimal digit is worth ulpDecimal*ε, and that // It assumes that a decimal digit is worth ulpDecimal*ε, and that
// all data is known with a error estimate of ulpBinary*ε. // all data is known with a error estimate of ulpBinary*ε.
func adjustLastDigit(d *decimal, currentDiff, targetDiff, maxDiff, ulpDecimal, ulpBinary uint64) bool { func adjustLastDigit(d *decimalSlice, currentDiff, targetDiff, maxDiff, ulpDecimal, ulpBinary uint64) bool {
if ulpDecimal < 2*ulpBinary { if ulpDecimal < 2*ulpBinary {
// Approximation is too wide. // Approximation is too wide.
return false return false
......
...@@ -101,37 +101,42 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { ...@@ -101,37 +101,42 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
// Negative precision means "only as much as needed to be exact." // Negative precision means "only as much as needed to be exact."
shortest := prec < 0 shortest := prec < 0
d := new(decimal) var digs decimalSlice
if shortest { if shortest {
ok := false ok := false
if optimize { if optimize {
// Try Grisu3 algorithm. // Try Grisu3 algorithm.
f := new(extFloat) f := new(extFloat)
lower, upper := f.AssignComputeBounds(mant, exp, neg, flt) lower, upper := f.AssignComputeBounds(mant, exp, neg, flt)
ok = f.ShortestDecimal(d, &lower, &upper) var buf [32]byte
digs.d = buf[:]
ok = f.ShortestDecimal(&digs, &lower, &upper)
} }
if !ok { if !ok {
// Create exact decimal representation. // Create exact decimal representation.
// The shift is exp - flt.mantbits because mant is a 1-bit integer // The shift is exp - flt.mantbits because mant is a 1-bit integer
// followed by a flt.mantbits fraction, and we are treating it as // followed by a flt.mantbits fraction, and we are treating it as
// a 1+flt.mantbits-bit integer. // a 1+flt.mantbits-bit integer.
d := new(decimal)
d.Assign(mant) d.Assign(mant)
d.Shift(exp - int(flt.mantbits)) d.Shift(exp - int(flt.mantbits))
roundShortest(d, mant, exp, flt) roundShortest(d, mant, exp, flt)
digs = decimalSlice{d: d.d[:], nd: d.nd, dp: d.dp}
} }
// Precision for shortest representation mode. // Precision for shortest representation mode.
if prec < 0 { if prec < 0 {
switch fmt { switch fmt {
case 'e', 'E': case 'e', 'E':
prec = d.nd - 1 prec = digs.nd - 1
case 'f': case 'f':
prec = max(d.nd-d.dp, 0) prec = max(digs.nd-digs.dp, 0)
case 'g', 'G': case 'g', 'G':
prec = d.nd prec = digs.nd
} }
} }
} else { } else {
// Create exact decimal representation. // Create exact decimal representation.
d := new(decimal)
d.Assign(mant) d.Assign(mant)
d.Shift(exp - int(flt.mantbits)) d.Shift(exp - int(flt.mantbits))
// Round appropriately. // Round appropriately.
...@@ -146,18 +151,19 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { ...@@ -146,18 +151,19 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
} }
d.Round(prec) d.Round(prec)
} }
digs = decimalSlice{d: d.d[:], nd: d.nd, dp: d.dp}
} }
switch fmt { switch fmt {
case 'e', 'E': case 'e', 'E':
return fmtE(dst, neg, d, prec, fmt) return fmtE(dst, neg, digs, prec, fmt)
case 'f': case 'f':
return fmtF(dst, neg, d, prec) return fmtF(dst, neg, digs, prec)
case 'g', 'G': case 'g', 'G':
// trailing fractional zeros in 'e' form will be trimmed. // trailing fractional zeros in 'e' form will be trimmed.
eprec := prec eprec := prec
if eprec > d.nd && d.nd >= d.dp { if eprec > digs.nd && digs.nd >= digs.dp {
eprec = d.nd eprec = digs.nd
} }
// %e is used if the exponent from the conversion // %e is used if the exponent from the conversion
// is less than -4 or greater than or equal to the precision. // is less than -4 or greater than or equal to the precision.
...@@ -165,17 +171,17 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { ...@@ -165,17 +171,17 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
if shortest { if shortest {
eprec = 6 eprec = 6
} }
exp := d.dp - 1 exp := digs.dp - 1
if exp < -4 || exp >= eprec { if exp < -4 || exp >= eprec {
if prec > d.nd { if prec > digs.nd {
prec = d.nd prec = digs.nd
} }
return fmtE(dst, neg, d, prec-1, fmt+'e'-'g') return fmtE(dst, neg, digs, prec-1, fmt+'e'-'g')
} }
if prec > d.dp { if prec > digs.dp {
prec = d.nd prec = digs.nd
} }
return fmtF(dst, neg, d, max(prec-d.dp, 0)) return fmtF(dst, neg, digs, max(prec-digs.dp, 0))
} }
// unknown format // unknown format
...@@ -283,8 +289,14 @@ func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) { ...@@ -283,8 +289,14 @@ func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
} }
} }
type decimalSlice struct {
d []byte
nd, dp int
neg bool
}
// %e: -d.ddddde±dd // %e: -d.ddddde±dd
func fmtE(dst []byte, neg bool, d *decimal, prec int, fmt byte) []byte { func fmtE(dst []byte, neg bool, d decimalSlice, prec int, fmt byte) []byte {
// sign // sign
if neg { if neg {
dst = append(dst, '-') dst = append(dst, '-')
...@@ -345,7 +357,7 @@ func fmtE(dst []byte, neg bool, d *decimal, prec int, fmt byte) []byte { ...@@ -345,7 +357,7 @@ func fmtE(dst []byte, neg bool, d *decimal, prec int, fmt byte) []byte {
} }
// %f: -ddddddd.ddddd // %f: -ddddddd.ddddd
func fmtF(dst []byte, neg bool, d *decimal, prec int) []byte { func fmtF(dst []byte, neg bool, d decimalSlice, prec int) []byte {
// sign // sign
if neg { if neg {
dst = append(dst, '-') dst = append(dst, '-')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment