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) + } } } }