Commit bd6d8428 authored by Martin Möhrmann's avatar Martin Möhrmann Committed by Rob Pike

fmt: refactor and unify float and complex formatting

Removes specialized functions for each verb and float/complex size
and replaces them with generic variants fmtFloat and
fmtComplex similar to other generic fmt functions.

Simplifies the complex formatting by relying on fmtFloat
to handle the verb and default precision selection.

Complex imaginary formatting does not need to clear the f.space flag
because the set f.plus flag will force a sign instead of a space.

Sets default precision for %b to -1 (same as %g and %G)
since precision for %b has no affect in strconv.AppendFloat.

Add more tests and group them a bit better.
Use local copies of +Inf,-Inf and NaN instead
of math package functions for testing.

Saves around 8kb in the go binary.

name              old time/op  new time/op  delta
SprintfFloat-2     200ns ± 4%   196ns ± 4%  -1.55%  (p=0.007 n=20+20)
SprintfComplex-2   569ns ± 4%   570ns ± 3%    ~     (p=0.804 n=20+20)

Change-Id: I36d35dab6f835fc2bd2c042ac97705868eb2446f
Reviewed-on: https://go-review.googlesource.com/20252Reviewed-by: 's avatarRob Pike <r@golang.org>
Run-TryBot: Rob Pike <r@golang.org>
parent 5763476f
......@@ -48,6 +48,12 @@ func TestFmtInterface(t *testing.T) {
}
}
var (
NaN = math.NaN()
posInf = math.Inf(1)
negInf = math.Inf(-1)
)
const b32 uint32 = 1<<32 - 1
const b64 uint64 = 1<<64 - 1
......@@ -345,9 +351,31 @@ var fmtTests = []struct {
{"% .3g", 1.0, " 1"},
{"%b", float32(1.0), "8388608p-23"},
{"%b", 1.0, "4503599627370496p-52"},
// Precision has no effect for binary float format.
{"%.4b", float32(1.0), "8388608p-23"},
{"%.4b", -1.0, "-4503599627370496p-52"},
// float infinites and NaNs
{"%f", posInf, "+Inf"},
{"%.1f", negInf, "-Inf"},
{"% f", NaN, " NaN"},
{"%20f", posInf, " +Inf"},
{"% 20F", posInf, " Inf"},
{"% 20e", negInf, " -Inf"},
{"%+20E", negInf, " -Inf"},
{"% +20g", negInf, " -Inf"},
{"%+-20G", posInf, "+Inf "},
{"%20e", NaN, " NaN"},
{"% +20E", NaN, " +NaN"},
{"% -20g", NaN, " NaN "},
{"%+-20G", NaN, "+NaN "},
// Zero padding does not apply to infinities and NaN.
{"%+020e", posInf, " +Inf"},
{"%-020f", negInf, "-Inf "},
{"%-020E", NaN, "NaN "},
// complex values
{"%.f", 0i, "(0+0i)"},
{"% .f", 0i, "( 0+0i)"},
{"%+.f", 0i, "(+0+0i)"},
{"% +.f", 0i, "(+0+0i)"},
{"%+.3e", 0i, "(+0.000e+00+0.000e+00i)"},
......@@ -368,10 +396,31 @@ var fmtTests = []struct {
{"%.3f", -1 - 2i, "(-1.000-2.000i)"},
{"%.3g", -1 - 2i, "(-1-2i)"},
{"% .3E", -1 - 2i, "(-1.000E+00-2.000E+00i)"},
{"%+.3g", 1 + 2i, "(+1+2i)"},
{"%+.3g", complex64(1 + 2i), "(+1+2i)"},
{"%+.3g", complex128(1 + 2i), "(+1+2i)"},
{"%b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"},
{"%b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"},
{"%b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"},
// Precision has no effect for binary complex format.
{"%.4b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"},
{"%.4b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"},
// complex infinites and NaNs
{"%f", complex(posInf, posInf), "(+Inf+Infi)"},
{"%f", complex(negInf, negInf), "(-Inf-Infi)"},
{"%f", complex(NaN, NaN), "(NaN+NaNi)"},
{"%.1f", complex(posInf, posInf), "(+Inf+Infi)"},
{"% f", complex(posInf, posInf), "( Inf+Infi)"},
{"% f", complex(negInf, negInf), "(-Inf-Infi)"},
{"% f", complex(NaN, NaN), "( NaN+NaNi)"},
{"%8e", complex(posInf, posInf), "( +Inf +Infi)"},
{"% 8E", complex(posInf, posInf), "( Inf +Infi)"},
{"%+8f", complex(negInf, negInf), "( -Inf -Infi)"},
{"% +8g", complex(negInf, negInf), "( -Inf -Infi)"},
{"% -8G", complex(NaN, NaN), "( NaN +NaN i)"},
{"%+-8b", complex(NaN, NaN), "(+NaN +NaN i)"},
// Zero padding does not apply to infinities and NaN.
{"%08f", complex(posInf, posInf), "( +Inf +Infi)"},
{"%-08g", complex(negInf, negInf), "(-Inf -Inf i)"},
{"%-08G", complex(NaN, NaN), "(NaN +NaN i)"},
// erroneous formats
{"", 2, "%!(EXTRA int=2)"},
......@@ -455,16 +504,6 @@ var fmtTests = []struct {
{"%g", 1.23456789e3, "1234.56789"},
{"%g", 1.23456789e-3, "0.00123456789"},
{"%g", 1.23456789e20, "1.23456789e+20"},
{"%20e", math.Inf(1), " +Inf"},
{"% 20f", math.Inf(1), " Inf"},
{"%+20f", math.Inf(1), " +Inf"},
{"% +20f", math.Inf(1), " +Inf"},
{"%-20f", math.Inf(-1), "-Inf "},
{"%20g", math.NaN(), " NaN"},
{"%+20f", math.NaN(), " +NaN"},
{"% +20f", math.NaN(), " +NaN"},
{"% -20f", math.NaN(), " NaN "},
{"%+-20f", math.NaN(), "+NaN "},
// arrays
{"%v", array, "[1 2 3 4 5]"},
......@@ -533,10 +572,13 @@ var fmtTests = []struct {
{"%# -6d", []byte{1, 11, 111}, "[ 1 11 111 ]"},
{"%#+-6d", [3]byte{1, 11, 111}, "[+1 +11 +111 ]"},
// floates with %v
{"%v", 1.2345678, "1.2345678"},
{"%v", float32(1.2345678), "1.2345678"},
// complexes with %v
{"%v", 1 + 2i, "(1+2i)"},
{"%v", complex64(1 + 2i), "(1+2i)"},
{"%v", complex128(1 + 2i), "(1+2i)"},
// structs
{"%v", A{1, 2, "a", []int{1, 2}}, `{1 2 a [1 2]}`},
......@@ -578,6 +620,8 @@ var fmtTests = []struct {
{"%#v", bslice, `[]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`},
{"%#v", []byte(nil), "[]byte(nil)"},
{"%#v", []int32(nil), "[]int32(nil)"},
{"%#v", 1.2345678, "1.2345678"},
{"%#v", float32(1.2345678), "1.2345678"},
// slices with other formats
{"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`},
......@@ -732,7 +776,7 @@ var fmtTests = []struct {
// be fetched directly, the lookup fails and returns a
// zero reflect.Value, which formats as <nil>.
// This test is just to check that it shows the two NaNs at all.
{"%v", map[float64]int{math.NaN(): 1, math.NaN(): 2}, "map[NaN:<nil> NaN:<nil>]"},
{"%v", map[float64]int{NaN: 1, NaN: 2}, "map[NaN:<nil> NaN:<nil>]"},
// Used to crash because nByte didn't allow for a sign.
{"%b", int64(-1 << 63), zeroFill("-1", 63, "")},
......@@ -822,19 +866,7 @@ var fmtTests = []struct {
// Complex numbers: exhaustively tested in TestComplexFormatting.
{"%7.2f", 1 + 2i, "( 1.00 +2.00i)"},
{"%+07.2f", -1 - 2i, "(-001.00-002.00i)"},
// Zero padding does not apply to infinities and NaN.
{"%020f", math.Inf(-1), " -Inf"},
{"%020f", math.Inf(+1), " +Inf"},
{"%020f", math.NaN(), " NaN"},
{"% 020f", math.Inf(-1), " -Inf"},
{"% 020f", math.Inf(+1), " Inf"},
{"% 020f", math.NaN(), " NaN"},
{"%+020f", math.Inf(-1), " -Inf"},
{"%+020f", math.Inf(+1), " +Inf"},
{"%+020f", math.NaN(), " +NaN"},
{"%-020f", math.Inf(-1), "-Inf "},
{"%-020f", math.Inf(+1), "+Inf "},
{"%-020f", math.NaN(), "NaN "},
{"%20f", -1.0, " -1.000000"},
// Make sure we can handle very large widths.
{"%0100f", -1.0, zeroFill("-", 99, "1.000000")},
......@@ -925,6 +957,10 @@ var fmtTests = []struct {
{"%☠", []uint8{0}, "%!☠([]uint8=[0])"},
{"%☠", [1]byte{0}, "%!☠([1]uint8=[0])"},
{"%☠", [1]uint8{0}, "%!☠([1]uint8=[0])"},
{"%☠", 1.2345678, "%!☠(float64=1.2345678)"},
{"%☠", float32(1.2345678), "%!☠(float32=1.2345678)"},
{"%☠", 1.2345678 + 1.2345678i, "%!☠(complex128=(1.2345678+1.2345678i))"},
{"%☠", complex64(1.2345678 + 1.2345678i), "%!☠(complex64=(1.2345678+1.2345678i))"},
}
// zeroFill generates zero-filled strings of the specified width. The length
......@@ -974,7 +1010,7 @@ func TestSprintf(t *testing.T) {
// thing as if done by hand with two singleton prints.
func TestComplexFormatting(t *testing.T) {
var yesNo = []bool{true, false}
var values = []float64{1, 0, -1, math.Inf(1), math.Inf(-1), math.NaN()}
var values = []float64{1, 0, -1, posInf, negInf, NaN}
for _, plus := range yesNo {
for _, zero := range yesNo {
for _, space := range yesNo {
......@@ -1140,6 +1176,14 @@ func BenchmarkSprintfFloat(b *testing.B) {
})
}
func BenchmarkSprintfComplex(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Sprintf("%f", 5.23184+5.23184i)
}
})
}
func BenchmarkSprintfBoolean(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
......
......@@ -405,19 +405,15 @@ func (f *fmt) fmt_qc(c int64) {
}
}
// floating-point
func doPrec(f *fmt, def int) int {
// fmt_float formats a float64. It assumes that verb is a valid format specifier
// for strconv.AppendFloat and therefore fits into a byte.
func (f *fmt) fmt_float(v float64, size int, verb rune, prec int) {
// Explicit precision in format specifier overrules default precision.
if f.precPresent {
return f.prec
prec = f.prec
}
return def
}
// formatFloat formats a float64; it is an efficient equivalent to f.pad(strconv.FormatFloat()...).
func (f *fmt) formatFloat(v float64, verb byte, prec, n int) {
// Format number, reserving space for leading + sign if needed.
num := strconv.AppendFloat(f.intbuf[:1], v, verb, prec, n)
num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)
if num[1] == '-' || num[1] == '+' {
num = num[1:]
} else {
......@@ -458,86 +454,3 @@ func (f *fmt) formatFloat(v float64, verb byte, prec, n int) {
// No sign to show and the number is positive; just print the unsigned number.
f.pad(num[1:])
}
// fmt_e64 formats a float64 in the form -1.23e+12.
func (f *fmt) fmt_e64(v float64) { f.formatFloat(v, 'e', doPrec(f, 6), 64) }
// fmt_E64 formats a float64 in the form -1.23E+12.
func (f *fmt) fmt_E64(v float64) { f.formatFloat(v, 'E', doPrec(f, 6), 64) }
// fmt_f64 formats a float64 in the form -1.23.
func (f *fmt) fmt_f64(v float64) { f.formatFloat(v, 'f', doPrec(f, 6), 64) }
// fmt_g64 formats a float64 in the 'f' or 'e' form according to size.
func (f *fmt) fmt_g64(v float64) { f.formatFloat(v, 'g', doPrec(f, -1), 64) }
// fmt_G64 formats a float64 in the 'f' or 'E' form according to size.
func (f *fmt) fmt_G64(v float64) { f.formatFloat(v, 'G', doPrec(f, -1), 64) }
// fmt_fb64 formats a float64 in the form -123p3 (exponent is power of 2).
func (f *fmt) fmt_fb64(v float64) { f.formatFloat(v, 'b', 0, 64) }
// float32
// cannot defer to float64 versions
// because it will get rounding wrong in corner cases.
// fmt_e32 formats a float32 in the form -1.23e+12.
func (f *fmt) fmt_e32(v float32) { f.formatFloat(float64(v), 'e', doPrec(f, 6), 32) }
// fmt_E32 formats a float32 in the form -1.23E+12.
func (f *fmt) fmt_E32(v float32) { f.formatFloat(float64(v), 'E', doPrec(f, 6), 32) }
// fmt_f32 formats a float32 in the form -1.23.
func (f *fmt) fmt_f32(v float32) { f.formatFloat(float64(v), 'f', doPrec(f, 6), 32) }
// fmt_g32 formats a float32 in the 'f' or 'e' form according to size.
func (f *fmt) fmt_g32(v float32) { f.formatFloat(float64(v), 'g', doPrec(f, -1), 32) }
// fmt_G32 formats a float32 in the 'f' or 'E' form according to size.
func (f *fmt) fmt_G32(v float32) { f.formatFloat(float64(v), 'G', doPrec(f, -1), 32) }
// fmt_fb32 formats a float32 in the form -123p3 (exponent is power of 2).
func (f *fmt) fmt_fb32(v float32) { f.formatFloat(float64(v), 'b', 0, 32) }
// fmt_c64 formats a complex64 according to the verb.
func (f *fmt) fmt_c64(v complex64, verb rune) {
f.fmt_complex(float64(real(v)), float64(imag(v)), 32, verb)
}
// fmt_c128 formats a complex128 according to the verb.
func (f *fmt) fmt_c128(v complex128, verb rune) {
f.fmt_complex(real(v), imag(v), 64, verb)
}
// fmt_complex formats a complex number as (r+ji).
func (f *fmt) fmt_complex(r, j float64, size int, verb rune) {
f.buf.WriteByte('(')
oldPlus := f.plus
oldSpace := f.space
for i := 0; ; i++ {
switch verb {
case 'b':
f.formatFloat(r, 'b', 0, size)
case 'e':
f.formatFloat(r, 'e', doPrec(f, 6), size)
case 'E':
f.formatFloat(r, 'E', doPrec(f, 6), size)
case 'f', 'F':
f.formatFloat(r, 'f', doPrec(f, 6), size)
case 'g':
f.formatFloat(r, 'g', doPrec(f, -1), size)
case 'G':
f.formatFloat(r, 'G', doPrec(f, -1), size)
}
if i != 0 {
break
}
// Imaginary part always has a sign.
f.plus = true
f.space = false
r = j
}
f.space = oldSpace
f.plus = oldPlus
f.buf.WriteString("i)")
}
......@@ -441,61 +441,39 @@ func (p *pp) fmtUint64(v uint64, verb rune) {
}
}
func (p *pp) fmtFloat32(v float32, verb rune) {
// fmtFloat formats a float. The default precision for each verb
// is specified as last argument in the call to fmt_float.
func (p *pp) fmtFloat(v float64, size int, verb rune) {
switch verb {
case 'b':
p.fmt.fmt_fb32(v)
case 'e':
p.fmt.fmt_e32(v)
case 'E':
p.fmt.fmt_E32(v)
case 'f', 'F':
p.fmt.fmt_f32(v)
case 'g', 'v':
p.fmt.fmt_g32(v)
case 'G':
p.fmt.fmt_G32(v)
default:
p.badVerb(verb)
}
}
func (p *pp) fmtFloat64(v float64, verb rune) {
switch verb {
case 'b':
p.fmt.fmt_fb64(v)
case 'e':
p.fmt.fmt_e64(v)
case 'E':
p.fmt.fmt_E64(v)
case 'f', 'F':
p.fmt.fmt_f64(v)
case 'g', 'v':
p.fmt.fmt_g64(v)
case 'G':
p.fmt.fmt_G64(v)
default:
p.badVerb(verb)
}
}
func (p *pp) fmtComplex64(v complex64, verb rune) {
switch verb {
case 'b', 'e', 'E', 'f', 'F', 'g', 'G':
p.fmt.fmt_c64(v, verb)
case 'v':
p.fmt.fmt_c64(v, 'g')
p.fmt.fmt_float(v, size, 'g', -1)
case 'b', 'g', 'G':
p.fmt.fmt_float(v, size, verb, -1)
case 'f', 'e', 'E':
p.fmt.fmt_float(v, size, verb, 6)
case 'F':
p.fmt.fmt_float(v, size, 'f', 6)
default:
p.badVerb(verb)
}
}
func (p *pp) fmtComplex128(v complex128, verb rune) {
// fmtComplex formats a complex number v with
// r = real(v) and j = imag(v) as (r+ji) using
// fmtFloat for r and j formatting.
func (p *pp) fmtComplex(v complex128, size int, verb rune) {
// Make sure any unsupported verbs are found before the
// calls to fmtFloat to not generate an incorrect error string.
switch verb {
case 'b', 'e', 'E', 'f', 'F', 'g', 'G':
p.fmt.fmt_c128(v, verb)
case 'v':
p.fmt.fmt_c128(v, 'g')
case 'v', 'b', 'g', 'G', 'f', 'F', 'e', 'E':
oldPlus := p.fmt.plus
p.buf.WriteByte('(')
p.fmtFloat(real(v), size/2, verb)
// Imaginary part always has a sign.
p.fmt.plus = true
p.fmtFloat(imag(v), size/2, verb)
p.buf.WriteString("i)")
p.fmt.plus = oldPlus
default:
p.badVerb(verb)
}
......@@ -744,13 +722,13 @@ func (p *pp) printArg(arg interface{}, verb rune, depth int) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat32(f, verb)
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat64(f, verb)
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex64(f, verb)
p.fmtComplex(complex128(f), 64, verb)
case complex128:
p.fmtComplex128(f, verb)
p.fmtComplex(f, 128, verb)
case int:
p.fmtInt64(int64(f), verb)
case int8:
......@@ -845,18 +823,14 @@ BigSwitch:
p.fmtInt64(f.Int(), verb)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
p.fmtUint64(f.Uint(), verb)
case reflect.Float32, reflect.Float64:
if f.Type().Size() == 4 {
p.fmtFloat32(float32(f.Float()), verb)
} else {
p.fmtFloat64(f.Float(), verb)
}
case reflect.Complex64, reflect.Complex128:
if f.Type().Size() == 8 {
p.fmtComplex64(complex64(f.Complex()), verb)
} else {
p.fmtComplex128(f.Complex(), verb)
}
case reflect.Float32:
p.fmtFloat(f.Float(), 32, verb)
case reflect.Float64:
p.fmtFloat(f.Float(), 64, verb)
case reflect.Complex64:
p.fmtComplex(f.Complex(), 64, verb)
case reflect.Complex128:
p.fmtComplex(f.Complex(), 128, verb)
case reflect.String:
p.fmtString(f.String(), verb)
case reflect.Map:
......
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