]> Cypherpunks repositories - gostls13.git/commitdiff
strconv: faster float conversion
authorRobert Griesemer <gri@golang.org>
Wed, 7 Dec 2011 18:30:27 +0000 (10:30 -0800)
committerRobert Griesemer <gri@golang.org>
Wed, 7 Dec 2011 18:30:27 +0000 (10:30 -0800)
- 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
src/pkg/strconv/ftoa_test.go
src/pkg/strconv/itoa.go

index e1ea0a350383d46b84b76443557e16f3248740c9..b2413eee6b4573c6580d6e3621f9ed2d216422c3 100644 (file)
@@ -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<<flt.expbits - 1)
        mant := bits & (uint64(1)<<flt.mantbits - 1)
@@ -66,13 +76,16 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
        switch exp {
        case 1<<flt.expbits - 1:
                // Inf, NaN
-               if mant != 0 {
-                       return "NaN"
-               }
-               if neg {
-                       return "-Inf"
+               var s string
+               switch {
+               case mant != 0:
+                       s = "NaN"
+               case neg:
+                       s = "-Inf"
+               default:
+                       s = "+Inf"
                }
-               return "+Inf"
+               return append(dst, s...)
 
        case 0:
                // denormalized
@@ -86,7 +99,7 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
 
        // Pick off easy binary format.
        if fmt == 'b' {
-               return fmtB(neg, mant, exp, flt)
+               return fmtB(dst, neg, mant, exp, flt)
        }
 
        // Create exact decimal representation.
@@ -127,9 +140,9 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
 
        switch fmt {
        case 'e', 'E':
-               return fmtE(neg, d, prec, fmt)
+               return fmtE(dst, neg, d, prec, fmt)
        case 'f':
-               return fmtF(neg, d, prec)
+               return fmtF(dst, neg, d, prec)
        case 'g', 'G':
                // trailing fractional zeros in 'e' form will be trimmed.
                eprec := prec
@@ -147,15 +160,16 @@ func genericFtoa(bits uint64, fmt byte, prec int, flt *floatInfo) string {
                        if 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 {
                        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 {
index 02206d5ad25351044c6b4936d99c0d71ebe387db..a6205ac4777e601ac5336479d3587a25c27e298e 100644 (file)
@@ -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)
+       }
+}
index 65229f704b8f4e6fcde0a65155e6ecd6f3ade34d..821abe0094b2c7d115c4d6c322b9fb4888438f0a 100644 (file)
@@ -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] = '-'
        }