From 12c736158affe5c112d119142bb9403d193080de Mon Sep 17 00:00:00 2001 From: "Michael T. Jones" Date: Thu, 21 Jul 2011 14:29:08 -0700 Subject: [PATCH] big: refine printf formatting and optimize string conversion Now handles standard precision specifications, standard interactions of redundant specifications (such as precision and zero-fill), handles the special case of precision specified but equal to zero, and generates the output without recursive calls to format/printf to be clearer and faster. R=gri, mtj, gri CC=golang-dev https://golang.org/cl/4703050 --- src/pkg/big/int.go | 132 ++++++++++++++++++++++------------------ src/pkg/big/int_test.go | 106 +++++++++++++++++++++++++++++--- 2 files changed, 171 insertions(+), 67 deletions(-) diff --git a/src/pkg/big/int.go b/src/pkg/big/int.go index 0aad189ad02..a9a70203311 100755 --- a/src/pkg/big/int.go +++ b/src/pkg/big/int.go @@ -316,9 +316,26 @@ func charset(ch int) string { return "" // unknown format } +// write count copies of text to s +func writeMultiple(s fmt.State, text string, count int) { + if len(text) > 0 { + b := []byte(text) + for ; count > 0; count-- { + s.Write(b) + } + } +} + // Format is a support routine for fmt.Formatter. It accepts // the formats 'b' (binary), 'o' (octal), 'd' (decimal), 'x' // (lowercase hexadecimal), and 'X' (uppercase hexadecimal). +// Also supported are the full suite of "Printf" style format +// codes for integral types, including PLUS, MINUS, and SPACE +// for sign control, HASH for leading ZERO in octal and for +// hexadecimal, a leading "0x" or "0X" for "%#x" and "%#X" +// respectively, specification of minimum digits precision, +// output field width, space or zero padding, and left or +// right justification. // func (x *Int) Format(s fmt.State, ch int) { cs := charset(ch) @@ -334,72 +351,69 @@ func (x *Int) Format(s fmt.State, ch int) { return } - // determine format - format := "%s" - if s.Flag('#') { - switch ch { - case 'o': - format = "0%s" - case 'x': - format = "0x%s" - case 'X': - format = "0X%s" - } - } - t := fmt.Sprintf(format, x.abs.string(cs)) - - // insert spaces in hexadecimal formats if needed - if len(t) > 0 && s.Flag(' ') && (ch == 'x' || ch == 'X') { - spaces := (len(t)+1)/2 - 1 - spaced := make([]byte, len(t)+spaces) - var i, j int - spaced[i] = t[j] - i++ - j++ - if len(t)&1 == 0 { - spaced[i] = t[j] - i++ - j++ - } - for j < len(t) { - spaced[i] = ' ' - i++ - spaced[i] = t[j] - i++ - j++ - spaced[i] = t[j] - i++ - j++ - } - t = string(spaced) - } - - // determine sign prefix - prefix := "" + // determine sign character + sign := "" switch { case x.neg: - prefix = "-" - case s.Flag('+'): - prefix = "+" - case s.Flag(' ') && ch != 'x' && ch != 'X': - prefix = " " + sign = "-" + case s.Flag('+'): // supersedes ' ' when both specified + sign = "+" + case s.Flag(' '): + sign = " " } - // fill to minimum width and prepend sign prefix - if width, ok := s.Width(); ok && len(t)+len(prefix) < width { - if s.Flag('0') { - t = fmt.Sprintf("%s%0*d%s", prefix, width-len(t)-len(prefix), 0, t) - } else { - if s.Flag('-') { - width = -width - } - t = fmt.Sprintf("%*s", width, prefix+t) + // determine prefix characters for indicating output base + prefix := "" + if s.Flag('#') { + switch ch { + case 'o': // octal + prefix = "0" + case 'x': // hexadecimal + prefix = "0x" + case 'X': + prefix = "0X" } - } else if prefix != "" { - t = prefix + t } - fmt.Fprint(s, t) + // determine digits with base set by len(cs) and digit characters from cs + digits := x.abs.string(cs) + + // number of characters for the Sprintf family's three classes of number padding + var left int // space characters to left of digits for right justification ("%8d") + var zeroes int // zero characters (acutally cs[0]) as left-most digits ("%.8d") + var right int // space characters to right of digits for left justification ("%-8d") + + // determine number padding from precision: the least number of digits to output + precision, precisionSet := s.Precision() + if precisionSet { + switch { + case len(digits) < precision: + zeroes = precision - len(digits) // count of zero padding + case digits == "0" && precision == 0: + return // print nothing if zero value (x == 0) and zero precision ("." or ".0") + } + } + + // determine field pad from width: the least number of characters to output + length := len(sign) + len(prefix) + zeroes + len(digits) + if width, widthSet := s.Width(); widthSet && length < width { // pad as specified + switch d := width - length; { + case s.Flag('-'): // pad on the right with spaces. supersedes '0' when both specified + right = d + case s.Flag('0') && !precisionSet: // pad with zeroes unless precision also specified + zeroes = d + default: // pad on the left with spaces + left = d + } + } + + // print Int as [left pad][sign][prefix][zero pad][digits][right pad] + writeMultiple(s, " ", left) + writeMultiple(s, sign, 1) + writeMultiple(s, prefix, 1) + writeMultiple(s, "0", zeroes) + writeMultiple(s, digits, 1) + writeMultiple(s, " ", right) } // scan sets z to the integer value corresponding to the longest possible prefix diff --git a/src/pkg/big/int_test.go b/src/pkg/big/int_test.go index 593d38ebb80..03446d6ae2d 100755 --- a/src/pkg/big/int_test.go +++ b/src/pkg/big/int_test.go @@ -366,12 +366,10 @@ var formatTests = []struct { {"1234", "%-5d", "1234 "}, {"1234", "%x", "4d2"}, {"1234", "%X", "4D2"}, - {"1234", "% x", "4 d2"}, {"-1234", "%3x", "-4d2"}, {"-1234", "%4x", "-4d2"}, {"-1234", "%5x", " -4d2"}, {"-1234", "%-5x", "-4d2 "}, - {"-1234", "% x", "-4 d2"}, {"1234", "%03d", "1234"}, {"1234", "%04d", "1234"}, {"1234", "%05d", "01234"}, @@ -380,11 +378,103 @@ var formatTests = []struct { {"1234", "%+06d", "+01234"}, {"1234", "% 06d", " 01234"}, {"1234", "%-6d", "1234 "}, - {"1234", "%-06d", "001234"}, - {"-1234", "%-06d", "-01234"}, - {"10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", // 10**100 - "% x", - "12 49 ad 25 94 c3 7c eb 0b 27 84 c4 ce 0b f3 8a ce 40 8e 21 1a 7c aa b2 43 08 a8 2e 8f 10 00 00 00 00 00 00 00 00 00 00 00 00"}, + {"1234", "%-06d", "1234 "}, + {"-1234", "%-06d", "-1234 "}, + + {"1234", "%.3d", "1234"}, + {"1234", "%.4d", "1234"}, + {"1234", "%.5d", "01234"}, + {"1234", "%.6d", "001234"}, + {"-1234", "%.3d", "-1234"}, + {"-1234", "%.4d", "-1234"}, + {"-1234", "%.5d", "-01234"}, + {"-1234", "%.6d", "-001234"}, + + {"1234", "%8.3d", " 1234"}, + {"1234", "%8.4d", " 1234"}, + {"1234", "%8.5d", " 01234"}, + {"1234", "%8.6d", " 001234"}, + {"-1234", "%8.3d", " -1234"}, + {"-1234", "%8.4d", " -1234"}, + {"-1234", "%8.5d", " -01234"}, + {"-1234", "%8.6d", " -001234"}, + + {"1234", "%+8.3d", " +1234"}, + {"1234", "%+8.4d", " +1234"}, + {"1234", "%+8.5d", " +01234"}, + {"1234", "%+8.6d", " +001234"}, + {"-1234", "%+8.3d", " -1234"}, + {"-1234", "%+8.4d", " -1234"}, + {"-1234", "%+8.5d", " -01234"}, + {"-1234", "%+8.6d", " -001234"}, + + {"1234", "% 8.3d", " 1234"}, + {"1234", "% 8.4d", " 1234"}, + {"1234", "% 8.5d", " 01234"}, + {"1234", "% 8.6d", " 001234"}, + {"-1234", "% 8.3d", " -1234"}, + {"-1234", "% 8.4d", " -1234"}, + {"-1234", "% 8.5d", " -01234"}, + {"-1234", "% 8.6d", " -001234"}, + + {"1234", "%.3x", "4d2"}, + {"1234", "%.4x", "04d2"}, + {"1234", "%.5x", "004d2"}, + {"1234", "%.6x", "0004d2"}, + {"-1234", "%.3x", "-4d2"}, + {"-1234", "%.4x", "-04d2"}, + {"-1234", "%.5x", "-004d2"}, + {"-1234", "%.6x", "-0004d2"}, + + {"1234", "%8.3x", " 4d2"}, + {"1234", "%8.4x", " 04d2"}, + {"1234", "%8.5x", " 004d2"}, + {"1234", "%8.6x", " 0004d2"}, + {"-1234", "%8.3x", " -4d2"}, + {"-1234", "%8.4x", " -04d2"}, + {"-1234", "%8.5x", " -004d2"}, + {"-1234", "%8.6x", " -0004d2"}, + + {"1234", "%+8.3x", " +4d2"}, + {"1234", "%+8.4x", " +04d2"}, + {"1234", "%+8.5x", " +004d2"}, + {"1234", "%+8.6x", " +0004d2"}, + {"-1234", "%+8.3x", " -4d2"}, + {"-1234", "%+8.4x", " -04d2"}, + {"-1234", "%+8.5x", " -004d2"}, + {"-1234", "%+8.6x", " -0004d2"}, + + {"1234", "% 8.3x", " 4d2"}, + {"1234", "% 8.4x", " 04d2"}, + {"1234", "% 8.5x", " 004d2"}, + {"1234", "% 8.6x", " 0004d2"}, + {"1234", "% 8.7x", " 00004d2"}, + {"1234", "% 8.8x", " 000004d2"}, + {"-1234", "% 8.3x", " -4d2"}, + {"-1234", "% 8.4x", " -04d2"}, + {"-1234", "% 8.5x", " -004d2"}, + {"-1234", "% 8.6x", " -0004d2"}, + {"-1234", "% 8.7x", "-00004d2"}, + {"-1234", "% 8.8x", "-000004d2"}, + + {"1234", "%-8.3d", "1234 "}, + {"1234", "%-8.4d", "1234 "}, + {"1234", "%-8.5d", "01234 "}, + {"1234", "%-8.6d", "001234 "}, + {"1234", "%-8.7d", "0001234 "}, + {"1234", "%-8.8d", "00001234"}, + {"-1234", "%-8.3d", "-1234 "}, + {"-1234", "%-8.4d", "-1234 "}, + {"-1234", "%-8.5d", "-01234 "}, + {"-1234", "%-8.6d", "-001234 "}, + {"-1234", "%-8.7d", "-0001234"}, + {"-1234", "%-8.8d", "-00001234"}, + + {"16777215", "%b", "111111111111111111111111"}, // 2**24 - 1 + + {"0", "%.d", ""}, + {"0", "%.0d", ""}, + {"0", "%3.d", ""}, } func TestFormat(t *testing.T) { @@ -399,7 +489,7 @@ func TestFormat(t *testing.T) { } output := fmt.Sprintf(test.format, x) if output != test.output { - t.Errorf("#%d got %q; want %q", i, output, test.output) + t.Errorf("#%d got %q; want %q, {%q, %q, %q}", i, output, test.output, test.input, test.format, test.output) } } }