Skip to content

Commit 51cfe68

Browse files
committed
math/big: provide support for conversion bases up to 62
Increase MaxBase from 36 to 62 and extend the conversion alphabet with the upper-case letters 'A' to 'Z'. For int conversions with bases <= 36, the letters 'A' to 'Z' have the same values (10 to 35) as the corresponding lower-case letters. For conversion bases > 36 up to 62, the upper-case letters have the values 36 to 61. Added MaxBase to api/except.txt: Clients should not make assumptions about the value of MaxBase being constant. The core of the change is in natconv.go. The remaining changes are adjusted tests and documentation. Fixes #21558. Change-Id: I5f74da633caafca03993e13f32ac9546c572cc84 Reviewed-on: https://go-review.googlesource.com/65970 Reviewed-by: Martin Möhrmann <[email protected]>
1 parent cf01e6f commit 51cfe68

File tree

7 files changed

+57
-15
lines changed

7 files changed

+57
-15
lines changed

api/except.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pkg encoding/json, method (*RawMessage) MarshalJSON() ([]uint8, error)
2+
pkg math/big, const MaxBase = 36
23
pkg math/big, type Word uintptr
34
pkg net, func ListenUnixgram(string, *UnixAddr) (*UDPConn, error)
45
pkg os (linux-arm), const O_SYNC = 4096

api/next.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg math/big, const MaxBase = 62

src/math/big/int.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,11 @@ func (x *Int) IsUint64() bool {
389389
// ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and a
390390
// ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base is 10.
391391
//
392+
// For bases <= 36, lower and upper case letters are considered the same:
393+
// The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35.
394+
// For bases > 36, the upper case letters 'A' to 'Z' represent the digit
395+
// values 36 to 61.
396+
//
392397
func (z *Int) SetString(s string, base int) (*Int, bool) {
393398
return z.setFromScanner(strings.NewReader(s), base)
394399
}

src/math/big/intconv.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import (
1313
)
1414

1515
// Text returns the string representation of x in the given base.
16-
// Base must be between 2 and 36, inclusive. The result uses the
17-
// lower-case letters 'a' to 'z' for digit values >= 10. No base
18-
// prefix (such as "0x") is added to the string.
16+
// Base must be between 2 and 62, inclusive. The result uses the
17+
// lower-case letters 'a' to 'z' for digit values 10 to 35, and
18+
// the upper-case letters 'A' to 'Z' for digit values 36 to 61.
19+
// No prefix (such as "0x") is added to the string.
1920
func (x *Int) Text(base int) string {
2021
if x == nil {
2122
return "<nil>"

src/math/big/intconv_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ var stringTests = []struct {
5656
{"-0b111", "-7", 0, -7, true},
5757
{"0b1001010111", "599", 0, 0x257, true},
5858
{"1001010111", "1001010111", 2, 0x257, true},
59+
{"A", "a", 36, 10, true},
60+
{"A", "A", 37, 36, true},
61+
{"ABCXYZ", "abcxyz", 36, 623741435, true},
62+
{"ABCXYZ", "ABCXYZ", 62, 33536793425, true},
5963
}
6064

6165
func TestIntText(t *testing.T) {
@@ -135,8 +139,16 @@ func TestGetString(t *testing.T) {
135139
}
136140
}
137141

138-
if got := fmt.Sprintf(format(test.base), z); got != test.out {
139-
t.Errorf("#%db got %s; want %s", i, got, test.out)
142+
f := format(test.base)
143+
got := fmt.Sprintf(f, z)
144+
if f == "%d" {
145+
if got != fmt.Sprintf("%d", test.val) {
146+
t.Errorf("#%db got %s; want %d", i, got, test.val)
147+
}
148+
} else {
149+
if got != test.out {
150+
t.Errorf("#%dc got %s; want %s", i, got, test.out)
151+
}
140152
}
141153
}
142154
}

src/math/big/natconv.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import (
1515
"sync"
1616
)
1717

18-
const digits = "0123456789abcdefghijklmnopqrstuvwxyz"
18+
const digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
1919

20-
// Note: MaxBase = len(digits), but it must remain a rune constant
20+
// Note: MaxBase = len(digits), but it must remain an untyped rune constant
2121
// for API compatibility.
2222

2323
// MaxBase is the largest number base accepted for string conversions.
24-
const MaxBase = 'z' - 'a' + 10 + 1
24+
const MaxBase = 10 + ('z' - 'a' + 1) + ('Z' - 'A' + 1)
25+
const maxBaseSmall = 10 + ('z' - 'a' + 1)
2526

2627
// maxPow returns (b**n, n) such that b**n is the largest power b**n <= _M.
2728
// For instance maxPow(10) == (1e19, 19) for 19 decimal digits in a 64bit Word.
@@ -59,11 +60,11 @@ func pow(x Word, n int) (p Word) {
5960
// It returns the corresponding natural number res, the actual base b,
6061
// a digit count, and a read or syntax error err, if any.
6162
//
62-
// number = [ prefix ] mantissa .
63-
// prefix = "0" [ "x" | "X" | "b" | "B" ] .
64-
// mantissa = digits | digits "." [ digits ] | "." digits .
65-
// digits = digit { digit } .
66-
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
63+
// number = [ prefix ] mantissa .
64+
// prefix = "0" [ "x" | "X" | "b" | "B" ] .
65+
// mantissa = digits | digits "." [ digits ] | "." digits .
66+
// digits = digit { digit } .
67+
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
6768
//
6869
// Unless fracOk is set, the base argument must be 0 or a value between
6970
// 2 and MaxBase. If fracOk is set, the base argument must be one of
@@ -80,6 +81,11 @@ func pow(x Word, n int) (p Word) {
8081
// is permitted. The result value is computed as if there were no period
8182
// present; and the count value is used to determine the fractional part.
8283
//
84+
// For bases <= 36, lower and upper case letters are considered the same:
85+
// The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35.
86+
// For bases > 36, the upper case letters 'A' to 'Z' represent the digit
87+
// values 36 to 61.
88+
//
8389
// A result digit count > 0 corresponds to the number of (non-prefix) digits
8490
// parsed. A digit count <= 0 indicates the presence of a period (if fracOk
8591
// is set, only), and -count is the number of fractional digits found.
@@ -173,7 +179,11 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in
173179
case 'a' <= ch && ch <= 'z':
174180
d1 = Word(ch - 'a' + 10)
175181
case 'A' <= ch && ch <= 'Z':
176-
d1 = Word(ch - 'A' + 10)
182+
if b <= maxBaseSmall {
183+
d1 = Word(ch - 'A' + 10)
184+
} else {
185+
d1 = Word(ch - 'A' + maxBaseSmall)
186+
}
177187
default:
178188
d1 = MaxBase + 1
179189
}

src/math/big/natconv_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import (
1313
"testing"
1414
)
1515

16+
func TestMaxBase(t *testing.T) {
17+
if MaxBase != len(digits) {
18+
t.Fatalf("%d != %d", MaxBase, len(digits))
19+
}
20+
}
21+
1622
// log2 computes the integer binary logarithm of x.
1723
// The result is the integer n for which 2^n <= x < 2^(n+1).
1824
// If x == 0, the result is -1.
@@ -61,6 +67,7 @@ var strTests = []struct {
6167
{nat{0xdeadbeef}, 16, "deadbeef"},
6268
{nat{0x229be7}, 17, "1a2b3c"},
6369
{nat{0x309663e6}, 32, "o9cov6"},
70+
{nat{0x309663e6}, 62, "TakXI"},
6471
}
6572

6673
func TestString(t *testing.T) {
@@ -110,6 +117,7 @@ var natScanTests = []struct {
110117
{s: "?"},
111118
{base: 10},
112119
{base: 36},
120+
{base: 62},
113121
{s: "?", base: 10},
114122
{s: "0x"},
115123
{s: "345", base: 2},
@@ -124,6 +132,7 @@ var natScanTests = []struct {
124132
{"0", 0, false, nil, 10, 1, true, 0},
125133
{"0", 10, false, nil, 10, 1, true, 0},
126134
{"0", 36, false, nil, 36, 1, true, 0},
135+
{"0", 62, false, nil, 62, 1, true, 0},
127136
{"1", 0, false, nat{1}, 10, 1, true, 0},
128137
{"1", 10, false, nat{1}, 10, 1, true, 0},
129138
{"0 ", 0, false, nil, 10, 1, true, ' '},
@@ -135,8 +144,11 @@ var natScanTests = []struct {
135144
{"03271", 0, false, nat{03271}, 8, 4, true, 0},
136145
{"10ab", 0, false, nat{10}, 10, 2, true, 'a'},
137146
{"1234567890", 0, false, nat{1234567890}, 10, 10, true, 0},
147+
{"A", 36, false, nat{10}, 36, 1, true, 0},
148+
{"A", 37, false, nat{36}, 37, 1, true, 0},
138149
{"xyz", 36, false, nat{(33*36+34)*36 + 35}, 36, 3, true, 0},
139-
{"xyz?", 36, false, nat{(33*36+34)*36 + 35}, 36, 3, true, '?'},
150+
{"XYZ?", 36, false, nat{(33*36+34)*36 + 35}, 36, 3, true, '?'},
151+
{"XYZ?", 62, false, nat{(59*62+60)*62 + 61}, 62, 3, true, '?'},
140152
{"0x", 16, false, nil, 16, 1, true, 'x'},
141153
{"0xdeadbeef", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
142154
{"0XDEADBEEF", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},

0 commit comments

Comments
 (0)