Commit 127b5a66 authored by Robert Griesemer's avatar Robert Griesemer

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
parent b955bbfb
...@@ -45,20 +45,30 @@ var float64info = floatInfo{52, 11, -1023} ...@@ -45,20 +45,30 @@ var float64info = floatInfo{52, 11, -1023}
// Ftoa32(f) is not the same as Ftoa64(float32(f)), // Ftoa32(f) is not the same as Ftoa64(float32(f)),
// because correct rounding and the number of digits // because correct rounding and the number of digits
// needed to identify f depend on the precision of the representation. // needed to identify f depend on the precision of the representation.
func FormatFloat(f float64, fmt byte, prec int, n int) string { func FormatFloat(f float64, fmt byte, prec, bitSize int) string {
if n == 32 { return string(genericFtoa(make([]byte, 0, 16), f, fmt, prec, bitSize))
return genericFtoa(uint64(math.Float32bits(float32(f))), fmt, prec, &float32info)
}
return genericFtoa(math.Float64bits(f), fmt, prec, &float64info)
} }
// AppendFloat appends the string form of the floating-point number f, // AppendFloat appends the string form of the floating-point number f,
// as generated by FormatFloat, to dst and returns the extended buffer. // as generated by FormatFloat, to dst and returns the extended buffer.
func AppendFloat(dst []byte, f float64, fmt byte, prec int, n int) []byte { func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int) []byte {
return append(dst, FormatFloat(f, fmt, prec, n)...) 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 neg := bits>>(flt.expbits+flt.mantbits) != 0
exp := int(bits>>flt.mantbits) & (1<<flt.expbits - 1) exp := int(bits>>flt.mantbits) & (1<<flt.expbits - 1)
mant := bits & (uint64(1)<<flt.mantbits - 1) mant := bits & (uint64(1)<<flt.mantbits - 1)
...@@ -66,13 +76,16 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string { ...@@ -66,13 +76,16 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
switch exp { switch exp {
case 1<<flt.expbits - 1: case 1<<flt.expbits - 1:
// Inf, NaN // Inf, NaN
if mant != 0 { var s string
return "NaN" switch {
} case mant != 0:
if neg { s = "NaN"
return "-Inf" case neg:
s = "-Inf"
default:
s = "+Inf"
} }
return "+Inf" return append(dst, s...)
case 0: case 0:
// denormalized // denormalized
...@@ -86,7 +99,7 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string { ...@@ -86,7 +99,7 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
// Pick off easy binary format. // Pick off easy binary format.
if fmt == 'b' { if fmt == 'b' {
return fmtB(neg, mant, exp, flt) return fmtB(dst, neg, mant, exp, flt)
} }
// Create exact decimal representation. // Create exact decimal representation.
...@@ -127,9 +140,9 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string { ...@@ -127,9 +140,9 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
switch fmt { switch fmt {
case 'e', 'E': case 'e', 'E':
return fmtE(neg, d, prec, fmt) return fmtE(dst, neg, d, prec, fmt)
case 'f': case 'f':
return fmtF(neg, d, prec) return fmtF(dst, neg, d, 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
...@@ -147,15 +160,16 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string { ...@@ -147,15 +160,16 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
if prec > d.nd { if prec > d.nd {
prec = 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 { if prec > d.dp {
prec = d.nd 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 // 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) { ...@@ -250,121 +264,103 @@ func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
} }
// %e: -d.ddddde±dd // %e: -d.ddddde±dd
func fmtE(neg bool, d *decimal, prec int, fmt byte) string { func fmtE(dst []byte, neg bool, d *decimal, prec int, fmt byte) []byte {
buf := make([]byte, 3+max(prec, 0)+30) // "-0." + prec digits + exp
w := 0 // write index
// sign // sign
if neg { if neg {
buf[w] = '-' dst = append(dst, '-')
w++
} }
// first digit // first digit
if d.nd == 0 { ch := byte('0')
buf[w] = '0' if d.nd != 0 {
} else { ch = d.d[0]
buf[w] = d.d[0]
} }
w++ dst = append(dst, ch)
// .moredigits // .moredigits
if prec > 0 { if prec > 0 {
buf[w] = '.' dst = append(dst, '.')
w++ for i := 1; i <= prec; i++ {
for i := 0; i < prec; i++ { ch = '0'
if 1+i < d.nd { if i < d.nd {
buf[w] = d.d[1+i] ch = d.d[i]
} else {
buf[w] = '0'
} }
w++ dst = append(dst, ch)
} }
} }
// e± // e±
buf[w] = fmt dst = append(dst, fmt)
w++
exp := d.dp - 1 exp := d.dp - 1
if d.nd == 0 { // special case: 0 has exponent 0 if d.nd == 0 { // special case: 0 has exponent 0
exp = 0 exp = 0
} }
if exp < 0 { if exp < 0 {
buf[w] = '-' ch = '-'
exp = -exp exp = -exp
} else { } else {
buf[w] = '+' ch = '+'
} }
w++ dst = append(dst, ch)
// dddd // dddd
// count digits var buf [3]byte
n := 0 i := len(buf)
for e := exp; e > 0; e /= 10 { for exp >= 10 {
n++ i--
buf[i] = byte(exp%10 + '0')
exp /= 10
} }
// leading zeros // exp < 10
for i := n; i < 2; i++ { i--
buf[w] = '0' buf[i] = byte(exp + '0')
w++
} // leading zeroes
// digits if i > len(buf)-2 {
w += n i--
n = 0 buf[i] = '0'
for e := exp; e > 0; e /= 10 {
n++
buf[w-n] = byte(e%10 + '0')
} }
return string(buf[0:w]) return append(dst, buf[i:]...)
} }
// %f: -ddddddd.ddddd // %f: -ddddddd.ddddd
func fmtF(neg bool, d *decimal, prec int) string { func fmtF(dst []byte, neg bool, d *decimal, prec int) []byte {
buf := make([]byte, 1+max(d.dp, 1)+1+max(prec, 0))
w := 0
// sign // sign
if neg { if neg {
buf[w] = '-' dst = append(dst, '-')
w++
} }
// integer, padded with zeros as needed. // integer, padded with zeros as needed.
if d.dp > 0 { if d.dp > 0 {
var i int var i int
for i = 0; i < d.dp && i < d.nd; i++ { for i = 0; i < d.dp && i < d.nd; i++ {
buf[w] = d.d[i] dst = append(dst, d.d[i])
w++
} }
for ; i < d.dp; i++ { for ; i < d.dp; i++ {
buf[w] = '0' dst = append(dst, '0')
w++
} }
} else { } else {
buf[w] = '0' dst = append(dst, '0')
w++
} }
// fraction // fraction
if prec > 0 { if prec > 0 {
buf[w] = '.' dst = append(dst, '.')
w++
for i := 0; i < prec; i++ { for i := 0; i < prec; i++ {
if d.dp+i < 0 || d.dp+i >= d.nd { ch := byte('0')
buf[w] = '0' if j := d.dp + i; 0 <= j && j < d.nd {
} else { ch = d.d[j]
buf[w] = d.d[d.dp+i]
} }
w++ dst = append(dst, ch)
} }
} }
return string(buf[0:w]) return dst
} }
// %b: -ddddddddp+ddd // %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 var buf [50]byte
w := len(buf) w := len(buf)
exp -= int(flt.mantbits) exp -= int(flt.mantbits)
...@@ -395,7 +391,7 @@ func fmtB(neg bool, mant uint64, exp int, flt *floatInfo) string { ...@@ -395,7 +391,7 @@ func fmtB(neg bool, mant uint64, exp int, flt *floatInfo) string {
w-- w--
buf[w] = '-' buf[w] = '-'
} }
return string(buf[w:]) return append(dst, buf[w:]...)
} }
func max(a, b int) int { func max(a, b int) int {
......
...@@ -149,26 +149,54 @@ func TestFtoa(t *testing.T) { ...@@ -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++ { for i := 0; i < b.N; i++ {
FormatFloat(33909, 'g', -1, 64) FormatFloat(33909, 'g', -1, 64)
} }
} }
func BenchmarkFtoa64Float(b *testing.B) { func BenchmarkFormatFloat(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
FormatFloat(339.7784, 'g', -1, 64) FormatFloat(339.7784, 'g', -1, 64)
} }
} }
func BenchmarkFtoa64FloatExp(b *testing.B) { func BenchmarkFormatFloatExp(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
FormatFloat(-5.09e75, 'g', -1, 64) FormatFloat(-5.09e75, 'g', -1, 64)
} }
} }
func BenchmarkFtoa64Big(b *testing.B) { func BenchmarkFormatFloatBig(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
FormatFloat(123456789123456789123456789, 'g', -1, 64) 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)
}
}
...@@ -46,21 +46,21 @@ var shifts = [len(digits) + 1]uint{ ...@@ -46,21 +46,21 @@ var shifts = [len(digits) + 1]uint{
} }
// formatBits computes the string representation of u in the given base. // formatBits computes the string representation of u in the given base.
// If negative is set, u is treated as negative int64 value. If append_ // If neg is set, u is treated as negative int64 value. If append_ is
// is set, the string is appended to dst and the resulting byte slice 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 // returned as the first result value; otherwise the string is returned
// as the second result value. // 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) { if base < 2 || base > len(digits) {
panic("invalid base") panic("strconv: illegal AppendInt/FormatInt base")
} }
// 2 <= base && base <= len(digits) // 2 <= base && base <= len(digits)
var a [64 + 1]byte // +1 for sign of 64bit value in base 2 var a [64 + 1]byte // +1 for sign of 64bit value in base 2
i := len(a) i := len(a)
if negative { if neg {
u = -u u = -u
} }
...@@ -99,7 +99,7 @@ func formatBits(dst []byte, u uint64, base int, negative, append_ bool) (d []byt ...@@ -99,7 +99,7 @@ func formatBits(dst []byte, u uint64, base int, negative, append_ bool) (d []byt
a[i] = digits[uintptr(u)] a[i] = digits[uintptr(u)]
// add sign, if any // add sign, if any
if negative { if neg {
i-- i--
a[i] = '-' a[i] = '-'
} }
......
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