Commit 129c6e44 authored by Robert Griesemer's avatar Robert Griesemer

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/+/165898Reviewed-by: default avatarEmmanuel Odeke <emm.odeke@gmail.com>
parent 2dd066d4
...@@ -97,6 +97,8 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) { ...@@ -97,6 +97,8 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
fallthrough // 10**e == 5**e * 2**e fallthrough // 10**e == 5**e * 2**e
case 2: case 2:
exp2 += d exp2 += d
case 8:
exp2 += d * 3 // octal digits are 3 bits each
case 16: case 16:
exp2 += d * 4 // hexadecimal digits are 4 bits each exp2 += d * 4 // hexadecimal digits are 4 bits each
default: default:
...@@ -222,21 +224,21 @@ func (z *Float) pow5(n uint64) *Float { ...@@ -222,21 +224,21 @@ func (z *Float) pow5(n uint64) *Float {
// //
// number = [ sign ] [ prefix ] mantissa [ exponent ] | infinity . // number = [ sign ] [ prefix ] mantissa [ exponent ] | infinity .
// sign = "+" | "-" . // sign = "+" | "-" .
// prefix = "0" ( "x" | "X" | "b" | "B" ) . // prefix = "0" ( "b" | "B" | "o" | "O" | "x" | "X" ) .
// mantissa = digits | digits "." [ digits ] | "." digits . // mantissa = digits | digits "." [ digits ] | "." digits .
// exponent = ( "e" | "E" | "p" | "P" ) [ sign ] digits . // exponent = ( "e" | "E" | "p" | "P" ) [ sign ] digits .
// digits = digit { digit } . // digits = digit { digit } .
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
// infinity = [ sign ] ( "inf" | "Inf" ) . // infinity = [ sign ] ( "inf" | "Inf" ) .
// //
// The base argument must be 0, 2, 10, or 16. Providing an invalid base // The base argument must be 0, 2, 8, 10, or 16. Providing an invalid base
// argument will lead to a run-time panic. // argument will lead to a run-time panic.
// //
// For base 0, the number prefix determines the actual base: A prefix of // For base 0, the number prefix determines the actual base: A prefix of
// "0x" or "0X" selects base 16, and a "0b" or "0B" prefix selects // ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and
// base 2; otherwise, the actual base is 10 and no prefix is accepted. // ``0x'' or ``0X'' selects base 16. Otherwise, the actual base is 10 and
// The octal prefix "0" is not supported (a leading "0" is simply // no prefix is accepted. The octal prefix "0" is not supported (a leading
// considered a "0"). // "0" is simply considered a "0").
// //
// A "p" or "P" exponent indicates a binary (rather then decimal) exponent; // A "p" or "P" exponent indicates a binary (rather then decimal) exponent;
// for instance "0x1.fffffffffffffp1023" (using base 0) represents the // for instance "0x1.fffffffffffffp1023" (using base 0) represents the
......
...@@ -110,6 +110,27 @@ func TestFloatSetFloat64String(t *testing.T) { ...@@ -110,6 +110,27 @@ func TestFloatSetFloat64String(t *testing.T) {
{"0b0.01p2", 1}, {"0b0.01p2", 1},
{"0b0.01P+2", 1}, {"0b0.01P+2", 1},
// octal mantissa, decimal exponent
{"0o0", 0},
{"-0o0", -zero_},
{"0o0e+10", 0},
{"-0o0e-10", -zero_},
{"0o12", 10},
{"0O12E2", 1000},
{"0o.4", 0.5},
{"0o.01", 0.015625},
{"0o.01e3", 15.625},
// octal mantissa, binary exponent
{"0o0p+10", 0},
{"-0o0p-10", -zero_},
{"0o.12p6", 10},
{"0o4p-3", 0.5},
{"0o0014p-6", 0.1875},
{"0o.001p9", 1},
{"0o0.01p7", 2},
{"0O0.01P+2", 0.0625},
// hexadecimal mantissa and exponent // hexadecimal mantissa and exponent
{"0x0", 0}, {"0x0", 0},
{"-0x0", -zero_}, {"-0x0", -zero_},
......
...@@ -172,8 +172,9 @@ func (x *Int) Format(s fmt.State, ch rune) { ...@@ -172,8 +172,9 @@ func (x *Int) Format(s fmt.State, ch rune) {
// //
// The base argument must be 0 or a value from 2 through MaxBase. If the base // The base argument must be 0 or a value from 2 through MaxBase. If the base
// is 0, the string prefix determines the actual conversion base. A prefix of // is 0, the string prefix determines the actual conversion base. A prefix of
// ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and a // ``0b'' or ``0B'' selects base 2; a ``0'', ``0o'', or ``0O'' prefix selects
// ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base is 10. // base 8, and a ``0x'' or ``0X'' prefix selects base 16. Otherwise the selected
// base is 10.
// //
func (z *Int) scan(r io.ByteScanner, base int) (*Int, int, error) { func (z *Int) scan(r io.ByteScanner, base int) (*Int, int, error) {
// determine sign // determine sign
......
...@@ -17,19 +17,24 @@ var stringTests = []struct { ...@@ -17,19 +17,24 @@ var stringTests = []struct {
val int64 val int64
ok bool ok bool
}{ }{
// invalid inputs
{in: ""}, {in: ""},
{in: "a"}, {in: "a"},
{in: "z"}, {in: "z"},
{in: "+"}, {in: "+"},
{in: "-"}, {in: "-"},
{in: "0b"}, {in: "0b"},
{in: "0o"},
{in: "0x"}, {in: "0x"},
{in: "0y"},
{in: "2", base: 2}, {in: "2", base: 2},
{in: "0b2", base: 0}, {in: "0b2", base: 0},
{in: "08"}, {in: "08"},
{in: "8", base: 8}, {in: "8", base: 8},
{in: "0xg", base: 0}, {in: "0xg", base: 0},
{in: "g", base: 16}, {in: "g", base: 16},
// valid inputs
{"0", "0", 0, 0, true}, {"0", "0", 0, 0, true},
{"0", "0", 10, 0, true}, {"0", "0", 10, 0, true},
{"0", "0", 16, 0, true}, {"0", "0", 16, 0, true},
...@@ -40,6 +45,8 @@ var stringTests = []struct { ...@@ -40,6 +45,8 @@ var stringTests = []struct {
{"10", "10", 16, 16, true}, {"10", "10", 16, 16, true},
{"-10", "-10", 16, -16, true}, {"-10", "-10", 16, -16, true},
{"+10", "10", 16, 16, true}, {"+10", "10", 16, 16, true},
{"0b10", "2", 0, 2, true},
{"0o10", "8", 0, 8, true},
{"0x10", "16", 0, 16, true}, {"0x10", "16", 0, 16, true},
{in: "0x10", base: 16}, {in: "0x10", base: 16},
{"-0x10", "-16", 0, -16, true}, {"-0x10", "-16", 0, -16, true},
......
...@@ -61,25 +61,25 @@ func pow(x Word, n int) (p Word) { ...@@ -61,25 +61,25 @@ func pow(x Word, n int) (p Word) {
// a digit count, and a read or syntax error err, if any. // a digit count, and a read or syntax error err, if any.
// //
// number = [ prefix ] mantissa . // number = [ prefix ] mantissa .
// prefix = "0" [ "x" | "X" | "b" | "B" ] . // prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] .
// mantissa = digits | digits "." [ digits ] | "." digits . // mantissa = digits | digits "." [ digits ] | "." digits .
// digits = digit { digit } . // digits = digit { digit } .
// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
// //
// Unless fracOk is set, the base argument must be 0 or a value between // Unless fracOk is set, the base argument must be 0 or a value between
// 2 and MaxBase. If fracOk is set, the base argument must be one of // 2 and MaxBase. If fracOk is set, the base argument must be one of
// 0, 2, 10, or 16. Providing an invalid base argument leads to a run- // 0, 2, 8, 10, or 16. Providing an invalid base argument leads to a run-
// time panic. // time panic.
// //
// For base 0, the number prefix determines the actual base: A prefix of // For base 0, the number prefix determines the actual base: A prefix of
// ``0x'' or ``0X'' selects base 16; if fracOk is not set, the ``0'' prefix // ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and
// selects base 8, and a ``0b'' or ``0B'' prefix selects base 2. Otherwise // ``0x'' or ``0X'' selects base 16. If fracOk is false, a ``0'' prefix
// (immediately followed by digits) selects base 8 as well. Otherwise,
// the selected base is 10 and no prefix is accepted. // the selected base is 10 and no prefix is accepted.
// //
// If fracOk is set, an octal prefix is ignored (a leading ``0'' simply // If fracOk is set, a period followed by a fractional part is permitted.
// stands for a zero digit), and a period followed by a fractional part // The result value is computed as if there were no period present; and
// is permitted. The result value is computed as if there were no period // the count value is used to determine the fractional part.
// present; and the count value is used to determine the fractional part.
// //
// For bases <= 36, lower and upper case letters are considered the same: // For bases <= 36, lower and upper case letters are considered the same:
// The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35. // The letters 'a' to 'z' and 'A' to 'Z' represent digit values 10 to 35.
...@@ -95,7 +95,7 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in ...@@ -95,7 +95,7 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in
// reject illegal bases // reject illegal bases
baseOk := base == 0 || baseOk := base == 0 ||
!fracOk && 2 <= base && base <= MaxBase || !fracOk && 2 <= base && base <= MaxBase ||
fracOk && (base == 2 || base == 10 || base == 16) fracOk && (base == 2 || base == 8 || base == 10 || base == 16)
if !baseOk { if !baseOk {
panic(fmt.Sprintf("illegal number base %d", base)) panic(fmt.Sprintf("illegal number base %d", base))
} }
...@@ -103,46 +103,44 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in ...@@ -103,46 +103,44 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in
// one char look-ahead // one char look-ahead
ch, err := r.ReadByte() ch, err := r.ReadByte()
if err != nil { if err != nil {
return return // io.EOF is also an error in this case
} }
// determine actual base // determine actual base
b = base b, prefix := base, 0
if base == 0 { if base == 0 {
// actual base is 10 unless there's a base prefix // actual base is 10 unless there's a base prefix
b = 10 b = 10
if ch == '0' { if ch == '0' {
count = 1 count = 1
switch ch, err = r.ReadByte(); err { ch, err = r.ReadByte()
case nil: if err != nil {
// possibly one of 0x, 0X, 0b, 0B if err == io.EOF {
if !fracOk { err = nil // not an error; input is "0"
b = 8 res = z[:0]
} }
switch ch { return
case 'x', 'X': }
b = 16 // possibly one of 0b, 0B, 0o, 0O, 0x, 0X
case 'b', 'B': switch ch {
b = 2 case 'b', 'B':
b, prefix = 2, 'b'
case 'o', 'O':
b, prefix = 8, 'o'
case 'x', 'X':
b, prefix = 16, 'x'
default:
if !fracOk {
b, prefix = 8, '0'
} }
switch b { }
case 16, 2: if prefix != 0 {
count = 0 // prefix is not counted count = 0 // prefix is not counted
if prefix != '0' {
if ch, err = r.ReadByte(); err != nil { if ch, err = r.ReadByte(); err != nil {
// io.EOF is also an error in this case return // io.EOF is also an error in this case
return
} }
case 8:
count = 0 // prefix is not counted
} }
case io.EOF:
// input is "0"
res = z[:0]
err = nil
return
default:
// read error
return
} }
} }
} }
...@@ -216,14 +214,12 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in ...@@ -216,14 +214,12 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in
if count == 0 { if count == 0 {
// no digits found // no digits found
switch { if prefix == '0' {
case base == 0 && b == 8:
// there was only the octal prefix 0 (possibly followed by digits > 7); // there was only the octal prefix 0 (possibly followed by digits > 7);
// count as one digit and return base 10, not 8 // count as one digit and return base 10, not 8
count = 1 count = 1
b = 10 b = 10
case base != 0 || b != 8: } else {
// there was neither a mantissa digit nor the octal prefix 0
err = errors.New("syntax error scanning number") err = errors.New("syntax error scanning number")
} }
return return
......
...@@ -112,23 +112,31 @@ var natScanTests = []struct { ...@@ -112,23 +112,31 @@ var natScanTests = []struct {
ok bool // expected success ok bool // expected success
next rune // next character (or 0, if at EOF) next rune // next character (or 0, if at EOF)
}{ }{
// error: no mantissa // invalid: no mantissa
{}, {},
{s: "?"}, {s: "?"},
{base: 10}, {base: 10},
{base: 36}, {base: 36},
{base: 62}, {base: 62},
{s: "?", base: 10}, {s: "?", base: 10},
{s: "0b"},
{s: "0o"},
{s: "0x"}, {s: "0x"},
{s: "0b2"},
{s: "0B2"},
{s: "0o8"},
{s: "0O8"},
{s: "0xg"},
{s: "0Xg"},
{s: "345", base: 2}, {s: "345", base: 2},
// error: incorrect use of decimal point // invalid: incorrect use of decimal point
{s: ".0"}, {s: ".0"},
{s: ".0", base: 10}, {s: ".0", base: 10},
{s: ".", base: 0}, {s: ".", base: 0},
{s: "0x.0"}, {s: "0x.0"},
// no errors // valid, no decimal point
{"0", 0, false, nil, 10, 1, true, 0}, {"0", 0, false, nil, 10, 1, true, 0},
{"0", 10, false, nil, 10, 1, true, 0}, {"0", 10, false, nil, 10, 1, true, 0},
{"0", 36, false, nil, 36, 1, true, 0}, {"0", 36, false, nil, 36, 1, true, 0},
...@@ -136,11 +144,17 @@ var natScanTests = []struct { ...@@ -136,11 +144,17 @@ var natScanTests = []struct {
{"1", 0, false, nat{1}, 10, 1, true, 0}, {"1", 0, false, nat{1}, 10, 1, true, 0},
{"1", 10, false, nat{1}, 10, 1, true, 0}, {"1", 10, false, nat{1}, 10, 1, true, 0},
{"0 ", 0, false, nil, 10, 1, true, ' '}, {"0 ", 0, false, nil, 10, 1, true, ' '},
{"00 ", 0, false, nil, 8, 1, true, ' '}, // octal 0
{"0b1", 0, false, nat{1}, 2, 1, true, 0},
{"0B11000101", 0, false, nat{0xc5}, 2, 8, true, 0},
{"0B110001012", 0, false, nat{0xc5}, 2, 8, true, '2'},
{"07", 0, false, nat{7}, 8, 1, true, 0},
{"08", 0, false, nil, 10, 1, true, '8'}, {"08", 0, false, nil, 10, 1, true, '8'},
{"08", 10, false, nat{8}, 10, 2, true, 0}, {"08", 10, false, nat{8}, 10, 2, true, 0},
{"018", 0, false, nat{1}, 8, 1, true, '8'}, {"018", 0, false, nat{1}, 8, 1, true, '8'},
{"0b1", 0, false, nat{1}, 2, 1, true, 0}, {"0o7", 0, false, nat{7}, 8, 1, true, 0},
{"0b11000101", 0, false, nat{0xc5}, 2, 8, true, 0}, {"0o18", 0, false, nat{1}, 8, 1, true, '8'},
{"0O17", 0, false, nat{017}, 8, 2, true, 0},
{"03271", 0, false, nat{03271}, 8, 4, true, 0}, {"03271", 0, false, nat{03271}, 8, 4, true, 0},
{"10ab", 0, false, nat{10}, 10, 2, true, 'a'}, {"10ab", 0, false, nat{10}, 10, 2, true, 'a'},
{"1234567890", 0, false, nat{1234567890}, 10, 10, true, 0}, {"1234567890", 0, false, nat{1234567890}, 10, 10, true, 0},
...@@ -153,13 +167,20 @@ var natScanTests = []struct { ...@@ -153,13 +167,20 @@ var natScanTests = []struct {
{"0xdeadbeef", 0, false, nat{0xdeadbeef}, 16, 8, true, 0}, {"0xdeadbeef", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
{"0XDEADBEEF", 0, false, nat{0xdeadbeef}, 16, 8, true, 0}, {"0XDEADBEEF", 0, false, nat{0xdeadbeef}, 16, 8, true, 0},
// no errors, decimal point // valid, with decimal point
{"0.", 0, false, nil, 10, 1, true, '.'}, {"0.", 0, false, nil, 10, 1, true, '.'},
{"0.", 10, true, nil, 10, 0, true, 0}, {"0.", 10, true, nil, 10, 0, true, 0},
{"0.1.2", 10, true, nat{1}, 10, -1, true, '.'}, {"0.1.2", 10, true, nat{1}, 10, -1, true, '.'},
{".000", 10, true, nil, 10, -3, true, 0}, {".000", 10, true, nil, 10, -3, true, 0},
{"12.3", 10, true, nat{123}, 10, -1, true, 0}, {"12.3", 10, true, nat{123}, 10, -1, true, 0},
{"012.345", 10, true, nat{12345}, 10, -3, true, 0}, {"012.345", 10, true, nat{12345}, 10, -3, true, 0},
{"0.1", 0, true, nat{1}, 10, -1, true, 0},
{"0.1", 2, true, nat{1}, 2, -1, true, 0},
{"0.12", 2, true, nat{1}, 2, -1, true, '2'},
{"0b0.1", 0, true, nat{1}, 2, -1, true, 0},
{"0B0.12", 0, true, nat{1}, 2, -1, true, '2'},
{"0o0.7", 0, true, nat{7}, 8, -1, true, 0},
{"0O0.78", 0, true, nat{7}, 8, -1, true, '8'},
} }
func TestScanBase(t *testing.T) { func TestScanBase(t *testing.T) {
......
...@@ -58,11 +58,13 @@ var setStringTests = []StringTest{ ...@@ -58,11 +58,13 @@ var setStringTests = []StringTest{
// These are not supported by fmt.Fscanf. // These are not supported by fmt.Fscanf.
var setStringTests2 = []StringTest{ var setStringTests2 = []StringTest{
{"0x10", "16", true}, {"0b1000/3", "8/3", true},
{"0B1000/0x8", "1", true},
{"-010/1", "-8", true}, // TODO(gri) should we even permit octal here? {"-010/1", "-8", true}, // TODO(gri) should we even permit octal here?
{"-010.", "-10", true}, {"-010.", "-10", true},
{"-0o10/1", "-8", true},
{"0x10/1", "16", true},
{"0x10/0x20", "1/2", true}, {"0x10/0x20", "1/2", true},
{"0b1000/3", "8/3", true},
{in: "4/3x"}, {in: "4/3x"},
// TODO(gri) add more tests // TODO(gri) add more tests
} }
...@@ -81,8 +83,12 @@ func TestRatSetString(t *testing.T) { ...@@ -81,8 +83,12 @@ func TestRatSetString(t *testing.T) {
} else if x.RatString() != test.out { } else if x.RatString() != test.out {
t.Errorf("#%d SetString(%q) got %s want %s", i, test.in, x.RatString(), test.out) t.Errorf("#%d SetString(%q) got %s want %s", i, test.in, x.RatString(), test.out)
} }
} else if x != nil { } else {
t.Errorf("#%d SetString(%q) got %p want nil", i, test.in, x) if test.ok {
t.Errorf("#%d SetString(%q) expected success", i, test.in)
} else if x != nil {
t.Errorf("#%d SetString(%q) got %p want nil", i, test.in, x)
}
} }
} }
} }
......
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