From e2828468ff0735988ffa1fbc8024707cd5abf91a Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 12 Dec 2013 13:43:00 -0800 Subject: [PATCH] go.tools/go/exact: serialization support and better unknown handling - fixed various imprecise doc strings - consistent handling of unknown values R=adonovan CC=golang-dev https://golang.org/cl/41170043 --- go/exact/exact.go | 237 ++++++++++++++++++++++++++++++++--------- go/exact/exact_test.go | 118 +++++++++++++++++++- 2 files changed, 298 insertions(+), 57 deletions(-) diff --git a/go/exact/exact.go b/go/exact/exact.go index 8438dcee..37617df4 100644 --- a/go/exact/exact.go +++ b/go/exact/exact.go @@ -3,7 +3,11 @@ // license that can be found in the LICENSE file. // Package exact implements mathematically exact values -// and operations for all Go basic types. +// and operations for untyped Go constant values. +// +// A special Unknown value may be used when a constant +// value is unknown due to an error; operations on unknown +// values produce unknown values. // package exact @@ -132,15 +136,15 @@ func MakeUint64(x uint64) Value { return normInt(new(big.Int).SetUint64(x)) } // MakeFloat64 returns the numeric value for x. // If x is not finite, the result is unknown. func MakeFloat64(x float64) Value { - f := new(big.Rat).SetFloat64(x) - if f != nil { + if f := new(big.Rat).SetFloat64(x); f != nil { return normFloat(f) } return unknownVal{} } -// MakeFromLiteral returns the corresponding literal value. -// If the literal has illegal format, the result is nil. +// MakeFromLiteral returns the corresponding integer, floating-point, +// imaginary, character, or string value for a Go literal string. The +// result is nil if the literal string is invalid. func MakeFromLiteral(lit string, tok token.Token) Value { switch tok { case token.INT: @@ -176,15 +180,18 @@ func MakeFromLiteral(lit string, tok token.Token) Value { } } - // TODO(gri) should we instead a) return unknown, or b) an error? + // TODO(gri) should we return an Unknown instead? return nil } // ---------------------------------------------------------------------------- // Accessors +// +// For unknown arguments the result is the zero value for the respective +// accessor type, except for Sign, where the result is 1. -// BoolVal returns the Go boolean value of x, which must be a Bool. -// The result is false for unknown values. +// BoolVal returns the Go boolean value of x, which must be a Bool or an Unknown. +// If x is Unknown, the result is false. func BoolVal(x Value) bool { switch x := x.(type) { case boolVal: @@ -192,11 +199,11 @@ func BoolVal(x Value) bool { case unknownVal: return false } - panic(fmt.Sprintf("invalid BoolVal(%v)", x)) + panic(fmt.Sprintf("%v not a Bool", x)) } -// StringVal returns the Go string value of x, which must be a String. -// The result is "" for unknown values. +// StringVal returns the Go string value of x, which must be a String or an Unknown. +// If x is Unknown, the result is "". func StringVal(x Value) string { switch x := x.(type) { case stringVal: @@ -204,12 +211,12 @@ func StringVal(x Value) string { case unknownVal: return "" } - panic(fmt.Sprintf("invalidStringVal(%v)", x)) + panic(fmt.Sprintf("%v not a String", x)) } // Int64Val returns the Go int64 value of x and whether the result is exact; -// x must be an Int. If the result is not exact, its value is undefined. -// The result is (0, false) for unknown values. +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). func Int64Val(x Value) (int64, bool) { switch x := x.(type) { case int64Val: @@ -219,12 +226,12 @@ func Int64Val(x Value) (int64, bool) { case unknownVal: return 0, false } - panic(fmt.Sprintf("invalid Int64Val(%v)", x)) + panic(fmt.Sprintf("%v not an Int", x)) } // Uint64Val returns the Go uint64 value of x and whether the result is exact; -// x must be an Int. If the result is not exact, its value is undefined. -// The result is (0, false) for unknown values. +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). func Uint64Val(x Value) (uint64, bool) { switch x := x.(type) { case int64Val: @@ -234,11 +241,12 @@ func Uint64Val(x Value) (uint64, bool) { case unknownVal: return 0, false } - panic(fmt.Sprintf("invalid Uint64Val(%v)", x)) + panic(fmt.Sprintf("%v not an Int", x)) } // Float64Val returns the nearest Go float64 value of x and whether the result is exact; -// x must be numeric but not Complex. The result is (0, false) for unknown values. +// x must be numeric but not Complex, or Unknown. +// If x is Unknown, the result is (0, false). func Float64Val(x Value) (float64, bool) { switch x := x.(type) { case int64Val: @@ -251,12 +259,12 @@ func Float64Val(x Value) (float64, bool) { case unknownVal: return 0, false } - panic(fmt.Sprintf("invalid Float64Val(%v)", x)) + panic(fmt.Sprintf("%v not a Float", x)) } -// BitLen() returns the number of bits required to represent -// the absolute value x in binary representation; x must be an Int. -// The result is 0 for unknown values. +// BitLen returns the number of bits required to represent +// the absolute value x in binary representation; x must be an Int or an Unknown. +// If x is Unknown, the result is 0. func BitLen(x Value) int { switch x := x.(type) { case int64Val: @@ -266,13 +274,12 @@ func BitLen(x Value) int { case unknownVal: return 0 } - panic(fmt.Sprintf("invalid BitLen(%v)", x)) + panic(fmt.Sprintf("%v not an Int", x)) } -// Sign returns -1, 0, or 1 depending on whether -// x < 0, x == 0, or x > 0. For complex values z, -// the sign is 0 if z == 0, otherwise it is != 0. -// The result is 1 for unknown values. +// Sign returns -1, 0, or 1 depending on whether x < 0, x == 0, or x > 0; +// x must be numeric or Unknown. For complex values x, the sign is 0 if x == 0, +// otherwise it is != 0. If x is Unknown, the result is 1. func Sign(x Value) int { switch x := x.(type) { case int64Val: @@ -292,50 +299,159 @@ func Sign(x Value) int { case unknownVal: return 1 // avoid spurious division by zero errors } - panic(fmt.Sprintf("invalid Sign(%v)", x)) + panic(fmt.Sprintf("%v not numeric", x)) +} + +// ---------------------------------------------------------------------------- +// Support for serializing/deserializing integers + +const ( + // Compute the size of a Word in bytes. + _m = ^big.Word(0) + _log = _m>>8&1 + _m>>16&1 + _m>>32&1 + wordSize = 1 << _log +) + +// Bytes returns the bytes for the absolute value of x in little- +// endian binary representation; x must be an Int. +func Bytes(x Value) []byte { + var val *big.Int + switch x := x.(type) { + case int64Val: + val = new(big.Int).SetInt64(int64(x)) + case intVal: + val = x.val + default: + panic(fmt.Sprintf("%v not an Int", x)) + } + + words := val.Bits() + bytes := make([]byte, len(words)*wordSize) + + i := 0 + for _, w := range words { + for j := 0; j < wordSize; j++ { + bytes[i] = byte(w) + w >>= 8 + i++ + } + } + // remove leading 0's + for i > 0 && bytes[i-1] == 0 { + i-- + } + + return bytes[:i] +} + +// MakeFromBytes returns the Int value given the bytes of its little-endian +// binary representation. An empty byte slice argument represents 0. +func MakeFromBytes(bytes []byte) Value { + words := make([]big.Word, (len(bytes)+(wordSize-1))/wordSize) + + i := 0 + var w big.Word + var s uint + for _, b := range bytes { + w |= big.Word(b) << s + if s += 8; s == wordSize*8 { + words[i] = w + i++ + w = 0 + s = 0 + } + } + // store last word + if i < len(words) { + words[i] = w + i++ + } + // remove leading 0's + for i > 0 && words[i-1] == 0 { + i-- + } + + return normInt(new(big.Int).SetBits(words[:i])) +} + +// ---------------------------------------------------------------------------- +// Support for disassembling fractions + +// Num returns the numerator of x; x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown, otherwise it is an Int. +func Num(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal: + return x + case floatVal: + return normInt(x.val.Num()) + } + panic(fmt.Sprintf("%v not Int or Float", x)) +} + +// Denom returns the denominator of x; x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown, otherwise it is an Int >= 1. +func Denom(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal: + return int64Val(1) + case floatVal: + return normInt(x.val.Denom()) + } + panic(fmt.Sprintf("%v not Int or Float", x)) } // ---------------------------------------------------------------------------- // Support for assembling/disassembling complex numbers // MakeImag returns the numeric value x*i (possibly 0); -// x must be numeric but not Complex. -// The result is unknown for unknown values. +// x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown. func MakeImag(x Value) Value { var im *big.Rat switch x := x.(type) { + case unknownVal: + return x case int64Val: im = big.NewRat(int64(x), 1) case intVal: im = new(big.Rat).SetFrac(x.val, int1) case floatVal: im = x.val - case unknownVal: - return x default: - panic(fmt.Sprintf("invalid MakeImag(%v)", x)) + panic(fmt.Sprintf("%v not Int or Float", x)) } return normComplex(rat0, im) } -// Real returns the real part of x, which must be a numeric value. -// The result is unknown for unknown values. +// Real returns the real part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. func Real(x Value) Value { - if z, ok := x.(complexVal); ok { - return normFloat(z.re) + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal, floatVal: + return int64Val(0) + case complexVal: + return normFloat(x.re) } - // TODO(gri) should we check explicit for unknownVal and disallow all others? - return x + panic(fmt.Sprintf("%v not numeric", x)) } -// Imag returns the imaginary part of x, which must be a numeric value. -// The result is 0 for unknown values. +// Imag returns the imaginary part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. func Imag(x Value) Value { - if z, ok := x.(complexVal); ok { - return normFloat(z.im) + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal, floatVal: + return int64Val(0) + case complexVal: + return normFloat(x.im) } - // TODO(gri) should we check explicit for unknownVal and disallow all others? - return int64Val(0) + panic(fmt.Sprintf("%v not numeric", x)) } // ---------------------------------------------------------------------------- @@ -356,6 +472,7 @@ func is63bit(x int64) bool { // UnaryOp returns the result of the unary expression op y. // The operation must be defined for the operand. // If size >= 0 it specifies the ^ (xor) result size in bytes. +// If y is Unknown, the result is Unknown. // func UnaryOp(op token.Token, y Value, size int) Value { switch op { @@ -367,6 +484,8 @@ func UnaryOp(op token.Token, y Value, size int) Value { case token.SUB: switch y := y.(type) { + case unknownVal: + return y case int64Val: if z := -y; z != y { return z // no overflow @@ -383,6 +502,8 @@ func UnaryOp(op token.Token, y Value, size int) Value { case token.XOR: var z big.Int switch y := y.(type) { + case unknownVal: + return y case int64Val: z.Not(big.NewInt(int64(y))) case intVal: @@ -400,7 +521,12 @@ func UnaryOp(op token.Token, y Value, size int) Value { return normInt(&z) case token.NOT: - return !y.(boolVal) + switch y := y.(type) { + case unknownVal: + return y + case boolVal: + return !y + } } Error: @@ -429,7 +555,8 @@ func ord(x Value) int { // match returns the matching representation (same type) with the // smallest complexity for two values x and y. If one of them is -// numeric, both of them must be numeric. +// numeric, both of them must be numeric. If one of them is Unknown, +// both results are Unknown. // func match(x, y Value) (_, _ Value) { if ord(x) > ord(y) { @@ -439,7 +566,10 @@ func match(x, y Value) (_, _ Value) { // ord(x) <= ord(y) switch x := x.(type) { - case unknownVal, boolVal, stringVal, complexVal: + case unknownVal: + return x, x + + case boolVal, stringVal, complexVal: return x, y case int64Val: @@ -477,7 +607,8 @@ func match(x, y Value) (_, _ Value) { } // BinaryOp returns the result of the binary expression x op y. -// The operation must be defined for the operands. +// The operation must be defined for the operands. If one of the +// operands is Unknown, the result is Unknown. // To force integer division of Int operands, use op == token.QUO_ASSIGN // instead of token.QUO; the result is guaranteed to be Int in this case. // Division by zero leads to a run-time panic. @@ -639,7 +770,7 @@ Error: // Shift returns the result of the shift expression x op s // with op == token.SHL or token.SHR (<< or >>). x must be -// an Int. +// an Int or an Unknown. If x is Unknown, the result is x. // func Shift(x Value, op token.Token, s uint) Value { switch x := x.(type) { @@ -694,13 +825,15 @@ func cmpZero(x int, op token.Token) bool { // Compare returns the result of the comparison x op y. // The comparison must be defined for the operands. +// If one of the operands is Unknown, the result is +// false. // func Compare(x Value, op token.Token, y Value) bool { x, y = match(x, y) switch x := x.(type) { case unknownVal: - return true + return false case boolVal: y := y.(boolVal) diff --git a/go/exact/exact_test.go b/go/exact/exact_test.go index d7767d4e..c5179708 100644 --- a/go/exact/exact_test.go +++ b/go/exact/exact_test.go @@ -12,14 +12,19 @@ import ( // TODO(gri) expand this test framework -var tests = []string{ +var opTests = []string{ // unary operations `+ 0 = 0`, + `+ ? = ?`, `- 1 = -1`, + `- ? = ?`, `^ 0 = -1`, + `^ ? = ?`, `! true = false`, `! false = true`, + `! ? = ?`, + // etc. // binary operations @@ -33,35 +38,51 @@ var tests = []string{ `0 + 0.1i = 0.1i`, `0.1 + 0.9 = 1`, `1e100 + 1e100 = 2e100`, + `? + 0 = ?`, + `0 + ? = ?`, `0 - 0 = 0`, `0 - 0.1 = -0.1`, `0 - 0.1i = -0.1i`, `1e100 - 1e100 = 0`, + `? - 0 = ?`, + `0 - ? = ?`, `0 * 0 = 0`, `1 * 0.1 = 0.1`, `1 * 0.1i = 0.1i`, `1i * 1i = -1`, + `? * 0 = ?`, + `0 * ? = ?`, `0 / 0 = "division_by_zero"`, `10 / 2 = 5`, `5 / 3 = 5/3`, `5i / 3i = 5/3`, + `? / 0 = ?`, + `0 / ? = ?`, `0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for / `10 % 3 = 1`, + `? % 0 = ?`, + `0 % ? = ?`, `0 & 0 = 0`, `12345 & 0 = 0`, `0xff & 0xf = 0xf`, + `? & 0 = ?`, + `0 & ? = ?`, `0 | 0 = 0`, `12345 | 0 = 12345`, `0xb | 0xa0 = 0xab`, + `? | 0 = ?`, + `0 | ? = ?`, `0 ^ 0 = 0`, `1 ^ -1 = -2`, + `? ^ 0 = ?`, + `0 ^ ? = ?`, `0 &^ 0 = 0`, `0xf &^ 1 = 0xe`, @@ -73,6 +94,8 @@ var tests = []string{ `1 << 10 = 1024`, `0 >> 0 = 0`, `1024 >> 10 == 1`, + `? << 0 == ?`, + `? >> 10 == ?`, // etc. // comparisons @@ -106,11 +129,26 @@ var tests = []string{ `1/123456788 <= 1/123456789 == false`, `0.11 > 0.11 = false`, `0.11 >= 0.11 = true`, + + `? == 0 = false`, + `? != 0 = false`, + `? < 10 = false`, + `? <= 10 = false`, + `? > 10 = false`, + `? >= 10 = false`, + + `0 == ? = false`, + `0 != ? = false`, + `0 < ? = false`, + `10 <= ? = false`, + `0 > ? = false`, + `10 >= ? = false`, + // etc. } func TestOps(t *testing.T) { - for _, test := range tests { + for _, test := range opTests { a := strings.Split(test, " ") i := 0 // operator index @@ -136,18 +174,27 @@ func TestOps(t *testing.T) { got := doOp(x, op, y) want := val(a[i+3]) - if !Compare(got, token.EQL, want) { + if !eql(got, want) { t.Errorf("%s: got %s; want %s", test, got, want) } - if x0 != nil && !Compare(x, token.EQL, x0) { + if x0 != nil && !eql(x, x0) { t.Errorf("%s: x changed to %s", test, x) } - if !Compare(y, token.EQL, y0) { + if !eql(y, y0) { t.Errorf("%s: y changed to %s", test, y) } } } +func eql(x, y Value) bool { + _, ux := x.(unknownVal) + _, uy := y.(unknownVal) + if ux || uy { + return ux == uy + } + return Compare(x, token.EQL, y) +} + // ---------------------------------------------------------------------------- // Support functions @@ -238,3 +285,64 @@ func doOp(x Value, op token.Token, y Value) (z Value) { return BinaryOp(x, op, y) } } + +// ---------------------------------------------------------------------------- +// Other tests + +var fracTests = []string{ + "0 0 1", + "1 1 1", + "-1 -1 1", + "1.2 6 5", + "-0.991 -991 1000", + "1e100 1e100 1", +} + +func TestFractions(t *testing.T) { + for _, test := range fracTests { + a := strings.Split(test, " ") + if len(a) != 3 { + t.Errorf("invalid test case: %s", test) + continue + } + + x := val(a[0]) + n := val(a[1]) + d := val(a[2]) + + if got := Num(x); !eql(got, n) { + t.Errorf("%s: got num = %s; want %s", test, got, n) + } + + if got := Denom(x); !eql(got, d) { + t.Errorf("%s: got denom = %s; want %s", test, got, d) + } + } +} + +var bytesTests = []string{ + "0", + "1", + "123456789", + "123456789012345678901234567890123456789012345678901234567890", +} + +func TestBytes(t *testing.T) { + for _, test := range bytesTests { + x := val(test) + bytes := Bytes(x) + + // special case 0 + if Sign(x) == 0 && len(bytes) != 0 { + t.Errorf("%s: got %v; want empty byte slice", test, bytes) + } + + if n := len(bytes); n > 0 && bytes[n-1] == 0 { + t.Errorf("%s: got %v; want no leading 0 byte", test, bytes) + } + + if got := MakeFromBytes(bytes); !eql(got, x) { + t.Errorf("%s: got %s; want %s (bytes = %v)", test, got, x, bytes) + } + } +}