From 129c6e449694f14fd27dfed03f7a0c95847ec366 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 6 Mar 2019 17:14:27 -0800 Subject: [PATCH] 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 --- src/math/big/floatconv.go | 14 +++--- src/math/big/floatconv_test.go | 21 +++++++++ src/math/big/intconv.go | 5 ++- src/math/big/intconv_test.go | 7 +++ src/math/big/natconv.go | 80 ++++++++++++++++------------------ src/math/big/natconv_test.go | 33 +++++++++++--- src/math/big/ratconv_test.go | 14 ++++-- 7 files changed, 114 insertions(+), 60 deletions(-) diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go index 5cc9e24f4c..b685b2a288 100644 --- a/src/math/big/floatconv.go +++ b/src/math/big/floatconv.go @@ -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 case 2: exp2 += d + case 8: + exp2 += d * 3 // octal digits are 3 bits each case 16: exp2 += d * 4 // hexadecimal digits are 4 bits each default: @@ -222,21 +224,21 @@ func (z *Float) pow5(n uint64) *Float { // // number = [ sign ] [ prefix ] mantissa [ exponent ] | infinity . // sign = "+" | "-" . -// prefix = "0" ( "x" | "X" | "b" | "B" ) . +// prefix = "0" ( "b" | "B" | "o" | "O" | "x" | "X" ) . // mantissa = digits | digits "." [ digits ] | "." digits . // exponent = ( "e" | "E" | "p" | "P" ) [ sign ] digits . // digits = digit { digit } . // digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // 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. // // 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 -// base 2; otherwise, the actual base is 10 and no prefix is accepted. -// The octal prefix "0" is not supported (a leading "0" is simply -// considered a "0"). +// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and +// ``0x'' or ``0X'' selects base 16. Otherwise, the actual base is 10 and +// no prefix is accepted. The octal prefix "0" is not supported (a leading +// "0" is simply considered a "0"). // // A "p" or "P" exponent indicates a binary (rather then decimal) exponent; // for instance "0x1.fffffffffffffp1023" (using base 0) represents the diff --git a/src/math/big/floatconv_test.go b/src/math/big/floatconv_test.go index 768943b902..f32dd8928b 100644 --- a/src/math/big/floatconv_test.go +++ b/src/math/big/floatconv_test.go @@ -110,6 +110,27 @@ func TestFloatSetFloat64String(t *testing.T) { {"0b0.01p2", 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 {"0x0", 0}, {"-0x0", -zero_}, diff --git a/src/math/big/intconv.go b/src/math/big/intconv.go index 65174c5018..d37d077920 100644 --- a/src/math/big/intconv.go +++ b/src/math/big/intconv.go @@ -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 // 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'' prefix selects base 2. Otherwise the selected base is 10. +// ``0b'' or ``0B'' selects base 2; a ``0'', ``0o'', or ``0O'' prefix selects +// 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) { // determine sign diff --git a/src/math/big/intconv_test.go b/src/math/big/intconv_test.go index d23a3e2beb..d625b6aa3d 100644 --- a/src/math/big/intconv_test.go +++ b/src/math/big/intconv_test.go @@ -17,19 +17,24 @@ var stringTests = []struct { val int64 ok bool }{ + // invalid inputs {in: ""}, {in: "a"}, {in: "z"}, {in: "+"}, {in: "-"}, {in: "0b"}, + {in: "0o"}, {in: "0x"}, + {in: "0y"}, {in: "2", base: 2}, {in: "0b2", base: 0}, {in: "08"}, {in: "8", base: 8}, {in: "0xg", base: 0}, {in: "g", base: 16}, + + // valid inputs {"0", "0", 0, 0, true}, {"0", "0", 10, 0, true}, {"0", "0", 16, 0, true}, @@ -40,6 +45,8 @@ var stringTests = []struct { {"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}, {in: "0x10", base: 16}, {"-0x10", "-16", 0, -16, true}, diff --git a/src/math/big/natconv.go b/src/math/big/natconv.go index 21ccbd6cfa..c3c4115097 100644 --- a/src/math/big/natconv.go +++ b/src/math/big/natconv.go @@ -61,25 +61,25 @@ func pow(x Word, n int) (p Word) { // a digit count, and a read or syntax error err, if any. // // number = [ prefix ] mantissa . -// prefix = "0" [ "x" | "X" | "b" | "B" ] . +// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] . // mantissa = digits | digits "." [ digits ] | "." digits . // digits = digit { digit } . // digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // // 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 -// 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. // // 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 -// selects base 8, and a ``0b'' or ``0B'' prefix selects base 2. Otherwise +// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and +// ``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. // -// If fracOk is set, an octal prefix is ignored (a leading ``0'' simply -// stands for a zero digit), and a period followed by a fractional part -// is permitted. The result value is computed as if there were no period -// present; and the count value is used to determine the fractional part. +// If fracOk is set, a period followed by a fractional part is permitted. +// The result value is computed as if there were no period present; and +// the count value is used to determine the fractional part. // // 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. @@ -95,7 +95,7 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in // reject illegal bases baseOk := base == 0 || !fracOk && 2 <= base && base <= MaxBase || - fracOk && (base == 2 || base == 10 || base == 16) + fracOk && (base == 2 || base == 8 || base == 10 || base == 16) if !baseOk { 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 // one char look-ahead ch, err := r.ReadByte() if err != nil { - return + return // io.EOF is also an error in this case } // determine actual base - b = base + b, prefix := base, 0 if base == 0 { // actual base is 10 unless there's a base prefix b = 10 if ch == '0' { count = 1 - switch ch, err = r.ReadByte(); err { - case nil: - // possibly one of 0x, 0X, 0b, 0B - if !fracOk { - b = 8 + ch, err = r.ReadByte() + if err != nil { + if err == io.EOF { + err = nil // not an error; input is "0" + res = z[:0] } - switch ch { - case 'x', 'X': - b = 16 - case 'b', 'B': - b = 2 - } - switch b { - case 16, 2: - count = 0 // prefix is not counted - if ch, err = r.ReadByte(); err != nil { - // 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 + } + // possibly one of 0b, 0B, 0o, 0O, 0x, 0X + switch ch { + case 'b', 'B': + b, prefix = 2, 'b' + case 'o', 'O': + b, prefix = 8, 'o' + case 'x', 'X': + b, prefix = 16, 'x' default: - // read error - return + if !fracOk { + b, prefix = 8, '0' + } + } + if prefix != 0 { + count = 0 // prefix is not counted + if prefix != '0' { + if ch, err = r.ReadByte(); err != nil { + return // io.EOF is also an error in this case + } + } } } } @@ -216,14 +214,12 @@ func (z nat) scan(r io.ByteScanner, base int, fracOk bool) (res nat, b, count in if count == 0 { // no digits found - switch { - case base == 0 && b == 8: + if prefix == '0' { // there was only the octal prefix 0 (possibly followed by digits > 7); // count as one digit and return base 10, not 8 count = 1 b = 10 - case base != 0 || b != 8: - // there was neither a mantissa digit nor the octal prefix 0 + } else { err = errors.New("syntax error scanning number") } return diff --git a/src/math/big/natconv_test.go b/src/math/big/natconv_test.go index 9f38bd94bb..645e2b8434 100644 --- a/src/math/big/natconv_test.go +++ b/src/math/big/natconv_test.go @@ -112,23 +112,31 @@ var natScanTests = []struct { ok bool // expected success next rune // next character (or 0, if at EOF) }{ - // error: no mantissa + // invalid: no mantissa {}, {s: "?"}, {base: 10}, {base: 36}, {base: 62}, {s: "?", base: 10}, + {s: "0b"}, + {s: "0o"}, {s: "0x"}, + {s: "0b2"}, + {s: "0B2"}, + {s: "0o8"}, + {s: "0O8"}, + {s: "0xg"}, + {s: "0Xg"}, {s: "345", base: 2}, - // error: incorrect use of decimal point + // invalid: incorrect use of decimal point {s: ".0"}, {s: ".0", base: 10}, {s: ".", base: 0}, {s: "0x.0"}, - // no errors + // valid, no decimal point {"0", 0, false, nil, 10, 1, true, 0}, {"0", 10, false, nil, 10, 1, true, 0}, {"0", 36, false, nil, 36, 1, true, 0}, @@ -136,11 +144,17 @@ var natScanTests = []struct { {"1", 0, false, nat{1}, 10, 1, true, 0}, {"1", 10, false, nat{1}, 10, 1, true, 0}, {"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", 10, false, nat{8}, 10, 2, true, 0}, {"018", 0, false, nat{1}, 8, 1, true, '8'}, - {"0b1", 0, false, nat{1}, 2, 1, true, 0}, - {"0b11000101", 0, false, nat{0xc5}, 2, 8, true, 0}, + {"0o7", 0, false, nat{7}, 8, 1, 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}, {"10ab", 0, false, nat{10}, 10, 2, true, 'a'}, {"1234567890", 0, false, nat{1234567890}, 10, 10, true, 0}, @@ -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}, - // no errors, decimal point + // valid, with decimal point {"0.", 0, false, nil, 10, 1, true, '.'}, {"0.", 10, true, nil, 10, 0, true, 0}, {"0.1.2", 10, true, nat{1}, 10, -1, true, '.'}, {".000", 10, true, nil, 10, -3, true, 0}, {"12.3", 10, true, nat{123}, 10, -1, 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) { diff --git a/src/math/big/ratconv_test.go b/src/math/big/ratconv_test.go index fe8b8b60af..bdc6a3e1b0 100644 --- a/src/math/big/ratconv_test.go +++ b/src/math/big/ratconv_test.go @@ -58,11 +58,13 @@ var setStringTests = []StringTest{ // These are not supported by fmt.Fscanf. 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.", "-10", true}, + {"-0o10/1", "-8", true}, + {"0x10/1", "16", true}, {"0x10/0x20", "1/2", true}, - {"0b1000/3", "8/3", true}, {in: "4/3x"}, // TODO(gri) add more tests } @@ -81,8 +83,12 @@ func TestRatSetString(t *testing.T) { } else if x.RatString() != test.out { t.Errorf("#%d SetString(%q) got %s want %s", i, test.in, x.RatString(), test.out) } - } else if x != nil { - t.Errorf("#%d SetString(%q) got %p want nil", i, test.in, x) + } else { + 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) + } } } }