]> Cypherpunks repositories - gostls13.git/commitdiff
strconv: format hex floats
authorRuss Cox <rsc@golang.org>
Wed, 30 Jan 2019 01:16:59 +0000 (20:16 -0500)
committerRuss Cox <rsc@golang.org>
Tue, 12 Feb 2019 14:48:22 +0000 (14:48 +0000)
This CL updates FormatFloat to format
standard hexadecimal floating-point constants,
using the 'x' and 'X' verbs.

See golang.org/design/19308-number-literals for background.

For #29008.

Change-Id: I540b8f71d492cfdb7c58af533d357a564591f28b
Reviewed-on: https://go-review.googlesource.com/c/160242
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
src/math/big/floatconv_test.go
src/strconv/ftoa.go
src/strconv/ftoa_test.go
src/strconv/quote.go

index 6db9bf2e466e4167c8329097068d6cf4e179d980..154c818905d3c3d11b799b09d970880420234921 100644 (file)
@@ -268,7 +268,7 @@ func TestFloat64Text(t *testing.T) {
                {32, 'g', -1, "32"},
                {32, 'g', 0, "3e+01"},
 
-               {100, 'x', -1, "%x"},
+               // {100, 'x', -1, "%x"},
 
                // {math.NaN(), 'g', -1, "NaN"},  // Float doesn't support NaNs
                // {-math.NaN(), 'g', -1, "NaN"}, // Float doesn't support NaNs
@@ -440,8 +440,8 @@ func TestFloatText(t *testing.T) {
                {"-1024.0", 64, 'p', 0, "-0x.8p+11"},
 
                // unsupported format
-               {"3.14", 64, 'x', 0, "%x"},
-               {"-3.14", 64, 'x', 0, "%x"},
+               //{"3.14", 64, 'x', 0, "%x"},
+               //{"-3.14", 64, 'x', 0, "%x"},
        } {
                f, _, err := ParseFloat(test.x, 0, test.prec, ToNearestEven)
                if err != nil {
index a7ccbe6727fdb20991ff1c055af2068e1539da20..432521b24fd3abdd7f42a9ebc02e922678fe6c6a 100644 (file)
@@ -32,12 +32,14 @@ var float64info = floatInfo{52, 11, -1023}
 // 'e' (-d.dddde±dd, a decimal exponent),
 // 'E' (-d.ddddE±dd, a decimal exponent),
 // 'f' (-ddd.dddd, no exponent),
-// 'g' ('e' for large exponents, 'f' otherwise), or
-// 'G' ('E' for large exponents, 'f' otherwise).
+// 'g' ('e' for large exponents, 'f' otherwise),
+// 'G' ('E' for large exponents, 'f' otherwise),
+// 'x' (-0xd.ddddp±ddd, a hexadecimal fraction and binary exponent), or
+// 'X' (-0Xd.ddddP±ddd, a hexadecimal fraction and binary exponent).
 //
 // The precision prec controls the number of digits (excluding the exponent)
-// printed by the 'e', 'E', 'f', 'g', and 'G' formats.
-// For 'e', 'E', and 'f' it is the number of digits after the decimal point.
+// printed by the 'e', 'E', 'f', 'g', 'G', 'x', and 'X' formats.
+// For 'e', 'E', 'f', 'x', and 'X', it is the number of digits after the decimal point.
 // For 'g' and 'G' it is the maximum number of significant digits (trailing
 // zeros are removed).
 // The special precision -1 uses the smallest number of digits
@@ -94,10 +96,13 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte {
        }
        exp += flt.bias
 
-       // Pick off easy binary format.
+       // Pick off easy binary, hex formats.
        if fmt == 'b' {
                return fmtB(dst, neg, mant, exp, flt)
        }
+       if fmt == 'x' || fmt == 'X' {
+               return fmtX(dst, prec, fmt, neg, mant, exp, flt)
+       }
 
        if !optimize {
                return bigFtoa(dst, prec, fmt, neg, mant, exp, flt)
@@ -439,6 +444,89 @@ func fmtB(dst []byte, neg bool, mant uint64, exp int, flt *floatInfo) []byte {
        return dst
 }
 
+// %x: -0x1.yyyyyyyyp±ddd or -0x0p+0. (y is hex digit, d is decimal digit)
+func fmtX(dst []byte, prec int, fmt byte, neg bool, mant uint64, exp int, flt *floatInfo) []byte {
+       if mant == 0 {
+               exp = 0
+       }
+
+       // Shift digits so leading 1 (if any) is at bit 1<<60.
+       mant <<= 60 - flt.mantbits
+       for mant != 0 && mant&(1<<60) == 0 {
+               mant <<= 1
+               exp--
+       }
+
+       // Round if requested.
+       if prec >= 0 && prec < 15 {
+               shift := uint(prec * 4)
+               extra := (mant << shift) & (1<<60 - 1)
+               mant >>= 60 - shift
+               if extra|(mant&1) > 1<<59 {
+                       mant++
+               }
+               mant <<= 60 - shift
+               if mant&(1<<61) != 0 {
+                       // Wrapped around.
+                       mant >>= 1
+                       exp++
+               }
+       }
+
+       hex := lowerhex
+       if fmt == 'X' {
+               hex = upperhex
+       }
+
+       // sign, 0x, leading digit
+       if neg {
+               dst = append(dst, '-')
+       }
+       dst = append(dst, '0', fmt, '0'+byte((mant>>60)&1))
+
+       // .fraction
+       mant <<= 4 // remove leading 0 or 1
+       if prec < 0 && mant != 0 {
+               dst = append(dst, '.')
+               for mant != 0 {
+                       dst = append(dst, hex[(mant>>60)&15])
+                       mant <<= 4
+               }
+       } else if prec > 0 {
+               dst = append(dst, '.')
+               for i := 0; i < prec; i++ {
+                       dst = append(dst, hex[(mant>>60)&15])
+                       mant <<= 4
+               }
+       }
+
+       // p±
+       ch := byte('P')
+       if fmt == lower(fmt) {
+               ch = 'p'
+       }
+       dst = append(dst, ch)
+       if exp < 0 {
+               ch = '-'
+               exp = -exp
+       } else {
+               ch = '+'
+       }
+       dst = append(dst, ch)
+
+       // dd or ddd or dddd
+       switch {
+       case exp < 100:
+               dst = append(dst, byte(exp/10)+'0', byte(exp%10)+'0')
+       case exp < 1000:
+               dst = append(dst, byte(exp/100)+'0', byte((exp/10)%10)+'0', byte(exp%10)+'0')
+       default:
+               dst = append(dst, byte(exp/1000)+'0', byte(exp/100)%10+'0', byte((exp/10)%10)+'0', byte(exp%10)+'0')
+       }
+
+       return dst
+}
+
 func min(a, b int) int {
        if a < b {
                return a
index 1d3030be81932a02aefdcf807769cb0464fdb23b..055fef99aa297f05de4822b38588e5c12a3d99fe 100644 (file)
@@ -30,9 +30,15 @@ var ftoatests = []ftoaTest{
        {1, 'f', 5, "1.00000"},
        {1, 'g', 5, "1"},
        {1, 'g', -1, "1"},
+       {1, 'x', -1, "0x1p+00"},
+       {1, 'x', 5, "0x1.00000p+00"},
        {20, 'g', -1, "20"},
+       {20, 'x', -1, "0x1.4p+04"},
        {1234567.8, 'g', -1, "1.2345678e+06"},
+       {1234567.8, 'x', -1, "0x1.2d687cccccccdp+20"},
        {200000, 'g', -1, "200000"},
+       {200000, 'x', -1, "0x1.86ap+17"},
+       {200000, 'X', -1, "0X1.86AP+17"},
        {2000000, 'g', -1, "2e+06"},
 
        // g conversion and zero suppression
@@ -50,6 +56,7 @@ var ftoatests = []ftoaTest{
        {0, 'f', 5, "0.00000"},
        {0, 'g', 5, "0"},
        {0, 'g', -1, "0"},
+       {0, 'x', 5, "0x0.00000p+00"},
 
        {-1, 'e', 5, "-1.00000e+00"},
        {-1, 'f', 5, "-1.00000"},
@@ -100,7 +107,8 @@ var ftoatests = []ftoaTest{
        {32, 'g', -1, "32"},
        {32, 'g', 0, "3e+01"},
 
-       {100, 'x', -1, "%x"},
+       {100, 'x', -1, "0x1.9p+06"},
+       {100, 'y', -1, "%y"},
 
        {math.NaN(), 'g', -1, "NaN"},
        {-math.NaN(), 'g', -1, "NaN"},
@@ -128,6 +136,23 @@ var ftoatests = []ftoaTest{
        // Issue 2625.
        {383260575764816448, 'f', 0, "383260575764816448"},
        {383260575764816448, 'g', -1, "3.8326057576481645e+17"},
+
+       // rounding
+       {2.275555555555555, 'x', -1, "0x1.23456789abcdep+01"},
+       {2.275555555555555, 'x', 0, "0x1p+01"},
+       {2.275555555555555, 'x', 2, "0x1.23p+01"},
+       {2.275555555555555, 'x', 16, "0x1.23456789abcde000p+01"},
+       {2.275555555555555, 'x', 21, "0x1.23456789abcde00000000p+01"},
+       {2.2755555510520935, 'x', -1, "0x1.2345678p+01"},
+       {2.2755555510520935, 'x', 6, "0x1.234568p+01"},
+       {2.275555431842804, 'x', -1, "0x1.2345668p+01"},
+       {2.275555431842804, 'x', 6, "0x1.234566p+01"},
+       {3.999969482421875, 'x', -1, "0x1.ffffp+01"},
+       {3.999969482421875, 'x', 4, "0x1.ffffp+01"},
+       {3.999969482421875, 'x', 3, "0x1.000p+02"},
+       {3.999969482421875, 'x', 2, "0x1.00p+02"},
+       {3.999969482421875, 'x', 1, "0x1.0p+02"},
+       {3.999969482421875, 'x', 0, "0x1p+02"},
 }
 
 func TestFtoa(t *testing.T) {
index 6cd2f93068c03c36af8a48fdfc356be629b81360..d8a1ed9eccb42276c0f55cea7473fe9220e265f1 100644 (file)
@@ -11,7 +11,10 @@ import (
        "unicode/utf8"
 )
 
-const lowerhex = "0123456789abcdef"
+const (
+       lowerhex = "0123456789abcdef"
+       upperhex = "0123456789ABCDEF"
+)
 
 func quoteWith(s string, quote byte, ASCIIonly, graphicOnly bool) string {
        return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote, ASCIIonly, graphicOnly))