From 127b5a66b1e350ab6a3626a81cd4a7cc7fcaf100 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 7 Dec 2011 10:30:27 -0800 Subject: [PATCH] strconv: faster float conversion - added AppendFloatX benchmarks - 2% to 13% better performance - check for illegal bitSize benchmark old ns/op new ns/op delta strconv_test.BenchmarkFormatFloatDecimal 2993 2733 -8.69% strconv_test.BenchmarkFormatFloat 3384 3141 -7.18% strconv_test.BenchmarkFormatFloatExp 9192 9010 -1.98% strconv_test.BenchmarkFormatFloatBig 3279 3207 -2.20% strconv_test.BenchmarkAppendFloatDecimal 2837 2478 -12.65% strconv_test.BenchmarkAppendFloat 3196 2928 -8.39% strconv_test.BenchmarkAppendFloatExp 9028 8773 -2.82% strconv_test.BenchmarkAppendFloatBig 3151 2782 -11.71% R=rsc, bradfitz CC=golang-dev https://golang.org/cl/5448122 --- src/pkg/strconv/ftoa.go | 160 +++++++++++++++++------------------ src/pkg/strconv/ftoa_test.go | 36 +++++++- src/pkg/strconv/itoa.go | 12 +-- 3 files changed, 116 insertions(+), 92 deletions(-) diff --git a/src/pkg/strconv/ftoa.go b/src/pkg/strconv/ftoa.go index e1ea0a3503..b2413eee6b 100644 --- a/src/pkg/strconv/ftoa.go +++ b/src/pkg/strconv/ftoa.go @@ -45,20 +45,30 @@ var float64info = floatInfo{52, 11, -1023} // Ftoa32(f) is not the same as Ftoa64(float32(f)), // because correct rounding and the number of digits // needed to identify f depend on the precision of the representation. -func FormatFloat(f float64, fmt byte, prec int, n int) string { - if n == 32 { - return genericFtoa(uint64(math.Float32bits(float32(f))), fmt, prec, &float32info) - } - return genericFtoa(math.Float64bits(f), fmt, prec, &float64info) +func FormatFloat(f float64, fmt byte, prec, bitSize int) string { + return string(genericFtoa(make([]byte, 0, 16), f, fmt, prec, bitSize)) } // AppendFloat appends the string form of the floating-point number f, // as generated by FormatFloat, to dst and returns the extended buffer. -func AppendFloat(dst []byte, f float64, fmt byte, prec int, n int) []byte { - return append(dst, FormatFloat(f, fmt, prec, n)...) +func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int) []byte { + return genericFtoa(dst, f, fmt, prec, bitSize) } -func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string { +func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { + var bits uint64 + var flt *floatInfo + switch bitSize { + case 32: + bits = uint64(math.Float32bits(float32(val))) + flt = &float32info + case 64: + bits = math.Float64bits(val) + flt = &float64info + default: + panic("strconv: illegal AppendFloat/FormatFloat bitSize") + } + neg := bits>>(flt.expbits+flt.mantbits) != 0 exp := int(bits>>flt.mantbits) & (1< d.nd { prec = d.nd } - return fmtE(neg, d, prec-1, fmt+'e'-'g') + return fmtE(dst, neg, d, prec-1, fmt+'e'-'g') } if prec > d.dp { prec = d.nd } - return fmtF(neg, d, max(prec-d.dp, 0)) + return fmtF(dst, neg, d, max(prec-d.dp, 0)) } - return "%" + string(fmt) + // unknown format + return append(dst, '%', fmt) } // Round d (= mant * 2^exp) to the shortest number of digits @@ -250,121 +264,103 @@ func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) { } // %e: -d.ddddde±dd -func fmtE(neg bool, d *decimal, prec int, fmt byte) string { - buf := make([]byte, 3+max(prec, 0)+30) // "-0." + prec digits + exp - w := 0 // write index - +func fmtE(dst []byte, neg bool, d *decimal, prec int, fmt byte) []byte { // sign if neg { - buf[w] = '-' - w++ + dst = append(dst, '-') } // first digit - if d.nd == 0 { - buf[w] = '0' - } else { - buf[w] = d.d[0] + ch := byte('0') + if d.nd != 0 { + ch = d.d[0] } - w++ + dst = append(dst, ch) // .moredigits if prec > 0 { - buf[w] = '.' - w++ - for i := 0; i < prec; i++ { - if 1+i < d.nd { - buf[w] = d.d[1+i] - } else { - buf[w] = '0' + dst = append(dst, '.') + for i := 1; i <= prec; i++ { + ch = '0' + if i < d.nd { + ch = d.d[i] } - w++ + dst = append(dst, ch) } } // e± - buf[w] = fmt - w++ + dst = append(dst, fmt) exp := d.dp - 1 if d.nd == 0 { // special case: 0 has exponent 0 exp = 0 } if exp < 0 { - buf[w] = '-' + ch = '-' exp = -exp } else { - buf[w] = '+' + ch = '+' } - w++ + dst = append(dst, ch) // dddd - // count digits - n := 0 - for e := exp; e > 0; e /= 10 { - n++ + var buf [3]byte + i := len(buf) + for exp >= 10 { + i-- + buf[i] = byte(exp%10 + '0') + exp /= 10 } - // leading zeros - for i := n; i < 2; i++ { - buf[w] = '0' - w++ - } - // digits - w += n - n = 0 - for e := exp; e > 0; e /= 10 { - n++ - buf[w-n] = byte(e%10 + '0') + // exp < 10 + i-- + buf[i] = byte(exp + '0') + + // leading zeroes + if i > len(buf)-2 { + i-- + buf[i] = '0' } - return string(buf[0:w]) + return append(dst, buf[i:]...) } // %f: -ddddddd.ddddd -func fmtF(neg bool, d *decimal, prec int) string { - buf := make([]byte, 1+max(d.dp, 1)+1+max(prec, 0)) - w := 0 - +func fmtF(dst []byte, neg bool, d *decimal, prec int) []byte { // sign if neg { - buf[w] = '-' - w++ + dst = append(dst, '-') } // integer, padded with zeros as needed. if d.dp > 0 { var i int for i = 0; i < d.dp && i < d.nd; i++ { - buf[w] = d.d[i] - w++ + dst = append(dst, d.d[i]) } for ; i < d.dp; i++ { - buf[w] = '0' - w++ + dst = append(dst, '0') } } else { - buf[w] = '0' - w++ + dst = append(dst, '0') } // fraction if prec > 0 { - buf[w] = '.' - w++ + dst = append(dst, '.') for i := 0; i < prec; i++ { - if d.dp+i < 0 || d.dp+i >= d.nd { - buf[w] = '0' - } else { - buf[w] = d.d[d.dp+i] + ch := byte('0') + if j := d.dp + i; 0 <= j && j < d.nd { + ch = d.d[j] } - w++ + dst = append(dst, ch) } } - return string(buf[0:w]) + return dst } // %b: -ddddddddp+ddd -func fmtB(neg bool, mant uint64, exp int, flt *floatInfo) string { +func fmtB(dst []byte, neg bool, mant uint64, exp int, flt *floatInfo) []byte { var buf [50]byte w := len(buf) exp -= int(flt.mantbits) @@ -395,7 +391,7 @@ func fmtB(neg bool, mant uint64, exp int, flt *floatInfo) string { w-- buf[w] = '-' } - return string(buf[w:]) + return append(dst, buf[w:]...) } func max(a, b int) int { diff --git a/src/pkg/strconv/ftoa_test.go b/src/pkg/strconv/ftoa_test.go index 02206d5ad2..a6205ac477 100644 --- a/src/pkg/strconv/ftoa_test.go +++ b/src/pkg/strconv/ftoa_test.go @@ -149,26 +149,54 @@ func TestFtoa(t *testing.T) { } } -func BenchmarkFtoa64Decimal(b *testing.B) { +func BenchmarkFormatFloatDecimal(b *testing.B) { for i := 0; i < b.N; i++ { FormatFloat(33909, 'g', -1, 64) } } -func BenchmarkFtoa64Float(b *testing.B) { +func BenchmarkFormatFloat(b *testing.B) { for i := 0; i < b.N; i++ { FormatFloat(339.7784, 'g', -1, 64) } } -func BenchmarkFtoa64FloatExp(b *testing.B) { +func BenchmarkFormatFloatExp(b *testing.B) { for i := 0; i < b.N; i++ { FormatFloat(-5.09e75, 'g', -1, 64) } } -func BenchmarkFtoa64Big(b *testing.B) { +func BenchmarkFormatFloatBig(b *testing.B) { for i := 0; i < b.N; i++ { FormatFloat(123456789123456789123456789, 'g', -1, 64) } } + +func BenchmarkAppendFloatDecimal(b *testing.B) { + dst := make([]byte, 0, 30) + for i := 0; i < b.N; i++ { + AppendFloat(dst, 33909, 'g', -1, 64) + } +} + +func BenchmarkAppendFloat(b *testing.B) { + dst := make([]byte, 0, 30) + for i := 0; i < b.N; i++ { + AppendFloat(dst, 339.7784, 'g', -1, 64) + } +} + +func BenchmarkAppendFloatExp(b *testing.B) { + dst := make([]byte, 0, 30) + for i := 0; i < b.N; i++ { + AppendFloat(dst, -5.09e75, 'g', -1, 64) + } +} + +func BenchmarkAppendFloatBig(b *testing.B) { + dst := make([]byte, 0, 30) + for i := 0; i < b.N; i++ { + AppendFloat(dst, 123456789123456789123456789, 'g', -1, 64) + } +} diff --git a/src/pkg/strconv/itoa.go b/src/pkg/strconv/itoa.go index 65229f704b..821abe0094 100644 --- a/src/pkg/strconv/itoa.go +++ b/src/pkg/strconv/itoa.go @@ -46,21 +46,21 @@ var shifts = [len(digits) + 1]uint{ } // formatBits computes the string representation of u in the given base. -// If negative is set, u is treated as negative int64 value. If append_ -// is set, the string is appended to dst and the resulting byte slice is +// If neg is set, u is treated as negative int64 value. If append_ is +// set, the string is appended to dst and the resulting byte slice is // returned as the first result value; otherwise the string is returned // as the second result value. // -func formatBits(dst []byte, u uint64, base int, negative, append_ bool) (d []byte, s string) { +func formatBits(dst []byte, u uint64, base int, neg, append_ bool) (d []byte, s string) { if base < 2 || base > len(digits) { - panic("invalid base") + panic("strconv: illegal AppendInt/FormatInt base") } // 2 <= base && base <= len(digits) var a [64 + 1]byte // +1 for sign of 64bit value in base 2 i := len(a) - if negative { + if neg { u = -u } @@ -99,7 +99,7 @@ func formatBits(dst []byte, u uint64, base int, negative, append_ bool) (d []byt a[i] = digits[uintptr(u)] // add sign, if any - if negative { + if neg { i-- a[i] = '-' } -- 2.50.0