From d6b06e364836c278376bba91ccb014fc0e713ba2 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 29 Jan 2019 20:16:59 -0500 Subject: [PATCH] strconv: format hex floats 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 TryBot-Result: Gobot Gobot Reviewed-by: Robert Griesemer --- src/math/big/floatconv_test.go | 6 +-- src/strconv/ftoa.go | 98 ++++++++++++++++++++++++++++++++-- src/strconv/ftoa_test.go | 27 +++++++++- src/strconv/quote.go | 5 +- 4 files changed, 126 insertions(+), 10 deletions(-) diff --git a/src/math/big/floatconv_test.go b/src/math/big/floatconv_test.go index 6db9bf2e466e41..154c818905d3c3 100644 --- a/src/math/big/floatconv_test.go +++ b/src/math/big/floatconv_test.go @@ -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 { diff --git a/src/strconv/ftoa.go b/src/strconv/ftoa.go index a7ccbe6727fdb2..432521b24fd3ab 100644 --- a/src/strconv/ftoa.go +++ b/src/strconv/ftoa.go @@ -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 diff --git a/src/strconv/ftoa_test.go b/src/strconv/ftoa_test.go index 1d3030be81932a..055fef99aa297f 100644 --- a/src/strconv/ftoa_test.go +++ b/src/strconv/ftoa_test.go @@ -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) { diff --git a/src/strconv/quote.go b/src/strconv/quote.go index 6cd2f93068c03c..d8a1ed9eccb422 100644 --- a/src/strconv/quote.go +++ b/src/strconv/quote.go @@ -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))