Skip to content

Commit bb1f56c

Browse files
rscnebulabox
authored andcommitted
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 golang#29008. Change-Id: I540b8f71d492cfdb7c58af533d357a564591f28b Reviewed-on: https://go-review.googlesource.com/c/160242 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 3a41752 commit bb1f56c

File tree

4 files changed

+126
-10
lines changed

4 files changed

+126
-10
lines changed

src/math/big/floatconv_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func TestFloat64Text(t *testing.T) {
268268
{32, 'g', -1, "32"},
269269
{32, 'g', 0, "3e+01"},
270270

271-
{100, 'x', -1, "%x"},
271+
// {100, 'x', -1, "%x"},
272272

273273
// {math.NaN(), 'g', -1, "NaN"}, // Float doesn't support NaNs
274274
// {-math.NaN(), 'g', -1, "NaN"}, // Float doesn't support NaNs
@@ -440,8 +440,8 @@ func TestFloatText(t *testing.T) {
440440
{"-1024.0", 64, 'p', 0, "-0x.8p+11"},
441441

442442
// unsupported format
443-
{"3.14", 64, 'x', 0, "%x"},
444-
{"-3.14", 64, 'x', 0, "%x"},
443+
//{"3.14", 64, 'x', 0, "%x"},
444+
//{"-3.14", 64, 'x', 0, "%x"},
445445
} {
446446
f, _, err := ParseFloat(test.x, 0, test.prec, ToNearestEven)
447447
if err != nil {

src/strconv/ftoa.go

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ var float64info = floatInfo{52, 11, -1023}
3232
// 'e' (-d.dddde±dd, a decimal exponent),
3333
// 'E' (-d.ddddE±dd, a decimal exponent),
3434
// 'f' (-ddd.dddd, no exponent),
35-
// 'g' ('e' for large exponents, 'f' otherwise), or
36-
// 'G' ('E' for large exponents, 'f' otherwise).
35+
// 'g' ('e' for large exponents, 'f' otherwise),
36+
// 'G' ('E' for large exponents, 'f' otherwise),
37+
// 'x' (-0xd.ddddp±ddd, a hexadecimal fraction and binary exponent), or
38+
// 'X' (-0Xd.ddddP±ddd, a hexadecimal fraction and binary exponent).
3739
//
3840
// The precision prec controls the number of digits (excluding the exponent)
39-
// printed by the 'e', 'E', 'f', 'g', and 'G' formats.
40-
// For 'e', 'E', and 'f' it is the number of digits after the decimal point.
41+
// printed by the 'e', 'E', 'f', 'g', 'G', 'x', and 'X' formats.
42+
// For 'e', 'E', 'f', 'x', and 'X', it is the number of digits after the decimal point.
4143
// For 'g' and 'G' it is the maximum number of significant digits (trailing
4244
// zeros are removed).
4345
// 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 {
9496
}
9597
exp += flt.bias
9698

97-
// Pick off easy binary format.
99+
// Pick off easy binary, hex formats.
98100
if fmt == 'b' {
99101
return fmtB(dst, neg, mant, exp, flt)
100102
}
103+
if fmt == 'x' || fmt == 'X' {
104+
return fmtX(dst, prec, fmt, neg, mant, exp, flt)
105+
}
101106

102107
if !optimize {
103108
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 {
439444
return dst
440445
}
441446

447+
// %x: -0x1.yyyyyyyyp±ddd or -0x0p+0. (y is hex digit, d is decimal digit)
448+
func fmtX(dst []byte, prec int, fmt byte, neg bool, mant uint64, exp int, flt *floatInfo) []byte {
449+
if mant == 0 {
450+
exp = 0
451+
}
452+
453+
// Shift digits so leading 1 (if any) is at bit 1<<60.
454+
mant <<= 60 - flt.mantbits
455+
for mant != 0 && mant&(1<<60) == 0 {
456+
mant <<= 1
457+
exp--
458+
}
459+
460+
// Round if requested.
461+
if prec >= 0 && prec < 15 {
462+
shift := uint(prec * 4)
463+
extra := (mant << shift) & (1<<60 - 1)
464+
mant >>= 60 - shift
465+
if extra|(mant&1) > 1<<59 {
466+
mant++
467+
}
468+
mant <<= 60 - shift
469+
if mant&(1<<61) != 0 {
470+
// Wrapped around.
471+
mant >>= 1
472+
exp++
473+
}
474+
}
475+
476+
hex := lowerhex
477+
if fmt == 'X' {
478+
hex = upperhex
479+
}
480+
481+
// sign, 0x, leading digit
482+
if neg {
483+
dst = append(dst, '-')
484+
}
485+
dst = append(dst, '0', fmt, '0'+byte((mant>>60)&1))
486+
487+
// .fraction
488+
mant <<= 4 // remove leading 0 or 1
489+
if prec < 0 && mant != 0 {
490+
dst = append(dst, '.')
491+
for mant != 0 {
492+
dst = append(dst, hex[(mant>>60)&15])
493+
mant <<= 4
494+
}
495+
} else if prec > 0 {
496+
dst = append(dst, '.')
497+
for i := 0; i < prec; i++ {
498+
dst = append(dst, hex[(mant>>60)&15])
499+
mant <<= 4
500+
}
501+
}
502+
503+
// p±
504+
ch := byte('P')
505+
if fmt == lower(fmt) {
506+
ch = 'p'
507+
}
508+
dst = append(dst, ch)
509+
if exp < 0 {
510+
ch = '-'
511+
exp = -exp
512+
} else {
513+
ch = '+'
514+
}
515+
dst = append(dst, ch)
516+
517+
// dd or ddd or dddd
518+
switch {
519+
case exp < 100:
520+
dst = append(dst, byte(exp/10)+'0', byte(exp%10)+'0')
521+
case exp < 1000:
522+
dst = append(dst, byte(exp/100)+'0', byte((exp/10)%10)+'0', byte(exp%10)+'0')
523+
default:
524+
dst = append(dst, byte(exp/1000)+'0', byte(exp/100)%10+'0', byte((exp/10)%10)+'0', byte(exp%10)+'0')
525+
}
526+
527+
return dst
528+
}
529+
442530
func min(a, b int) int {
443531
if a < b {
444532
return a

src/strconv/ftoa_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ var ftoatests = []ftoaTest{
3030
{1, 'f', 5, "1.00000"},
3131
{1, 'g', 5, "1"},
3232
{1, 'g', -1, "1"},
33+
{1, 'x', -1, "0x1p+00"},
34+
{1, 'x', 5, "0x1.00000p+00"},
3335
{20, 'g', -1, "20"},
36+
{20, 'x', -1, "0x1.4p+04"},
3437
{1234567.8, 'g', -1, "1.2345678e+06"},
38+
{1234567.8, 'x', -1, "0x1.2d687cccccccdp+20"},
3539
{200000, 'g', -1, "200000"},
40+
{200000, 'x', -1, "0x1.86ap+17"},
41+
{200000, 'X', -1, "0X1.86AP+17"},
3642
{2000000, 'g', -1, "2e+06"},
3743

3844
// g conversion and zero suppression
@@ -50,6 +56,7 @@ var ftoatests = []ftoaTest{
5056
{0, 'f', 5, "0.00000"},
5157
{0, 'g', 5, "0"},
5258
{0, 'g', -1, "0"},
59+
{0, 'x', 5, "0x0.00000p+00"},
5360

5461
{-1, 'e', 5, "-1.00000e+00"},
5562
{-1, 'f', 5, "-1.00000"},
@@ -100,7 +107,8 @@ var ftoatests = []ftoaTest{
100107
{32, 'g', -1, "32"},
101108
{32, 'g', 0, "3e+01"},
102109

103-
{100, 'x', -1, "%x"},
110+
{100, 'x', -1, "0x1.9p+06"},
111+
{100, 'y', -1, "%y"},
104112

105113
{math.NaN(), 'g', -1, "NaN"},
106114
{-math.NaN(), 'g', -1, "NaN"},
@@ -128,6 +136,23 @@ var ftoatests = []ftoaTest{
128136
// Issue 2625.
129137
{383260575764816448, 'f', 0, "383260575764816448"},
130138
{383260575764816448, 'g', -1, "3.8326057576481645e+17"},
139+
140+
// rounding
141+
{2.275555555555555, 'x', -1, "0x1.23456789abcdep+01"},
142+
{2.275555555555555, 'x', 0, "0x1p+01"},
143+
{2.275555555555555, 'x', 2, "0x1.23p+01"},
144+
{2.275555555555555, 'x', 16, "0x1.23456789abcde000p+01"},
145+
{2.275555555555555, 'x', 21, "0x1.23456789abcde00000000p+01"},
146+
{2.2755555510520935, 'x', -1, "0x1.2345678p+01"},
147+
{2.2755555510520935, 'x', 6, "0x1.234568p+01"},
148+
{2.275555431842804, 'x', -1, "0x1.2345668p+01"},
149+
{2.275555431842804, 'x', 6, "0x1.234566p+01"},
150+
{3.999969482421875, 'x', -1, "0x1.ffffp+01"},
151+
{3.999969482421875, 'x', 4, "0x1.ffffp+01"},
152+
{3.999969482421875, 'x', 3, "0x1.000p+02"},
153+
{3.999969482421875, 'x', 2, "0x1.00p+02"},
154+
{3.999969482421875, 'x', 1, "0x1.0p+02"},
155+
{3.999969482421875, 'x', 0, "0x1p+02"},
131156
}
132157

133158
func TestFtoa(t *testing.T) {

src/strconv/quote.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import (
1111
"unicode/utf8"
1212
)
1313

14-
const lowerhex = "0123456789abcdef"
14+
const (
15+
lowerhex = "0123456789abcdef"
16+
upperhex = "0123456789ABCDEF"
17+
)
1518

1619
func quoteWith(s string, quote byte, ASCIIonly, graphicOnly bool) string {
1720
return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote, ASCIIonly, graphicOnly))

0 commit comments

Comments
 (0)