Skip to content

Commit 129c6e4

Browse files
committed
math/big: support new octal prefixes 0o and 0O
This CL extends the various SetString and Parse methods for Ints, Rats, and Floats to accept the new octal prefixes. The main change is in natconv.go, all other changes are documentation and test updates. Finally, this CL also fixes TestRatSetString which silently dropped certain failures. Updates #12711. Change-Id: I5ee5879e25013ba1e6eda93ff280915f25ab5d55 Reviewed-on: https://go-review.googlesource.com/c/go/+/165898 Reviewed-by: Emmanuel Odeke <[email protected]>
1 parent 2dd066d commit 129c6e4

File tree

7 files changed

+112
-58
lines changed

7 files changed

+112
-58
lines changed

src/math/big/floatconv.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
9797
fallthrough // 10**e == 5**e * 2**e
9898
case 2:
9999
exp2 += d
100+
case 8:
101+
exp2 += d * 3 // octal digits are 3 bits each
100102
case 16:
101103
exp2 += d * 4 // hexadecimal digits are 4 bits each
102104
default:
@@ -222,21 +224,21 @@ func (z *Float) pow5(n uint64) *Float {
222224
//
223225
// number = [ sign ] [ prefix ] mantissa [ exponent ] | infinity .
224226
// sign = "+" | "-" .
225-
// prefix = "0" ( "x" | "X" | "b" | "B" ) .
227+
// prefix = "0" ( "b" | "B" | "o" | "O" | "x" | "X" ) .
226228
// mantissa = digits | digits "." [ digits ] | "." digits .
227229
// exponent = ( "e" | "E" | "p" | "P" ) [ sign ] digits .
228230
// digits = digit { digit } .
229231
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
230232
// infinity = [ sign ] ( "inf" | "Inf" ) .
231233
//
232-
// The base argument must be 0, 2, 10, or 16. Providing an invalid base
234+
// The base argument must be 0, 2, 8, 10, or 16. Providing an invalid base
233235
// argument will lead to a run-time panic.
234236
//
235237
// For base 0, the number prefix determines the actual base: A prefix of
236-
// "0x" or "0X" selects base 16, and a "0b" or "0B" prefix selects
237-
// base 2; otherwise, the actual base is 10 and no prefix is accepted.
238-
// The octal prefix "0" is not supported (a leading "0" is simply
239-
// considered a "0").
238+
// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and
239+
// ``0x'' or ``0X'' selects base 16. Otherwise, the actual base is 10 and
240+
// no prefix is accepted. The octal prefix "0" is not supported (a leading
241+
// "0" is simply considered a "0").
240242
//
241243
// A "p" or "P" exponent indicates a binary (rather then decimal) exponent;
242244
// for instance "0x1.fffffffffffffp1023" (using base 0) represents the

src/math/big/floatconv_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,27 @@ func TestFloatSetFloat64String(t *testing.T) {
110110
{"0b0.01p2", 1},
111111
{"0b0.01P+2", 1},
112112

113+
// octal mantissa, decimal exponent
114+
{"0o0", 0},
115+
{"-0o0", -zero_},
116+
{"0o0e+10", 0},
117+
{"-0o0e-10", -zero_},
118+
{"0o12", 10},
119+
{"0O12E2", 1000},
120+
{"0o.4", 0.5},
121+
{"0o.01", 0.015625},
122+
{"0o.01e3", 15.625},
123+
124+
// octal mantissa, binary exponent
125+
{"0o0p+10", 0},
126+
{"-0o0p-10", -zero_},
127+
{"0o.12p6", 10},
128+
{"0o4p-3", 0.5},
129+
{"0o0014p-6", 0.1875},
130+
{"0o.001p9", 1},
131+
{"0o0.01p7", 2},
132+
{"0O0.01P+2", 0.0625},
133+
113134
// hexadecimal mantissa and exponent
114135
{"0x0", 0},
115136
{"-0x0", -zero_},

src/math/big/intconv.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,9 @@ func (x *Int) Format(s fmt.State, ch rune) {
172172
//
173173
// The base argument must be 0 or a value from 2 through MaxBase. If the base
174174
// is 0, the string prefix determines the actual conversion base. A prefix of
175-
// ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and a
176-
// ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base is 10.
175+
// ``0b'' or ``0B'' selects base 2; a ``0'', ``0o'', or ``0O'' prefix selects
176+
// base 8, and a ``0x'' or ``0X'' prefix selects base 16. Otherwise the selected
177+
// base is 10.
177178
//
178179
func (z *Int) scan(r io.ByteScanner, base int) (*Int, int, error) {
179180
// determine sign

src/math/big/intconv_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,24 @@ var stringTests = []struct {
1717
val int64
1818
ok bool
1919
}{
20+
// invalid inputs
2021
{in: ""},
2122
{in: "a"},
2223
{in: "z"},
2324
{in: "+"},
2425
{in: "-"},
2526
{in: "0b"},
27+
{in: "0o"},
2628
{in: "0x"},
29+
{in: "0y"},
2730
{in: "2", base: 2},
2831
{in: "0b2", base: 0},
2932
{in: "08"},
3033
{in: "8", base: 8},
3134
{in: "0xg", base: 0},
3235
{in: "g", base: 16},
36+
37+
// valid inputs
3338
{"0", "0", 0, 0, true},
3439
{"0", "0", 10, 0, true},
3540
{"0", "0", 16, 0, true},
@@ -40,6 +45,8 @@ var stringTests = []struct {
4045
{"10", "10", 16, 16, true},
4146
{"-10", "-10", 16, -16, true},
4247
{"+10", "10", 16, 16, true},
48+
{"0b10", "2", 0, 2, true},
49+
{"0o10", "8", 0, 8, true},
4350
{"0x10", "16", 0, 16, true},
4451
{in: "0x10", base: 16},
4552
{"-0x10", "-16", 0, -16, true},

src/math/big/natconv.go

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,25 @@ func pow(x Word, n int) (p Word) {
6161
// a digit count, and a read or syntax error err, if any.
6262
//
6363
// number = [ prefix ] mantissa .
64-
// prefix = "0" [ "x" | "X" | "b" | "B" ] .
64+
// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] .
6565
// mantissa = digits | digits "." [ digits ] | "." digits .
6666
// digits = digit { digit } .
6767
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
6868
//
6969
// Unless fracOk is set, the base argument must be 0 or a value between
7070
// 2 and MaxBase. If fracOk is set, the base argument must be one of
71-
// 0, 2, 10, or 16. Providing an invalid base argument leads to a run-
71+
// 0, 2, 8, 10, or 16. Providing an invalid base argument leads to a run-
7272
// time panic.
7373
//
7474
// For base 0, the number prefix determines the actual base: A prefix of
75-
// ``0x'' or ``0X'' selects base 16; if fracOk is not set, the ``0'' prefix
76-
// selects base 8, and a ``0b'' or ``0B'' prefix selects base 2. Otherwise
75+
// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and
76+
// ``0x'' or ``0X'' selects base 16. If fracOk is false, a ``0'' prefix
77+
// (immediately followed by digits) selects base 8 as well. Otherwise,
7778
// the selected base is 10 and no prefix is accepted.
7879
//
79-
// If fracOk is set, an octal prefix is ignored (a leading ``0'' simply
80-
// stands for a zero digit), and a period followed by a fractional part
81-
// is permitted. The result value is computed as if there were no period
82-
// present; and the count value is used to determine the fractional part.
80+
// If fracOk is set, a period followed by a fractional part is permitted.
81+
// The result value is computed as if there were no period present; and
82+
// the count value is used to determine the fractional part.
8383
//
8484
// For bases <= 36, lower and upper case letters are considered the same:
8585
// The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35.
@@ -95,54 +95,52 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in
9595
// reject illegal bases
9696
baseOk := base == 0 ||
9797
!fracOk && 2 <= base && base <= MaxBase ||
98-
fracOk && (base == 2 || base == 10 || base == 16)
98+
fracOk && (base == 2 || base == 8 || base == 10 || base == 16)
9999
if !baseOk {
100100
panic(fmt.Sprintf("illegal number base %d", base))
101101
}
102102

103103
// one char look-ahead
104104
ch, err := r.ReadByte()
105105
if err != nil {
106-
return
106+
return // io.EOF is also an error in this case
107107
}
108108

109109
// determine actual base
110-
b = base
110+
b, prefix := base, 0
111111
if base == 0 {
112112
// actual base is 10 unless there's a base prefix
113113
b = 10
114114
if ch == '0' {
115115
count = 1
116-
switch ch, err = r.ReadByte(); err {
117-
case nil:
118-
// possibly one of 0x, 0X, 0b, 0B
119-
if !fracOk {
120-
b = 8
116+
ch, err = r.ReadByte()
117+
if err != nil {
118+
if err == io.EOF {
119+
err = nil // not an error; input is "0"
120+
res = z[:0]
121121
}
122-
switch ch {
123-
case 'x', 'X':
124-
b = 16
125-
case 'b', 'B':
126-
b = 2
122+
return
123+
}
124+
// possibly one of 0b, 0B, 0o, 0O, 0x, 0X
125+
switch ch {
126+
case 'b', 'B':
127+
b, prefix = 2, 'b'
128+
case 'o', 'O':
129+
b, prefix = 8, 'o'
130+
case 'x', 'X':
131+
b, prefix = 16, 'x'
132+
default:
133+
if !fracOk {
134+
b, prefix = 8, '0'
127135
}
128-
switch b {
129-
case 16, 2:
130-
count = 0 // prefix is not counted
136+
}
137+
if prefix != 0 {
138+
count = 0 // prefix is not counted
139+
if prefix != '0' {
131140
if ch, err = r.ReadByte(); err != nil {
132-
// io.EOF is also an error in this case
133-
return
141+
return // io.EOF is also an error in this case
134142
}
135-
case 8:
136-
count = 0 // prefix is not counted
137143
}
138-
case io.EOF:
139-
// input is "0"
140-
res = z[:0]
141-
err = nil
142-
return
143-
default:
144-
// read error
145-
return
146144
}
147145
}
148146
}
@@ -216,14 +214,12 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in
216214

217215
if count == 0 {
218216
// no digits found
219-
switch {
220-
case base == 0 && b == 8:
217+
if prefix == '0' {
221218
// there was only the octal prefix 0 (possibly followed by digits > 7);
222219
// count as one digit and return base 10, not 8
223220
count = 1
224221
b = 10
225-
case base != 0 || b != 8:
226-
// there was neither a mantissa digit nor the octal prefix 0
222+
} else {
227223
err = errors.New("syntax error scanning number")
228224
}
229225
return

src/math/big/natconv_test.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,35 +112,49 @@ var natScanTests = []struct {
112112
ok bool // expected success
113113
next rune // next character (or 0, if at EOF)
114114
}{
115-
// error: no mantissa
115+
// invalid: no mantissa
116116
{},
117117
{s: "?"},
118118
{base: 10},
119119
{base: 36},
120120
{base: 62},
121121
{s: "?", base: 10},
122+
{s: "0b"},
123+
{s: "0o"},
122124
{s: "0x"},
125+
{s: "0b2"},
126+
{s: "0B2"},
127+
{s: "0o8"},
128+
{s: "0O8"},
129+
{s: "0xg"},
130+
{s: "0Xg"},
123131
{s: "345", base: 2},
124132

125-
// error: incorrect use of decimal point
133+
// invalid: incorrect use of decimal point
126134
{s: ".0"},
127135
{s: ".0", base: 10},
128136
{s: ".", base: 0},
129137
{s: "0x.0"},
130138

131-
// no errors
139+
// valid, no decimal point
132140
{"0", 0, false, nil, 10, 1, true, 0},
133141
{"0", 10, false, nil, 10, 1, true, 0},
134142
{"0", 36, false, nil, 36, 1, true, 0},
135143
{"0", 62, false, nil, 62, 1, true, 0},
136144
{"1", 0, false, nat{1}, 10, 1, true, 0},
137145
{"1", 10, false, nat{1}, 10, 1, true, 0},
138146
{"0 ", 0, false, nil, 10, 1, true, ' '},
147+
{"00 ", 0, false, nil, 8, 1, true, ' '}, // octal 0
148+
{"0b1", 0, false, nat{1}, 2, 1, true, 0},
149+
{"0B11000101", 0, false, nat{0xc5}, 2, 8, true, 0},
150+
{"0B110001012", 0, false, nat{0xc5}, 2, 8, true, '2'},
151+
{"07", 0, false, nat{7}, 8, 1, true, 0},
139152
{"08", 0, false, nil, 10, 1, true, '8'},
140153
{"08", 10, false, nat{8}, 10, 2, true, 0},
141154
{"018", 0, false, nat{1}, 8, 1, true, '8'},
142-
{"0b1", 0, false, nat{1}, 2, 1, true, 0},
143-
{"0b11000101", 0, false, nat{0xc5}, 2, 8, true, 0},
155+
{"0o7", 0, false, nat{7}, 8, 1, true, 0},
156+
{"0o18", 0, false, nat{1}, 8, 1, true, '8'},
157+
{"0O17", 0, false, nat{017}, 8, 2, true, 0},
144158
{"03271", 0, false, nat{03271}, 8, 4, true, 0},
145159
{"10ab", 0, false, nat{10}, 10, 2, true, 'a'},
146160
{"1234567890", 0, false, nat{1234567890}, 10, 10, true, 0},
@@ -153,13 +167,20 @@ var natScanTests = []struct {
153167
{"0xdeadbeef", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
154168
{"0XDEADBEEF", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
155169

156-
// no errors, decimal point
170+
// valid, with decimal point
157171
{"0.", 0, false, nil, 10, 1, true, '.'},
158172
{"0.", 10, true, nil, 10, 0, true, 0},
159173
{"0.1.2", 10, true, nat{1}, 10, -1, true, '.'},
160174
{".000", 10, true, nil, 10, -3, true, 0},
161175
{"12.3", 10, true, nat{123}, 10, -1, true, 0},
162176
{"012.345", 10, true, nat{12345}, 10, -3, true, 0},
177+
{"0.1", 0, true, nat{1}, 10, -1, true, 0},
178+
{"0.1", 2, true, nat{1}, 2, -1, true, 0},
179+
{"0.12", 2, true, nat{1}, 2, -1, true, '2'},
180+
{"0b0.1", 0, true, nat{1}, 2, -1, true, 0},
181+
{"0B0.12", 0, true, nat{1}, 2, -1, true, '2'},
182+
{"0o0.7", 0, true, nat{7}, 8, -1, true, 0},
183+
{"0O0.78", 0, true, nat{7}, 8, -1, true, '8'},
163184
}
164185

165186
func TestScanBase(t *testing.T) {

src/math/big/ratconv_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ var setStringTests = []StringTest{
5858

5959
// These are not supported by fmt.Fscanf.
6060
var setStringTests2 = []StringTest{
61-
{"0x10", "16", true},
61+
{"0b1000/3", "8/3", true},
62+
{"0B1000/0x8", "1", true},
6263
{"-010/1", "-8", true}, // TODO(gri) should we even permit octal here?
6364
{"-010.", "-10", true},
65+
{"-0o10/1", "-8", true},
66+
{"0x10/1", "16", true},
6467
{"0x10/0x20", "1/2", true},
65-
{"0b1000/3", "8/3", true},
6668
{in: "4/3x"},
6769
// TODO(gri) add more tests
6870
}
@@ -81,8 +83,12 @@ func TestRatSetString(t *testing.T) {
8183
} else if x.RatString() != test.out {
8284
t.Errorf("#%d SetString(%q) got %s want %s", i, test.in, x.RatString(), test.out)
8385
}
84-
} else if x != nil {
85-
t.Errorf("#%d SetString(%q) got %p want nil", i, test.in, x)
86+
} else {
87+
if test.ok {
88+
t.Errorf("#%d SetString(%q) expected success", i, test.in)
89+
} else if x != nil {
90+
t.Errorf("#%d SetString(%q) got %p want nil", i, test.in, x)
91+
}
8692
}
8793
}
8894
}

0 commit comments

Comments
 (0)