Eliminated unnecessary string conversions throughout and removed
(internal) capability for arbitrary character sets in conversion
routines (functionality was not exported and not used internally).
benchmark old ns/op new ns/op delta
BenchmarkDecimalConversion-8 198283 187085 -5.65%
BenchmarkStringPiParallel-8 46116 47822 +3.70%
BenchmarkString10Base2-8 216 166 -23.15%
BenchmarkString100Base2-8 886 762 -14.00%
BenchmarkString1000Base2-8 7296 6625 -9.20%
BenchmarkString10000Base2-8 72371 65563 -9.41%
BenchmarkString100000Base2-8 725849 672766 -7.31%
BenchmarkString10Base8-8 160 114 -28.75%
BenchmarkString100Base8-8 398 309 -22.36%
BenchmarkString1000Base8-8 2650 2244 -15.32%
BenchmarkString10000Base8-8 24974 21745 -12.93%
BenchmarkString100000Base8-8 245457 217489 -11.39%
BenchmarkString10Base10-8 337 288 -14.54%
BenchmarkString100Base10-8 1298 1046 -19.41%
BenchmarkString1000Base10-8 6200 5752 -7.23%
BenchmarkString10000Base10-8 24942 22589 -9.43%
BenchmarkString100000Base10-8
8012921 7947152 -0.82%
BenchmarkString10Base16-8 156 107 -31.41%
BenchmarkString100Base16-8 344 255 -25.87%
BenchmarkString1000Base16-8 2067 1705 -17.51%
BenchmarkString10000Base16-8 19026 16112 -15.32%
BenchmarkString100000Base16-8 184038 163457 -11.18%
Change-Id: I68bd807529bd9b985f4b6ac2a87764bcc1a7d2f7
Reviewed-on: https://go-review.googlesource.com/14926
Reviewed-by: Alan Donovan <adonovan@google.com>
}
// Convert mantissa into decimal representation.
- s := m.decimalString() // TODO(gri) avoid string conversion here
+ s := m.itoa(10)
n := len(s)
x.exp = n
// Trim trailing zeros; instead the exponent is tracking
package big
import (
+ "bytes"
"fmt"
"strconv"
- "strings"
)
// Text converts the floating-point number x to a string according
m = nat(nil).shr(m, uint(w-x.prec))
}
- buf = append(buf, m.decimalString()...)
+ buf = append(buf, m.itoa(10)...)
buf = append(buf, 'p')
e := int64(x.exp) - int64(x.prec)
if e >= 0 {
m = m[i:]
buf = append(buf, "0x."...)
- buf = append(buf, strings.TrimRight(m.hexString(), "0")...)
+ buf = append(buf, bytes.TrimRight(m.itoa(16), "0")...)
buf = append(buf, 'p')
if x.exp >= 0 {
buf = append(buf, '+')
case x == nil:
return "<nil>"
case x.neg:
- return "-" + x.abs.decimalString()
+ return "-" + string(x.abs.itoa(10))
}
- return x.abs.decimalString()
-}
-
-func charset(ch rune) string {
- switch ch {
- case 'b':
- return lowercaseDigits[0:2]
- case 'o':
- return lowercaseDigits[0:8]
- case 'd', 's', 'v':
- return lowercaseDigits[0:10]
- case 'x':
- return lowercaseDigits[0:16]
- case 'X':
- return uppercaseDigits[0:16]
- }
- return "" // unknown format
+ return string(x.abs.itoa(10))
}
// write count copies of text to s
// right justification.
//
func (x *Int) Format(s fmt.State, ch rune) {
- cs := charset(ch)
-
- // special cases
- switch {
- case cs == "":
+ // determine base
+ var base int
+ switch ch {
+ case 'b':
+ base = 2
+ case 'o':
+ base = 8
+ case 'd', 's', 'v':
+ base = 10
+ case 'x', 'X':
+ base = 16
+ default:
// unknown format
fmt.Fprintf(s, "%%!%c(big.Int=%s)", ch, x.String())
return
- case x == nil:
+ }
+
+ if x == nil {
fmt.Fprint(s, "<nil>")
return
}
}
}
- // determine digits with base set by len(cs) and digit characters from cs
- digits := x.abs.string(cs)
+ digits := x.abs.itoa(base)
+ if ch == 'X' {
+ // faster than bytes.ToUpper
+ for i, d := range digits {
+ if 'a' <= d && d <= 'z' {
+ digits[i] = 'A' + (d - 'a')
+ }
+ }
+ }
// number of characters for the three classes of number padding
var left int // space characters to left of digits for right justification ("%8d")
switch {
case len(digits) < precision:
zeros = precision - len(digits) // count of zero padding
- case digits == "0" && precision == 0:
+ case len(digits) == 1 && digits[0] == '0' && precision == 0:
return // print nothing if zero value (x == 0) and zero precision ("." or ".0")
}
}
writeMultiple(s, sign, 1)
writeMultiple(s, prefix, 1)
writeMultiple(s, "0", zeros)
- writeMultiple(s, digits, 1)
+ s.Write(digits)
writeMultiple(s, " ", right)
}
func TestMulRangeN(t *testing.T) {
for i, r := range mulRangesN {
- prod := nat(nil).mulRange(r.a, r.b).decimalString()
+ prod := string(nat(nil).mulRange(r.a, r.b).itoa(10))
if prod != r.prod {
t.Errorf("#%d: got %s; want %s", i, prod, r.prod)
}
for i := uint(0); i <= 3*_W; i++ {
n := y.trailingZeroBits()
if n != i {
- t.Errorf("got 0x%s.trailingZeroBits() = %d; want %d", y.hexString(), n, i)
+ t.Errorf("got 0x%s.trailingZeroBits() = %d; want %d", y.itoa(16), n, i)
}
y = y.shl(y, 1)
}
z := nat(nil).montgomery(x, y, m, k0, len(m))
z = z.norm()
if z.cmp(out) != 0 {
- t.Errorf("#%d got %s want %s", i, z.decimalString(), out.decimalString())
+ t.Errorf("#%d got %s want %s", i, z.itoa(10), out.itoa(10))
}
}
}
z := nat(nil).expNN(x, y, m)
if z.cmp(out) != 0 {
- t.Errorf("#%d got %s want %s", i, z.decimalString(), out.decimalString())
+ t.Errorf("#%d got %s want %s", i, z.itoa(10), out.itoa(10))
}
}
}
func TestFibo(t *testing.T) {
for i, want := range fiboNums {
n := i * 10
- got := fibo(n).decimalString()
+ got := string(fibo(n).itoa(10))
if got != want {
t.Errorf("fibo(%d) failed: got %s want %s", n, got, want)
}
"sync"
)
+const digits = "0123456789abcdefghijklmnopqrstuvwxyz"
+
+// Note: MaxBase = len(digits), but it must remain a rune constant
+// for API compatibility.
+
// MaxBase is the largest number base accepted for string conversions.
const MaxBase = 'z' - 'a' + 10 + 1
return
}
-// Character sets for string conversion.
-const (
- lowercaseDigits = "0123456789abcdefghijklmnopqrstuvwxyz"
- uppercaseDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-)
-
-// decimalString returns a decimal representation of x.
-// It calls x.string with the charset "0123456789".
-func (x nat) decimalString() string {
- return x.string(lowercaseDigits[:10])
-}
-
-// hexString returns a hexadecimal representation of x.
-// It calls x.string with the charset "0123456789abcdef".
-func (x nat) hexString() string {
- return x.string(lowercaseDigits[:16])
-}
-
-// string converts x to a string using digits from a charset; a digit with
-// value d is represented by charset[d]. The conversion base is determined
-// by len(charset), which must be >= 2 and <= 256.
-func (x nat) string(charset string) string {
- b := Word(len(charset))
- if b < 2 || b > 256 {
- panic("invalid character set length")
+// itoa converts x to an ASCII representation in the given base;
+// base must be between 2 and MaxBase, inclusive.
+func (x nat) itoa(base int) []byte {
+ if base < 2 || base > MaxBase {
+ panic("invalid base")
}
// x == 0
if len(x) == 0 {
- return string(charset[0])
+ return []byte("0")
}
// len(x) > 0
// allocate buffer for conversion
- i := int(float64(x.bitLen())/math.Log2(float64(b))) + 1 // off by one at most
+ i := int(float64(x.bitLen())/math.Log2(float64(base))) + 1 // off by 1 at most
s := make([]byte, i)
// convert power of two and non power of two bases separately
- if b == b&-b {
+ if b := Word(base); b == b&-b {
// shift is base b digit size in bits
shift := trailingZeroBits(b) // shift > 0 because b >= 2
mask := Word(1<<shift - 1)
// convert full digits
for nbits >= shift {
i--
- s[i] = charset[w&mask]
+ s[i] = digits[w&mask]
w >>= shift
nbits -= shift
}
// partial digit in current word w (== x[k-1]) and next word x[k]
w |= x[k] << nbits
i--
- s[i] = charset[w&mask]
+ s[i] = digits[w&mask]
// advance
w = x[k] >> (shift - nbits)
// convert digits of most-significant word w (omit leading zeros)
for w != 0 {
i--
- s[i] = charset[w&mask]
+ s[i] = digits[w&mask]
w >>= shift
}
q := nat(nil).set(x)
// convert q to string s in base b
- q.convertWords(s, charset, b, ndigits, bb, table)
+ q.convertWords(s, b, ndigits, bb, table)
// strip leading zeros
// (x != 0; thus s must contain at least one non-zero digit
// and the loop will terminate)
i = 0
- for zero := charset[0]; s[i] == zero; {
+ for s[i] == '0' {
i++
}
}
- return string(s[i:])
+ return s[i:]
}
// Convert words of q to base b digits in s. If q is large, it is recursively "split in half"
// ~30x for 20000 digits. Use nat_test.go's BenchmarkLeafSize tests to optimize leafSize for
// specific hardware.
//
-func (q nat) convertWords(s []byte, charset string, b Word, ndigits int, bb Word, table []divisor) {
+func (q nat) convertWords(s []byte, b Word, ndigits int, bb Word, table []divisor) {
// split larger blocks recursively
if table != nil {
// len(q) > leafSize > 0
// convert subblocks and collect results in s[:h] and s[h:]
h := len(s) - table[index].ndigits
- r.convertWords(s[h:], charset, b, ndigits, bb, table[0:index])
- s = s[:h] // == q.convertWords(s, charset, b, ndigits, bb, table[0:index+1])
+ r.convertWords(s[h:], b, ndigits, bb, table[0:index])
+ s = s[:h] // == q.convertWords(s, b, ndigits, bb, table[0:index+1])
}
}
// this appears to be faster for BenchmarkString10000Base10
// and smaller strings (but a bit slower for larger ones)
t := r / 10
- s[i] = charset[r-t<<3-t-t] // TODO(gri) replace w/ t*10 once compiler produces better code
+ s[i] = '0' + byte(r-t<<3-t-t) // TODO(gri) replace w/ t*10 once compiler produces better code
r = t
}
}
q, r = q.divW(q, bb)
for j := 0; j < ndigits && i > 0; j++ {
i--
- s[i] = charset[r%b]
+ s[i] = digits[r%b]
r /= b
}
}
}
// prepend high-order zeros
- zero := charset[0]
for i > 0 { // while need more leading zeros
i--
- s[i] = zero
+ s[i] = '0'
}
}
package big
import (
+ "bytes"
"io"
"strings"
"testing"
)
-func toString(x nat, charset string) string {
- base := len(charset)
-
+func itoa(x nat, base int) []byte {
// special cases
switch {
case base < 2:
panic("illegal base")
case len(x) == 0:
- return string(charset[0])
+ return []byte("0")
}
// allocate buffer for conversion
i--
var r Word
q, r = q.divW(q, Word(base))
- s[i] = charset[r]
+ s[i] = digits[r]
}
- return string(s[i:])
+ return s[i:]
}
var strTests = []struct {
x nat // nat value to be converted
- c string // conversion charset
+ b int // conversion base
s string // expected result
}{
- {nil, "01", "0"},
- {nat{1}, "01", "1"},
- {nat{0xc5}, "01", "11000101"},
- {nat{03271}, lowercaseDigits[:8], "3271"},
- {nat{10}, lowercaseDigits[:10], "10"},
- {nat{1234567890}, uppercaseDigits[:10], "1234567890"},
- {nat{0xdeadbeef}, lowercaseDigits[:16], "deadbeef"},
- {nat{0xdeadbeef}, uppercaseDigits[:16], "DEADBEEF"},
- {nat{0x229be7}, lowercaseDigits[:17], "1a2b3c"},
- {nat{0x309663e6}, uppercaseDigits[:32], "O9COV6"},
+ {nil, 2, "0"},
+ {nat{1}, 2, "1"},
+ {nat{0xc5}, 2, "11000101"},
+ {nat{03271}, 8, "3271"},
+ {nat{10}, 10, "10"},
+ {nat{1234567890}, 10, "1234567890"},
+ {nat{0xdeadbeef}, 16, "deadbeef"},
+ {nat{0x229be7}, 17, "1a2b3c"},
+ {nat{0x309663e6}, 32, "o9cov6"},
}
func TestString(t *testing.T) {
- // test invalid character set explicitly
+ // test invalid base explicitly
var panicStr string
func() {
defer func() {
panicStr = recover().(string)
}()
- natOne.string("0")
+ natOne.itoa(1)
}()
- if panicStr != "invalid character set length" {
- t.Errorf("expected panic for invalid character set")
+ if panicStr != "invalid base" {
+ t.Errorf("expected panic for invalid base")
}
for _, a := range strTests {
- s := a.x.string(a.c)
+ s := string(a.x.itoa(a.b))
if s != a.s {
t.Errorf("string%+v\n\tgot s = %s; want %s", a, s, a.s)
}
- x, b, _, err := nat(nil).scan(strings.NewReader(a.s), len(a.c), false)
+ x, b, _, err := nat(nil).scan(strings.NewReader(a.s), a.b, false)
if x.cmp(a.x) != 0 {
t.Errorf("scan%+v\n\tgot z = %v; want %v", a, x, a.x)
}
- if b != len(a.c) {
- t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, len(a.c))
+ if b != a.b {
+ t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, a.b)
}
if err != nil {
t.Errorf("scan%+v\n\tgot error = %s", a, err)
if err != nil {
t.Errorf("scanning pi: %s", err)
}
- if s := z.decimalString(); s != pi {
+ if s := string(z.itoa(10)); s != pi {
t.Errorf("scanning pi: got %s", s)
}
}
func BenchmarkStringPiParallel(b *testing.B) {
var x nat
x, _, _, _ = x.scan(strings.NewReader(pi), 0, false)
- if x.decimalString() != pi {
+ if string(x.itoa(10)) != pi {
panic("benchmark incorrect: conversion failed")
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
- x.decimalString()
+ x.itoa(10)
}
})
}
var z nat
z = z.expWW(x, y)
- var s string
- s = z.string(lowercaseDigits[:base])
- if t := toString(z, lowercaseDigits[:base]); t != s {
+ s := z.itoa(base)
+ if t := itoa(z, base); !bytes.Equal(s, t) {
b.Fatalf("scanning: got %s; want %s", s, t)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
- z.scan(strings.NewReader(s), base, false)
+ z.scan(bytes.NewReader(s), base, false)
}
}
b.StopTimer()
var z nat
z = z.expWW(x, y)
- z.string(lowercaseDigits[:base]) // warm divisor cache
+ z.itoa(base) // warm divisor cache
b.StartTimer()
for i := 0; i < b.N; i++ {
- _ = z.string(lowercaseDigits[:base])
+ _ = z.itoa(base)
}
}
func BenchmarkLeafSize32(b *testing.B) { LeafSizeHelper(b, 10, 32) } // try some large lengths
func BenchmarkLeafSize64(b *testing.B) { LeafSizeHelper(b, 10, 64) }
-func LeafSizeHelper(b *testing.B, base Word, size int) {
+func LeafSizeHelper(b *testing.B, base, size int) {
b.StopTimer()
originalLeafSize := leafSize
resetTable(cacheBase10.table[:])
for d := 1; d <= 10000; d *= 10 {
b.StopTimer()
var z nat
- z = z.expWW(base, Word(d)) // build target number
- _ = z.string(lowercaseDigits[:base]) // warm divisor cache
+ z = z.expWW(Word(base), Word(d)) // build target number
+ _ = z.itoa(base) // warm divisor cache
b.StartTimer()
for i := 0; i < b.N; i++ {
- _ = z.string(lowercaseDigits[:base])
+ _ = z.itoa(base)
}
}
}
func TestStringPowers(t *testing.T) {
- var b, p Word
- for b = 2; b <= 16; b++ {
+ var p Word
+ for b := 2; b <= 16; b++ {
for p = 0; p <= 512; p++ {
- x := nat(nil).expWW(b, p)
- xs := x.string(lowercaseDigits[:b])
- xs2 := toString(x, lowercaseDigits[:b])
- if xs != xs2 {
+ x := nat(nil).expWW(Word(b), p)
+ xs := x.itoa(b)
+ xs2 := itoa(x, b)
+ if !bytes.Equal(xs, xs2) {
t.Errorf("failed at %d ** %d in base %d: %s != %s", b, p, b, xs, xs2)
}
}
func (x *Rat) String() string {
s := "/1"
if len(x.b.abs) != 0 {
- s = "/" + x.b.abs.decimalString()
+ s = "/" + string(x.b.abs.itoa(10))
}
return x.a.String() + s
}
}
}
- s := q.decimalString()
+ s := string(q.itoa(10))
if x.a.neg {
s = "-" + s
}
if prec > 0 {
- rs := r.decimalString()
+ rs := string(r.itoa(10))
leadingZeros := prec - len(rs)
s += "." + strings.Repeat("0", leadingZeros) + rs
}