]> Cypherpunks repositories - gostls13.git/commitdiff
math/big: support new octal prefixes 0o and 0O
authorRobert Griesemer <gri@golang.org>
Thu, 7 Mar 2019 01:14:27 +0000 (17:14 -0800)
committerRobert Griesemer <gri@golang.org>
Thu, 7 Mar 2019 21:13:57 +0000 (21:13 +0000)
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 <emm.odeke@gmail.com>
src/math/big/floatconv.go
src/math/big/floatconv_test.go
src/math/big/intconv.go
src/math/big/intconv_test.go
src/math/big/natconv.go
src/math/big/natconv_test.go
src/math/big/ratconv_test.go

index 5cc9e24f4c42e2f996a78703b85ff4de71021456..b685b2a2883b84e5e12379a352fcf7c3f960ce21 100644 (file)
@@ -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
index 768943b9025e9ab10ee0264c6c3cf1cea88f180c..f32dd8928b980b546c91a53263a4e559376d81dc 100644 (file)
@@ -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_},
index 65174c501860cfec223189b223ed64ba86983eff..d37d077920f5402502c33171fac5f01d85bbb524 100644 (file)
@@ -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
index d23a3e2beb42774b10045826caed0b904e3b63af..d625b6aa3d5ec52a30b7321a3a3739504041f74c 100644 (file)
@@ -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},
index 21ccbd6cfafcb42c1fe215bd32289c609d0b52e6..c3c41150979110929f384ede2e8e55f9f788e693 100644 (file)
@@ -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
+                               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:
+                               if !fracOk {
+                                       b, prefix = 8, '0'
                                }
-                               switch b {
-                               case 16, 2:
-                                       count = 0 // prefix is not counted
+                       }
+                       if prefix != 0 {
+                               count = 0 // prefix is not counted
+                               if prefix != '0' {
                                        if ch, err = r.ReadByte(); err != nil {
-                                               // io.EOF is also an error in this case
-                                               return
+                                               return // io.EOF is also an error in this case
                                        }
-                               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
 
        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
index 9f38bd94bbab985743f49cd2cd4b35def051956b..645e2b8434fe3fe35c2165f6bdf843a3869abfca 100644 (file)
@@ -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) {
index fe8b8b60af608e4743299618f48e935ba78a3763..bdc6a3e1b004e9b9910c310ccd8f6621dd3917d8 100644 (file)
@@ -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)
+                       }
                }
        }
 }