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
This commit is contained in:
Robert Griesemer 2013-12-12 13:43:00 -08:00
parent 4728c2f3f6
commit e2828468ff
2 changed files with 298 additions and 57 deletions

View File

@ -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)

View File

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