From d0c17461a785a03781e4cdbd18f7f3ba04d4b9db Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 2 Dec 2015 17:41:39 -0800 Subject: [PATCH] go/constant: switch to floating-point representation when fractions become too large Use two internal representations for Float values (similar to what is done for Int values). Transparently switch to a big.Float representation when big.Rat values become unwieldy. This is almost never needed for real-world programs but it is trivial to create test cases that cannot be handled with rational arithmetic alone. As a consequence, the go/constant API semantics changes slightly: Until now, a value could always be represented in its "smallest" form (e.g., float values that happened to be integers would be represented as integers). Now, constant Kind depends on how the value was created, rather than its actual value. (The reason why we cannot automatically "normalize" values to their smallest form anymore is because floating-point numbers are not exact in general; and thus normalization is often not possible in the first place, or would throw away precision when it is not desired.) This has repercussions as to how constant Values are used go/types and required corresponding adjustments. Details of the changes: go/constant package: - use big.Rat and big.Float values to represent floating-point values (internal change) - changed semantic of Value.Kind accordingly - String now returns a short, human-readable form of a value (this leads to better error messages in go/types) - added ToInt, ToFloat, and ToComplex conversion functions - added ExactString to obtain an exact string form of a value go/types: - adjusted and simplified implementation of representableConst - adjusted various places where Value.Kind was expected to be "smallest" by calling the respective ToInt/Float/Complex conversion functions - enabled 5 disabled tests in stdlib_test.go that now work api checker: - print all constant values in a short human-readable form (floats are printed in floating-point form), but also print an exact form if it is different from the short form - adjusted test golden file and go.1.1.text reference file Fixes #11327. Change-Id: I492b704aae5b0238e5b7cee13e18ffce61193587 Reviewed-on: https://go-review.googlesource.com/17360 Reviewed-by: Alan Donovan Run-TryBot: Robert Griesemer TryBot-Result: Gobot Gobot --- api/go1.1.txt | 30 +- src/cmd/api/goapi.go | 9 +- src/cmd/api/testdata/src/pkg/p1/golden.txt | 2 +- src/go/constant/value.go | 675 +++++++++++++----- src/go/constant/value_test.go | 115 ++- .../internal/gccgoimporter/importer_test.go | 8 +- src/go/types/api_test.go | 32 +- src/go/types/builtins.go | 2 +- src/go/types/conversions.go | 2 +- src/go/types/eval_test.go | 2 +- src/go/types/expr.go | 129 ++-- src/go/types/operand.go | 21 +- src/go/types/stdlib_test.go | 11 +- src/go/types/testdata/const0.src | 13 + src/go/types/testdata/decls0.src | 5 +- src/go/types/testdata/errors.src | 4 +- src/go/types/typexpr.go | 21 +- 17 files changed, 753 insertions(+), 328 deletions(-) diff --git a/api/go1.1.txt b/api/go1.1.txt index 0b27523ab55134..81d095704a4c9c 100644 --- a/api/go1.1.txt +++ b/api/go1.1.txt @@ -1983,13 +1983,13 @@ pkg log/syslog (openbsd-amd64-cgo), const LOG_SYSLOG = 40 pkg log/syslog (openbsd-amd64-cgo), const LOG_USER = 8 pkg log/syslog (openbsd-amd64-cgo), const LOG_UUCP = 64 pkg log/syslog (openbsd-amd64-cgo), const LOG_WARNING = 4 -pkg math, const E = 271828182845904523536028747135266249775724709369995957496696763/100000000000000000000000000000000000000000000000000000000000000 -pkg math, const Ln10 = 23025850929940456840179914546843642076011014886287729760333279/10000000000000000000000000000000000000000000000000000000000000 -pkg math, const Ln2 = 693147180559945309417232121458176568075500134360255254120680009/1000000000000000000000000000000000000000000000000000000000000000 -pkg math, const Log10E = 10000000000000000000000000000000000000000000000000000000000000/23025850929940456840179914546843642076011014886287729760333279 -pkg math, const Log2E = 1000000000000000000000000000000000000000000000000000000000000000/693147180559945309417232121458176568075500134360255254120680009 -pkg math, const MaxFloat32 = 340282346638528859811704183484516925440 -pkg math, const MaxFloat64 = 179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +pkg math, const E = 2.71828 // 271828182845904523536028747135266249775724709369995957496696763/100000000000000000000000000000000000000000000000000000000000000 +pkg math, const Ln10 = 2.30259 // 23025850929940456840179914546843642076011014886287729760333279/10000000000000000000000000000000000000000000000000000000000000 +pkg math, const Ln2 = 0.693147 // 693147180559945309417232121458176568075500134360255254120680009/1000000000000000000000000000000000000000000000000000000000000000 +pkg math, const Log10E = 0.434294 // 10000000000000000000000000000000000000000000000000000000000000/23025850929940456840179914546843642076011014886287729760333279 +pkg math, const Log2E = 1.4427 // 1000000000000000000000000000000000000000000000000000000000000000/693147180559945309417232121458176568075500134360255254120680009 +pkg math, const MaxFloat32 = 3.40282e+38 // 340282346638528859811704183484516925440 +pkg math, const MaxFloat64 = 1.79769e+308 // 179769313486231570814527423731704356798100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 pkg math, const MaxInt16 = 32767 pkg math, const MaxInt32 = 2147483647 pkg math, const MaxInt64 = 9223372036854775807 @@ -2002,14 +2002,14 @@ pkg math, const MinInt16 = -32768 pkg math, const MinInt32 = -2147483648 pkg math, const MinInt64 = -9223372036854775808 pkg math, const MinInt8 = -128 -pkg math, const Phi = 80901699437494742410229341718281905886015458990288143106772431/50000000000000000000000000000000000000000000000000000000000000 -pkg math, const Pi = 314159265358979323846264338327950288419716939937510582097494459/100000000000000000000000000000000000000000000000000000000000000 -pkg math, const SmallestNonzeroFloat32 = 17516230804060213386546619791123951641/12500000000000000000000000000000000000000000000000000000000000000000000000000000000 -pkg math, const SmallestNonzeroFloat64 = 4940656458412465441765687928682213723651/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -pkg math, const Sqrt2 = 70710678118654752440084436210484903928483593768847403658833987/50000000000000000000000000000000000000000000000000000000000000 -pkg math, const SqrtE = 164872127070012814684865078781416357165377610071014801157507931/100000000000000000000000000000000000000000000000000000000000000 -pkg math, const SqrtPhi = 63600982475703448212621123086874574585780402092004812430832019/50000000000000000000000000000000000000000000000000000000000000 -pkg math, const SqrtPi = 177245385090551602729816748334114518279754945612238712821380779/100000000000000000000000000000000000000000000000000000000000000 +pkg math, const Phi = 1.61803 // 80901699437494742410229341718281905886015458990288143106772431/50000000000000000000000000000000000000000000000000000000000000 +pkg math, const Pi = 3.14159 // 314159265358979323846264338327950288419716939937510582097494459/100000000000000000000000000000000000000000000000000000000000000 +pkg math, const SmallestNonzeroFloat32 = 1.4013e-45 // 17516230804060213386546619791123951641/12500000000000000000000000000000000000000000000000000000000000000000000000000000000 +pkg math, const SmallestNonzeroFloat64 = 4.94066e-324 // 4940656458412465441765687928682213723651/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +pkg math, const Sqrt2 = 1.41421 // 70710678118654752440084436210484903928483593768847403658833987/50000000000000000000000000000000000000000000000000000000000000 +pkg math, const SqrtE = 1.64872 // 164872127070012814684865078781416357165377610071014801157507931/100000000000000000000000000000000000000000000000000000000000000 +pkg math, const SqrtPhi = 1.27202 // 63600982475703448212621123086874574585780402092004812430832019/50000000000000000000000000000000000000000000000000000000000000 +pkg math, const SqrtPi = 1.77245 // 177245385090551602729816748334114518279754945612238712821380779/100000000000000000000000000000000000000000000000000000000000000 pkg math/big, const MaxBase = 36 pkg math/big, method (*Int) MarshalJSON() ([]uint8, error) pkg math/big, method (*Int) SetUint64(uint64) *Int diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go index 5d1cf05e31b44f..8b20d123b15b57 100644 --- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -680,7 +680,14 @@ func (w *Walker) emitObj(obj types.Object) { switch obj := obj.(type) { case *types.Const: w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type())) - w.emitf("const %s = %s", obj.Name(), obj.Val()) + x := obj.Val() + short := x.String() + exact := x.ExactString() + if short == exact { + w.emitf("const %s = %s", obj.Name(), short) + } else { + w.emitf("const %s = %s // %s", obj.Name(), short, exact) + } case *types.Var: w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type())) case *types.TypeName: diff --git a/src/cmd/api/testdata/src/pkg/p1/golden.txt b/src/cmd/api/testdata/src/pkg/p1/golden.txt index 3c43a226ff7bd6..0378a568701758 100644 --- a/src/cmd/api/testdata/src/pkg/p1/golden.txt +++ b/src/cmd/api/testdata/src/pkg/p1/golden.txt @@ -10,7 +10,7 @@ pkg p1, const ConstChase2 = 11 pkg p1, const ConstChase2 ideal-int pkg p1, const ConversionConst = 5 pkg p1, const ConversionConst MyInt -pkg p1, const FloatConst = 3/2 +pkg p1, const FloatConst = 1.5 // 3/2 pkg p1, const FloatConst ideal-float pkg p1, const StrConst = "foo" pkg p1, const StrConst ideal-string diff --git a/src/go/constant/value.go b/src/go/constant/value.go index 8a2dda060c08b4..04a2ac981efb52 100644 --- a/src/go/constant/value.go +++ b/src/go/constant/value.go @@ -3,8 +3,7 @@ // license that can be found in the LICENSE file. // Package constant implements Values representing untyped -// Go constants and the corresponding operations. Values -// and operations may have arbitrary or unlimited precision. +// Go constants and their corresponding operations. // // A special Unknown value may be used when a value // is unknown due to an error. Operations on unknown @@ -16,16 +15,15 @@ package constant // import "go/constant" import ( "fmt" "go/token" + "math" "math/big" "strconv" + "unicode/utf8" ) // Kind specifies the kind of value represented by a Value. type Kind int -// Implementation note: Kinds must be enumerated in -// order of increasing "complexity" (used by match). - const ( // unknown values Unknown Kind = iota @@ -40,15 +38,20 @@ const ( Complex ) -// A Value represents a mathematically exact value of a given Kind. +// A Value represents the value of a Go constant. type Value interface { - // Kind returns the value kind; it is always the smallest - // kind in which the value can be represented exactly. + // Kind returns the value kind. Kind() Kind - // String returns a human-readable form of the value. + // String returns a short, human-readable form of the value. + // For numeric values, the result may be an approximation; + // for String values the result may be a shortened string. + // Use ExactString for a string representing a value exactly. String() string + // ExactString returns an exact, printable form of the value. + ExactString() string + // Prevent external implementations. implementsValue() } @@ -56,14 +59,19 @@ type Value interface { // ---------------------------------------------------------------------------- // Implementations +// Maximum supported mantissa precision. +// The spec requires at least 256 bits; typical implementations use 512 bits. +const prec = 512 + type ( unknownVal struct{} boolVal bool stringVal string - int64Val int64 - intVal struct{ val *big.Int } - floatVal struct{ val *big.Rat } - complexVal struct{ re, im *big.Rat } + int64Val int64 // Int values representable as an int64 + intVal struct{ val *big.Int } // Int values not representable as an int64 + ratVal struct{ val *big.Rat } // Float values representable as a fraction + floatVal struct{ val *big.Float } // Float values not representable as a fraction + complexVal struct{ re, im Value } ) func (unknownVal) Kind() Kind { return Unknown } @@ -71,52 +79,187 @@ func (boolVal) Kind() Kind { return Bool } func (stringVal) Kind() Kind { return String } func (int64Val) Kind() Kind { return Int } func (intVal) Kind() Kind { return Int } +func (ratVal) Kind() Kind { return Float } func (floatVal) Kind() Kind { return Float } func (complexVal) Kind() Kind { return Complex } -func (unknownVal) String() string { return "unknown" } -func (x boolVal) String() string { return fmt.Sprintf("%v", bool(x)) } -func (x stringVal) String() string { return strconv.Quote(string(x)) } -func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } -func (x intVal) String() string { return x.val.String() } -func (x floatVal) String() string { return x.val.String() } +func (unknownVal) String() string { return "unknown" } +func (x boolVal) String() string { return strconv.FormatBool(bool(x)) } + +// String returns a possibly shortened quoted form of the String value. +func (x stringVal) String() string { + const maxLen = 72 // a reasonable length + s := strconv.Quote(string(x)) + if utf8.RuneCountInString(s) > maxLen { + // The string without the enclosing quotes is greater than maxLen-2 runes + // long. Remove the last 3 runes (including the closing '"') by keeping + // only the first maxLen-3 runes; then add "...". + i := 0 + for n := 0; n < maxLen-3; n++ { + _, size := utf8.DecodeRuneInString(s) + i += size + } + s = s[:i] + "..." + } + return s +} + +func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } +func (x intVal) String() string { return x.val.String() } +func (x ratVal) String() string { return rtof(x).String() } + +// String returns returns a decimal approximation of the Float value. +func (x floatVal) String() string { + f := x.val + + // Don't try to convert infinities (will not terminate). + if f.IsInf() { + return f.String() + } + + // Use exact fmt formatting if in float64 range (common case): + // proceed if f doesn't underflow to 0 or overflow to inf. + if x, _ := f.Float64(); f.Sign() == 0 == (x == 0) && !math.IsInf(x, 0) { + return fmt.Sprintf("%.6g", x) + } + + // Out of float64 range. Do approximate manual to decimal + // conversion to avoid precise but possibly slow Float + // formatting. + // f = mant * 2**exp + var mant big.Float + exp := f.MantExp(&mant) // 0.5 <= |mant| < 1.0 + + // approximate float64 mantissa m and decimal exponent d + // f ~ m * 10**d + m, _ := mant.Float64() // 0.5 <= |m| < 1.0 + d := float64(exp) * (math.Ln2 / math.Ln10) // log_10(2) + + // adjust m for truncated (integer) decimal exponent e + e := int64(d) + m *= math.Pow(10, d-float64(e)) + + // ensure 1 <= |m| < 10 + switch am := math.Abs(m); { + case am < 1-0.5e-6: + // The %.6g format below rounds m to 5 digits after the + // decimal point. Make sure that m*10 < 10 even after + // rounding up: m*10 + 0.5e-5 < 10 => m < 1 - 0.5e6. + m *= 10 + e-- + case am >= 10: + m /= 10 + e++ + } + + return fmt.Sprintf("%.6ge%+d", m, e) +} + func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) } +func (x unknownVal) ExactString() string { return x.String() } +func (x boolVal) ExactString() string { return x.String() } +func (x stringVal) ExactString() string { return strconv.Quote(string(x)) } +func (x int64Val) ExactString() string { return x.String() } +func (x intVal) ExactString() string { return x.String() } + +func (x ratVal) ExactString() string { + r := x.val + if r.IsInt() { + return r.Num().String() + } + return r.String() +} + +func (x floatVal) ExactString() string { return x.val.Text('p', 0) } + +func (x complexVal) ExactString() string { + return fmt.Sprintf("(%s + %si)", x.re.ExactString(), x.im.ExactString()) +} + func (unknownVal) implementsValue() {} func (boolVal) implementsValue() {} func (stringVal) implementsValue() {} func (int64Val) implementsValue() {} +func (ratVal) implementsValue() {} func (intVal) implementsValue() {} func (floatVal) implementsValue() {} func (complexVal) implementsValue() {} -// int64 bounds +func newInt() *big.Int { return new(big.Int) } +func newRat() *big.Rat { return new(big.Rat) } +func newFloat() *big.Float { return new(big.Float).SetPrec(prec) } + +func i64toi(x int64Val) intVal { return intVal{newInt().SetInt64(int64(x))} } +func i64tor(x int64Val) ratVal { return ratVal{newRat().SetInt64(int64(x))} } +func i64tof(x int64Val) floatVal { return floatVal{newFloat().SetInt64(int64(x))} } +func itor(x intVal) ratVal { return ratVal{newRat().SetInt(x.val)} } +func itof(x intVal) floatVal { return floatVal{newFloat().SetInt(x.val)} } + +func rtof(x ratVal) floatVal { + a := newFloat().SetInt(x.val.Num()) + b := newFloat().SetInt(x.val.Denom()) + return floatVal{a.Quo(a, b)} +} + +func vtoc(x Value) complexVal { return complexVal{x, int64Val(0)} } + var ( minInt64 = big.NewInt(-1 << 63) maxInt64 = big.NewInt(1<<63 - 1) ) -func normInt(x *big.Int) Value { +func makeInt(x *big.Int) Value { if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 { return int64Val(x.Int64()) } return intVal{x} } -func normFloat(x *big.Rat) Value { - if x.IsInt() { - return normInt(x.Num()) +// Permit fractions with component sizes up to maxExp +// before switching to using floating-point numbers. +const maxExp = 4 << 10 + +func makeRat(x *big.Rat) Value { + a := x.Num() + b := x.Denom() + if a.BitLen() < maxExp && b.BitLen() < maxExp { + // ok to remain fraction + return ratVal{x} } - return floatVal{x} + // components too large => switch to float + fa := newFloat().SetInt(a) + fb := newFloat().SetInt(b) + return floatVal{fa.Quo(fa, fb)} } -func normComplex(re, im *big.Rat) Value { - if im.Sign() == 0 { - return normFloat(re) +var floatVal0 = floatVal{newFloat()} + +func makeFloat(x *big.Float) Value { + // convert -0 + if x.Sign() == 0 { + return floatVal0 } + return floatVal{x} +} + +func makeComplex(re, im Value) Value { return complexVal{re, im} } +func makeFloatFromLiteral(lit string) Value { + if f, ok := newFloat().SetString(lit); ok { + if f.MantExp(nil) < maxExp { + // ok to use rationals + r, _ := newRat().SetString(lit) + return makeRat(r) + } + // otherwise use floats + return makeFloat(f) + } + return nil +} + // ---------------------------------------------------------------------------- // Factories @@ -133,62 +276,74 @@ func MakeString(s string) Value { return stringVal(s) } func MakeInt64(x int64) Value { return int64Val(x) } // MakeUint64 returns the Int value for x. -func MakeUint64(x uint64) Value { return normInt(new(big.Int).SetUint64(x)) } +func MakeUint64(x uint64) Value { + if x < 1<<63 { + return int64Val(int64(x)) + } + return intVal{newInt().SetUint64(x)} +} -// MakeFloat64 returns the numeric value for x. -// If x is not finite, the result is unknown. +// MakeFloat64 returns the Float value for x. +// If x is not finite, the result is an Unknown. func MakeFloat64(x float64) Value { - if f := new(big.Rat).SetFloat64(x); f != nil { - return normFloat(f) + if math.IsInf(x, 0) || math.IsNaN(x) { + return unknownVal{} } - return unknownVal{} + // convert -0 to 0 + if x == 0 { + return int64Val(0) + } + return ratVal{newRat().SetFloat64(x)} } // MakeFromLiteral returns the corresponding integer, floating-point, -// imaginary, character, or string value for a Go literal string. -// If prec > 0, prec specifies an upper limit for the precision of -// a numeric value. If the literal string is invalid, the result is -// nil. -// BUG(gri) Only prec == 0 is supported at the moment. -func MakeFromLiteral(lit string, tok token.Token, prec uint) Value { - if prec != 0 { - panic("limited precision not supported") +// imaginary, character, or string value for a Go literal string. The +// tok value must be one of token.INT, token.FLOAT, toke.IMAG, token. +// CHAR, or token.STRING. The final argument must be zero. +// If the literal string syntax is invalid, the result is an Unknown. +func MakeFromLiteral(lit string, tok token.Token, zero uint) Value { + if zero != 0 { + panic("MakeFromLiteral called with non-zero last argument") } + switch tok { case token.INT: if x, err := strconv.ParseInt(lit, 0, 64); err == nil { return int64Val(x) } - if x, ok := new(big.Int).SetString(lit, 0); ok { + if x, ok := newInt().SetString(lit, 0); ok { return intVal{x} } case token.FLOAT: - if x, ok := new(big.Rat).SetString(lit); ok { - return normFloat(x) + if x := makeFloatFromLiteral(lit); x != nil { + return x } case token.IMAG: if n := len(lit); n > 0 && lit[n-1] == 'i' { - if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok { - return normComplex(big.NewRat(0, 1), im) + if im := makeFloatFromLiteral(lit[:n-1]); im != nil { + return makeComplex(int64Val(0), im) } } case token.CHAR: if n := len(lit); n >= 2 { if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { - return int64Val(code) + return MakeInt64(int64(code)) } } case token.STRING: if s, err := strconv.Unquote(lit); err == nil { - return stringVal(s) + return MakeString(s) } + + default: + panic(fmt.Sprintf("%v is not a valid token", tok)) } - return nil + return unknownVal{} } // ---------------------------------------------------------------------------- @@ -205,8 +360,9 @@ func BoolVal(x Value) bool { return bool(x) case unknownVal: return false + default: + panic(fmt.Sprintf("%v not a Bool", x)) } - panic(fmt.Sprintf("%v not a Bool", x)) } // StringVal returns the Go string value of x, which must be a String or an Unknown. @@ -217,8 +373,9 @@ func StringVal(x Value) string { return string(x) case unknownVal: return "" + default: + panic(fmt.Sprintf("%v not a String", x)) } - panic(fmt.Sprintf("%v not a String", x)) } // Int64Val returns the Go int64 value of x and whether the result is exact; @@ -229,11 +386,12 @@ func Int64Val(x Value) (int64, bool) { case int64Val: return int64(x), true case intVal: - return x.val.Int64(), x.val.BitLen() <= 63 + return x.val.Int64(), false // not an int64Val and thus not exact case unknownVal: return 0, false + default: + panic(fmt.Sprintf("%v not an Int", x)) } - panic(fmt.Sprintf("%v not an Int", x)) } // Uint64Val returns the Go uint64 value of x and whether the result is exact; @@ -247,8 +405,9 @@ func Uint64Val(x Value) (uint64, bool) { return x.val.Uint64(), x.val.Sign() >= 0 && x.val.BitLen() <= 64 case unknownVal: return 0, false + default: + panic(fmt.Sprintf("%v not an Int", x)) } - panic(fmt.Sprintf("%v not an Int", x)) } // Float32Val is like Float64Val but for float32 instead of float64. @@ -258,17 +417,22 @@ func Float32Val(x Value) (float32, bool) { f := float32(x) return f, int64Val(f) == x case intVal: - return new(big.Rat).SetFrac(x.val, int1).Float32() - case floatVal: + f, acc := newFloat().SetInt(x.val).Float32() + return f, acc == big.Exact + case ratVal: return x.val.Float32() + case floatVal: + f, acc := x.val.Float32() + return f, acc == big.Exact case unknownVal: return 0, false + default: + panic(fmt.Sprintf("%v not a Float", x)) } - panic(fmt.Sprintf("%v not a Float", x)) } // Float64Val returns the nearest Go float64 value of x and whether the result is exact; -// x must be numeric but not Complex, or Unknown. For values too small (too close to 0) +// x must be numeric or an Unknown, but not Complex. For values too small (too close to 0) // to represent as float64, Float64Val silently underflows to 0. The result sign always // matches the sign of x, even for 0. // If x is Unknown, the result is (0, false). @@ -278,13 +442,18 @@ func Float64Val(x Value) (float64, bool) { f := float64(int64(x)) return f, int64Val(f) == x case intVal: - return new(big.Rat).SetFrac(x.val, int1).Float64() - case floatVal: + f, acc := newFloat().SetInt(x.val).Float64() + return f, acc == big.Exact + case ratVal: return x.val.Float64() + case floatVal: + f, acc := x.val.Float64() + return f, acc == big.Exact case unknownVal: return 0, false + default: + panic(fmt.Sprintf("%v not a Float", x)) } - panic(fmt.Sprintf("%v not a Float", x)) } // BitLen returns the number of bits required to represent @@ -293,13 +462,14 @@ func Float64Val(x Value) (float64, bool) { func BitLen(x Value) int { switch x := x.(type) { case int64Val: - return new(big.Int).SetInt64(int64(x)).BitLen() + return i64toi(x).val.BitLen() case intVal: return x.val.BitLen() case unknownVal: return 0 + default: + panic(fmt.Sprintf("%v not an Int", 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; @@ -317,18 +487,21 @@ func Sign(x Value) int { return 0 case intVal: return x.val.Sign() + case ratVal: + return x.val.Sign() case floatVal: return x.val.Sign() case complexVal: - return x.re.Sign() | x.im.Sign() + return Sign(x.re) | Sign(x.im) case unknownVal: return 1 // avoid spurious division by zero errors + default: + panic(fmt.Sprintf("%v not numeric", x)) } - panic(fmt.Sprintf("%v not numeric", x)) } // ---------------------------------------------------------------------------- -// Support for serializing/deserializing integers +// Support for assembling/disassembling numeric values const ( // Compute the size of a Word in bytes. @@ -340,17 +513,17 @@ const ( // 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 + var t intVal switch x := x.(type) { case int64Val: - val = new(big.Int).SetInt64(int64(x)) + t = i64toi(x) case intVal: - val = x.val + t = x default: panic(fmt.Sprintf("%v not an Int", x)) } - words := val.Bits() + words := t.val.Bits() bytes := make([]byte, len(words)*wordSize) i := 0 @@ -396,72 +569,106 @@ func MakeFromBytes(bytes []byte) Value { i-- } - return normInt(new(big.Int).SetBits(words[:i])) + return makeInt(newInt().SetBits(words[:i])) } -// ---------------------------------------------------------------------------- -// Support for disassembling fractions +// toRat returns the fraction corresponding to x, or nil +// if x cannot be represented as a fraction a/b because +// its components a or b are too large. +func toRat(x *big.Float) *big.Rat { + m := newFloat() + e := x.MantExp(m) + + // fail to convert if fraction components are too large + if e <= maxExp || e >= maxExp { + return nil + } + + // convert mantissa to big.Int value by shifting by ecorr + ecorr := int(m.MinPrec()) + a, _ := m.SetMantExp(m, ecorr).Int(nil) + e -= ecorr // correct exponent + + // compute actual fraction + b := big.NewInt(1) + switch { + case e < 0: + b.Lsh(b, uint(-e)) + case e > 0: + a.Lsh(a, uint(e)) + } + + return new(big.Rat).SetFrac(a, b) +} // 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 +// If x is Unknown, or if it is too large or small to represent as a +// fraction, the result is Unknown. Otherwise the result is an Int // with the same sign as x. func Num(x Value) Value { switch x := x.(type) { - case unknownVal, int64Val, intVal: + case int64Val, intVal: return x + case ratVal: + return makeInt(x.val.Num()) case floatVal: - return normInt(x.val.Num()) + if r := toRat(x.val); r != nil { + return makeInt(r.Num()) + } + case unknownVal: + break + default: + panic(fmt.Sprintf("%v not Int or Float", x)) } - panic(fmt.Sprintf("%v not Int or Float", x)) + return unknownVal{} } // 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. +// If x is Unknown, or if it is too large or small to represent as a +// fraction, the result is Unknown. Otherwise the result is an Int >= 1. func Denom(x Value) Value { switch x := x.(type) { - case unknownVal: - return x case int64Val, intVal: return int64Val(1) + case ratVal: + return makeInt(x.val.Denom()) case floatVal: - return normInt(x.val.Denom()) + if r := toRat(x.val); r != nil { + return makeInt(r.Denom()) + } + case unknownVal: + break + default: + panic(fmt.Sprintf("%v not Int or Float", x)) } - panic(fmt.Sprintf("%v not Int or Float", x)) + return unknownVal{} } -// ---------------------------------------------------------------------------- -// Support for assembling/disassembling complex numbers - -// MakeImag returns the numeric value x*i (possibly 0); +// MakeImag returns the Complex value x*i; // 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) { + switch 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 int64Val, intVal, ratVal, floatVal: + return makeComplex(int64Val(0), x) default: 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 or unknown value. // If x is Unknown, the result is Unknown. func Real(x Value) Value { switch x := x.(type) { - case unknownVal, int64Val, intVal, floatVal: + case unknownVal, int64Val, intVal, ratVal, floatVal: return x case complexVal: - return normFloat(x.re) + return x.re + default: + panic(fmt.Sprintf("%v not numeric", x)) } - panic(fmt.Sprintf("%v not numeric", x)) } // Imag returns the imaginary part of x, which must be a numeric or unknown value. @@ -470,12 +677,106 @@ func Imag(x Value) Value { switch x := x.(type) { case unknownVal: return x - case int64Val, intVal, floatVal: + case int64Val, intVal, ratVal, floatVal: return int64Val(0) case complexVal: - return normFloat(x.im) + return x.im + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// ---------------------------------------------------------------------------- +// Numeric conversions + +// ToInt converts x to an Int value if x is representable as an Int. +// Otherwise it returns an Unknown. +func ToInt(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return x + + case ratVal: + if x.val.IsInt() { + return makeInt(x.val.Num()) + } + + case floatVal: + // avoid creation of huge integers + // (existing tests require permitting exponents of at least 1024) + if x.val.MantExp(nil) <= 1024 { + i := newInt() + if _, acc := x.val.Int(i); acc == big.Exact { + return makeInt(i) + } + + // If we can get an integer by rounding up or down, + // assume x is not an integer because of rounding + // errors in prior computations. + + const delta = 4 // a small number of bits > 0 + var t big.Float + t.SetPrec(prec - delta) + + // try rounding down a little + t.SetMode(big.ToZero) + t.Set(x.val) + if _, acc := t.Int(i); acc == big.Exact { + return makeInt(i) + } + + // try rounding up a little + t.SetMode(big.AwayFromZero) + t.Set(x.val) + if _, acc := t.Int(i); acc == big.Exact { + return makeInt(i) + } + } + + case complexVal: + if re := ToFloat(x); re.Kind() == Float { + return ToInt(re) + } + } + + return unknownVal{} +} + +// ToFloat converts x to a Float value if x is representable as a Float. +// Otherwise it returns an Unknown. +func ToFloat(x Value) Value { + switch x := x.(type) { + case int64Val: + return i64tof(x) + case intVal: + return itof(x) + case ratVal, floatVal: + return x + case complexVal: + if im := ToInt(x.im); im.Kind() == Int && Sign(im) == 0 { + // imaginary component is 0 + return ToFloat(x.re) + } + } + return unknownVal{} +} + +// ToComplex converts x to a Complex value if x is representable as a Complex. +// Otherwise it returns an Unknown. +func ToComplex(x Value) Value { + switch x := x.(type) { + case int64Val: + return vtoc(i64tof(x)) + case intVal: + return vtoc(itof(x)) + case ratVal: + return vtoc(x) + case floatVal: + return vtoc(x) + case complexVal: + return x } - panic(fmt.Sprintf("%v not numeric", x)) + return unknownVal{} } // ---------------------------------------------------------------------------- @@ -502,7 +803,7 @@ func UnaryOp(op token.Token, y Value, prec uint) Value { switch op { case token.ADD: switch y.(type) { - case unknownVal, int64Val, intVal, floatVal, complexVal: + case unknownVal, int64Val, intVal, ratVal, floatVal, complexVal: return y } @@ -514,17 +815,21 @@ func UnaryOp(op token.Token, y Value, prec uint) Value { if z := -y; z != y { return z // no overflow } - return normInt(new(big.Int).Neg(big.NewInt(int64(y)))) + return makeInt(newInt().Neg(big.NewInt(int64(y)))) case intVal: - return normInt(new(big.Int).Neg(y.val)) + return makeInt(newInt().Neg(y.val)) + case ratVal: + return makeRat(newRat().Neg(y.val)) case floatVal: - return normFloat(new(big.Rat).Neg(y.val)) + return makeFloat(newFloat().Neg(y.val)) case complexVal: - return normComplex(new(big.Rat).Neg(y.re), new(big.Rat).Neg(y.im)) + re := UnaryOp(token.SUB, y.re, 0) + im := UnaryOp(token.SUB, y.im, 0) + return makeComplex(re, im) } case token.XOR: - var z big.Int + z := newInt() switch y := y.(type) { case unknownVal: return y @@ -539,9 +844,9 @@ func UnaryOp(op token.Token, y Value, prec uint) Value { // thus "too large": We must limit the result precision // to the type's precision. if prec > 0 { - z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), prec)) // z &^= (-1)<>). x must be // an Int or an Unknown. If x is Unknown, the result is x. @@ -808,8 +1146,8 @@ func Shift(x Value, op token.Token, s uint) Value { } switch op { case token.SHL: - z := big.NewInt(int64(x)) - return normInt(z.Lsh(z, s)) + z := i64toi(x).val + return makeInt(z.Lsh(z, s)) case token.SHR: return x >> s } @@ -818,12 +1156,12 @@ func Shift(x Value, op token.Token, s uint) Value { if s == 0 { return x } - var z big.Int + z := newInt() switch op { case token.SHL: - return normInt(z.Lsh(x.val, s)) + return makeInt(z.Lsh(x.val, s)) case token.SHR: - return normInt(z.Rsh(x.val, s)) + return makeInt(z.Rsh(x.val, s)) } } @@ -889,18 +1227,21 @@ func Compare(x Value, op token.Token, y Value) bool { case intVal: return cmpZero(x.val.Cmp(y.(intVal).val), op) + case ratVal: + return cmpZero(x.val.Cmp(y.(ratVal).val), op) + case floatVal: return cmpZero(x.val.Cmp(y.(floatVal).val), op) case complexVal: y := y.(complexVal) - re := x.re.Cmp(y.re) - im := x.im.Cmp(y.im) + re := Compare(x.re, token.EQL, y.re) + im := Compare(x.im, token.EQL, y.im) switch op { case token.EQL: - return re == 0 && im == 0 + return re && im case token.NEQ: - return re != 0 || im != 0 + return !re || !im } case stringVal: diff --git a/src/go/constant/value_test.go b/src/go/constant/value_test.go index 08cdd5e625cb8f..932287ffefd7bf 100644 --- a/src/go/constant/value_test.go +++ b/src/go/constant/value_test.go @@ -176,12 +176,17 @@ func TestOps(t *testing.T) { want := val(a[i+3]) if !eql(got, want) { t.Errorf("%s: got %s; want %s", test, got, want) + continue } + if x0 != nil && !eql(x, x0) { t.Errorf("%s: x changed to %s", test, x) + continue } + if !eql(y, y0) { t.Errorf("%s: y changed to %s", test, y) + continue } } } @@ -195,6 +200,68 @@ func eql(x, y Value) bool { return Compare(x, token.EQL, y) } +// ---------------------------------------------------------------------------- +// String tests + +var xxx = strings.Repeat("x", 68) + +var stringTests = []struct { + input, short, exact string +}{ + // Unknown + {"", "unknown", "unknown"}, + {"0x", "unknown", "unknown"}, + {"'", "unknown", "unknown"}, + {"1f0", "unknown", "unknown"}, + {"unknown", "unknown", "unknown"}, + + // Bool + {"true", "true", "true"}, + {"false", "false", "false"}, + + // String + {`""`, `""`, `""`}, + {`"foo"`, `"foo"`, `"foo"`}, + {`"` + xxx + `xx"`, `"` + xxx + `xx"`, `"` + xxx + `xx"`}, + {`"` + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + `xxx"`}, + {`"` + xxx + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + xxx + `xxx"`}, + + // Int + {"0", "0", "0"}, + {"-1", "-1", "-1"}, + {"12345", "12345", "12345"}, + {"-12345678901234567890", "-12345678901234567890", "-12345678901234567890"}, + {"12345678901234567890", "12345678901234567890", "12345678901234567890"}, + + // Float + {"0.", "0", "0"}, + {"-0.0", "0", "0"}, + {"10.0", "10", "10"}, + {"2.1", "2.1", "21/10"}, + {"-2.1", "-2.1", "-21/10"}, + {"1e9999", "1e+9999", "0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216"}, + {"2.71828182845904523536028747135266249775724709369995957496696763", "2.71828", "271828182845904523536028747135266249775724709369995957496696763/100000000000000000000000000000000000000000000000000000000000000"}, + + // Complex + {"0i", "(0 + 0i)", "(0 + 0i)"}, + {"-0i", "(0 + 0i)", "(0 + 0i)"}, + {"10i", "(0 + 10i)", "(0 + 10i)"}, + {"-10i", "(0 + -10i)", "(0 + -10i)"}, + {"1e9999i", "(0 + 1e+9999i)", "(0 + 0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216i)"}, +} + +func TestString(t *testing.T) { + for _, test := range stringTests { + x := val(test.input) + if got := x.String(); got != test.short { + t.Errorf("%s: got %q; want %q as short string", test.input, got, test.short) + } + if got := x.ExactString(); got != test.exact { + t.Errorf("%s: got %q; want %q as exact string", test.input, got, test.exact) + } + } +} + // ---------------------------------------------------------------------------- // Support functions @@ -212,6 +279,13 @@ func val(lit string) Value { return MakeBool(false) } + if i := strings.IndexByte(lit, '/'); i >= 0 { + // assume fraction + a := MakeFromLiteral(lit[:i], token.INT, 0) + b := MakeFromLiteral(lit[i+1:], token.INT, 0) + return BinaryOp(a, token.QUO, b) + } + tok := token.INT switch first, last := lit[0], lit[len(lit)-1]; { case first == '"' || first == '`': @@ -290,32 +364,29 @@ func doOp(x Value, op token.Token, y Value) (z Value) { // 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", + "0", + "1", + "-1", + "1.2", + "-0.991", + "2.718281828", + "3.14159265358979323e-10", + "1e100", + "1e1000", } 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) + x := val(test) + // We don't check the actual numerator and denominator because they + // are unlikely to be 100% correct due to floatVal rounding errors. + // Instead, we compute the fraction again and compare the rounded + // result. + q := BinaryOp(Num(x), token.QUO, Denom(x)) + got := q.String() + want := x.String() + if got != want { + t.Errorf("%s: got quotient %s, want %s", x, got, want) } } } diff --git a/src/go/internal/gccgoimporter/importer_test.go b/src/go/internal/gccgoimporter/importer_test.go index f3bcadbaf77051..c10fa484e39185 100644 --- a/src/go/internal/gccgoimporter/importer_test.go +++ b/src/go/internal/gccgoimporter/importer_test.go @@ -91,10 +91,10 @@ func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]Init var importerTests = [...]importerTest{ {pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"}, - {pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1/1 + -1/1i)"}, - {pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1/1 + 1/1i)"}, - {pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1/1 + -1/1i)"}, - {pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1/1 + 1/1i)"}, + {pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"}, + {pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1 + 1i)"}, + {pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1 + -1i)"}, + {pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1 + 1i)"}, // TODO: enable this entry once bug has been tracked down //{pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import", "math..import"}}, } diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index 76c34b4c4fb026..c2feed3813eb43 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -54,14 +54,14 @@ func TestValuesInfo(t *testing.T) { {`package a1; const _ = 0`, `0`, `untyped int`, `0`}, {`package a2; const _ = 'A'`, `'A'`, `untyped rune`, `65`}, {`package a3; const _ = 0.`, `0.`, `untyped float`, `0`}, - {`package a4; const _ = 0i`, `0i`, `untyped complex`, `0`}, + {`package a4; const _ = 0i`, `0i`, `untyped complex`, `(0 + 0i)`}, {`package a5; const _ = "foo"`, `"foo"`, `untyped string`, `"foo"`}, {`package b0; var _ = false`, `false`, `bool`, `false`}, {`package b1; var _ = 0`, `0`, `int`, `0`}, {`package b2; var _ = 'A'`, `'A'`, `rune`, `65`}, {`package b3; var _ = 0.`, `0.`, `float64`, `0`}, - {`package b4; var _ = 0i`, `0i`, `complex128`, `0`}, + {`package b4; var _ = 0i`, `0i`, `complex128`, `(0 + 0i)`}, {`package b5; var _ = "foo"`, `"foo"`, `string`, `"foo"`}, {`package c0a; var _ = bool(false)`, `false`, `bool`, `false`}, @@ -80,9 +80,9 @@ func TestValuesInfo(t *testing.T) { {`package c3b; var _ = float32(0.)`, `float32(0.)`, `float32`, `0`}, {`package c3c; type T float32; var _ = T(0.)`, `T(0.)`, `c3c.T`, `0`}, - {`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `0`}, - {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `0`}, - {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `0`}, + {`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `(0 + 0i)`}, + {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `(0 + 0i)`}, + {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `(0 + 0i)`}, {`package c5a; var _ = string("foo")`, `"foo"`, `string`, `"foo"`}, {`package c5b; var _ = string("foo")`, `string("foo")`, `string`, `"foo"`}, @@ -97,10 +97,10 @@ func TestValuesInfo(t *testing.T) { {`package e1; const _ = float32(-1e-200)`, `float32(-1e-200)`, `float32`, `0`}, {`package e2; const _ = float64( 1e-2000)`, `float64(1e-2000)`, `float64`, `0`}, {`package e3; const _ = float64(-1e-2000)`, `float64(-1e-2000)`, `float64`, `0`}, - {`package e4; const _ = complex64( 1e-200)`, `complex64(1e-200)`, `complex64`, `0`}, - {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `0`}, - {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `0`}, - {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `0`}, + {`package e4; const _ = complex64( 1e-200)`, `complex64(1e-200)`, `complex64`, `(0 + 0i)`}, + {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `(0 + 0i)`}, + {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `(0 + 0i)`}, + {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `(0 + 0i)`}, {`package f0 ; var _ float32 = 1e-200`, `1e-200`, `float32`, `0`}, {`package f1 ; var _ float32 = -1e-200`, `-1e-200`, `float32`, `0`}, @@ -108,12 +108,12 @@ func TestValuesInfo(t *testing.T) { {`package f3a; var _ float64 = -1e-2000`, `-1e-2000`, `float64`, `0`}, {`package f2b; var _ = 1e-2000`, `1e-2000`, `float64`, `0`}, {`package f3b; var _ = -1e-2000`, `-1e-2000`, `float64`, `0`}, - {`package f4 ; var _ complex64 = 1e-200 `, `1e-200`, `complex64`, `0`}, - {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `0`}, - {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, - {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, - {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, - {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, + {`package f4 ; var _ complex64 = 1e-200 `, `1e-200`, `complex64`, `(0 + 0i)`}, + {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `(0 + 0i)`}, + {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`}, } for _, test := range tests { @@ -143,7 +143,7 @@ func TestValuesInfo(t *testing.T) { } // check that value is correct - if got := tv.Value.String(); got != test.val { + if got := tv.Value.ExactString(); got != test.val { t.Errorf("package %s: got value %s; want %s", name, got, test.val) } } diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index c288024c5488e0..803264fb585de4 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -266,7 +266,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b // if both arguments are constants, the result is a constant if x.mode == constant_ && y.mode == constant_ { - x.val = constant.BinaryOp(x.val, token.ADD, constant.MakeImag(y.val)) + x.val = constant.BinaryOp(constant.ToFloat(x.val), token.ADD, constant.MakeImag(constant.ToFloat(y.val))) } else { x.mode = value } diff --git a/src/go/types/conversions.go b/src/go/types/conversions.go index 33e8930fbb630f..f98cc8d81a89f0 100644 --- a/src/go/types/conversions.go +++ b/src/go/types/conversions.go @@ -18,7 +18,7 @@ func (check *Checker) conversion(x *operand, T Type) { case constArg && isConstType(T): // constant conversion switch t := T.Underlying().(*Basic); { - case representableConst(x.val, check.conf, t.kind, &x.val): + case representableConst(x.val, check.conf, t, &x.val): ok = true case isInteger(x.typ) && isString(t): codepoint := int64(-1) diff --git a/src/go/types/eval_test.go b/src/go/types/eval_test.go index 7e0be43e723e4f..603211257dc572 100644 --- a/src/go/types/eval_test.go +++ b/src/go/types/eval_test.go @@ -48,7 +48,7 @@ func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, ex // compare values gotStr := "" if gotTv.Value != nil { - gotStr = gotTv.Value.String() + gotStr = gotTv.Value.ExactString() } if gotStr != valStr { t.Errorf("Eval(%q) got value %s, want %s", expr, gotStr, valStr) diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 387a32fc159d21..942d3fd5f73704 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -180,25 +180,27 @@ func roundFloat64(x constant.Value) constant.Value { } // representableConst reports whether x can be represented as -// value of the given basic type kind and for the configuration +// value of the given basic type and for the configuration // provided (only needed for int/uint sizes). // // If rounded != nil, *rounded is set to the rounded value of x for // representable floating-point values; it is left alone otherwise. // It is ok to provide the addressof the first argument for rounded. -func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *constant.Value) bool { - switch x.Kind() { - case constant.Unknown: - return true - - case constant.Bool: - return as == Bool || as == UntypedBool +func representableConst(x constant.Value, conf *Config, typ *Basic, rounded *constant.Value) bool { + if x.Kind() == constant.Unknown { + return true // avoid follow-up errors + } - case constant.Int: + switch { + case isInteger(typ): + x := constant.ToInt(x) + if x.Kind() != constant.Int { + return false + } if x, ok := constant.Int64Val(x); ok { - switch as { + switch typ.kind { case Int: - var s = uint(conf.sizeof(Typ[as])) * 8 + var s = uint(conf.sizeof(typ)) * 8 return int64(-1)<<(s-1) <= x && x <= int64(1)<<(s-1)-1 case Int8: const s = 8 @@ -209,10 +211,10 @@ func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *c case Int32: const s = 32 return -1<<(s-1) <= x && x <= 1<<(s-1)-1 - case Int64: + case Int64, UntypedInt: return true case Uint, Uintptr: - if s := uint(conf.sizeof(Typ[as])) * 8; s < 64 { + if s := uint(conf.sizeof(typ)) * 8; s < 64 { return 0 <= x && x <= int64(1)<= 0 && n <= int(s) case Uint64: return constant.Sign(x) >= 0 && n <= 64 - case Float32, Complex64: - if rounded == nil { - return fitsFloat32(x) - } - r := roundFloat32(x) - if r != nil { - *rounded = r - return true - } - case Float64, Complex128: - if rounded == nil { - return fitsFloat64(x) - } - r := roundFloat64(x) - if r != nil { - *rounded = r - return true - } - case UntypedInt, UntypedFloat, UntypedComplex: + case UntypedInt: return true } - case constant.Float: - switch as { - case Float32, Complex64: + case isFloat(typ): + x := constant.ToFloat(x) + if x.Kind() != constant.Float { + return false + } + switch typ.kind { + case Float32: if rounded == nil { return fitsFloat32(x) } @@ -273,7 +259,7 @@ func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *c *rounded = r return true } - case Float64, Complex128: + case Float64: if rounded == nil { return fitsFloat64(x) } @@ -282,12 +268,18 @@ func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *c *rounded = r return true } - case UntypedFloat, UntypedComplex: + case UntypedFloat: return true + default: + unreachable() } - case constant.Complex: - switch as { + case isComplex(typ): + x := constant.ToComplex(x) + if x.Kind() != constant.Complex { + return false + } + switch typ.kind { case Complex64: if rounded == nil { return fitsFloat32(constant.Real(x)) && fitsFloat32(constant.Imag(x)) @@ -310,13 +302,15 @@ func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *c } case UntypedComplex: return true + default: + unreachable() } - case constant.String: - return as == String || as == UntypedString + case isString(typ): + return x.Kind() == constant.String - default: - unreachable() + case isBoolean(typ): + return x.Kind() == constant.Bool } return false @@ -325,7 +319,7 @@ func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *c // representable checks that a constant operand is representable in the given basic type. func (check *Checker) representable(x *operand, typ *Basic) { assert(x.mode == constant_) - if !representableConst(x.val, check.conf, typ.kind, &x.val) { + if !representableConst(x.val, check.conf, typ, &x.val) { var msg string if isNumeric(x.typ) && isNumeric(typ) { // numeric conversion : error msg @@ -498,8 +492,6 @@ func (check *Checker) convertUntyped(x *operand, target Type) { return } // expression value may have been rounded - update if needed - // TODO(gri) A floating-point value may silently underflow to - // zero. If it was negative, the sign is lost. See issue 6898. check.updateExprVal(x.expr, x.val) } else { // Non-constant untyped values may appear as the @@ -621,9 +613,16 @@ func (check *Checker) comparison(x, y *operand, op token.Token) { func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { untypedx := isUntyped(x.typ) - // The lhs must be of integer type or be representable - // as an integer; otherwise the shift has no chance. - if !x.isInteger() { + var xval constant.Value + if x.mode == constant_ { + xval = constant.ToInt(x.val) + } + + if isInteger(x.typ) || untypedx && xval != nil && xval.Kind() == constant.Int { + // The lhs is of integer type or an untyped constant representable + // as an integer. Nothing to do. + } else { + // shift has no chance check.invalidOp(x.pos(), "shifted operand %s must be integer", x) x.mode = invalid return @@ -633,7 +632,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { // integer type or be an untyped constant that can be converted to // unsigned integer type." switch { - case isInteger(y.typ) && isUnsigned(y.typ): + case isUnsigned(y.typ): // nothing to do case isUntyped(y.typ): check.convertUntyped(y, Typ[UntypedInt]) @@ -650,14 +649,15 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { if x.mode == constant_ { if y.mode == constant_ { // rhs must be an integer value - if !y.isInteger() { + yval := constant.ToInt(y.val) + if yval.Kind() != constant.Int { check.invalidOp(y.pos(), "shift count %s must be unsigned integer", y) x.mode = invalid return } // rhs must be within reasonable bounds const stupidShift = 1023 - 1 + 52 // so we can express smallestFloat64 - s, ok := constant.Uint64Val(y.val) + s, ok := constant.Uint64Val(yval) if !ok || s > stupidShift { check.invalidOp(y.pos(), "stupid shift count %s", y) x.mode = invalid @@ -670,7 +670,8 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { if !isInteger(x.typ) { x.typ = Typ[UntypedInt] } - x.val = constant.Shift(x.val, op, uint(s)) + // x is a constant so xval != nil and it must be of Int kind. + x.val = constant.Shift(xval, op, uint(s)) // Typed constants must be representable in // their type after each constant operation. if isTyped(x.typ) { @@ -802,12 +803,16 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o } if x.mode == constant_ && y.mode == constant_ { + xval := x.val + yval := y.val typ := x.typ.Underlying().(*Basic) // force integer division of integer operands if op == token.QUO && isInteger(typ) { + xval = constant.ToInt(xval) + yval = constant.ToInt(yval) op = token.QUO_ASSIGN } - x.val = constant.BinaryOp(x.val, op, y.val) + x.val = constant.BinaryOp(xval, op, yval) // Typed constants must be representable in // their type after each constant operation. if isTyped(typ) { @@ -851,7 +856,7 @@ func (check *Checker) index(index ast.Expr, max int64) (i int64, valid bool) { check.invalidArg(x.pos(), "index %s must not be negative", &x) return } - i, valid = constant.Int64Val(x.val) + i, valid = constant.Int64Val(constant.ToInt(x.val)) if !valid || max >= 0 && i >= max { check.errorf(x.pos(), "index %s is out of bounds", &x) return i, false diff --git a/src/go/types/operand.go b/src/go/types/operand.go index 09eac8354dce1f..b2f16b64d80424 100644 --- a/src/go/types/operand.go +++ b/src/go/types/operand.go @@ -166,13 +166,6 @@ func (x *operand) String() string { // setConst sets x to the untyped constant for literal lit. func (x *operand) setConst(tok token.Token, lit string) { - val := constant.MakeFromLiteral(lit, tok, 0) - if val == nil { - // TODO(gri) Should we make it an unknown constant instead? - x.mode = invalid - return - } - var kind BasicKind switch tok { case token.INT: @@ -185,11 +178,13 @@ func (x *operand) setConst(tok token.Token, lit string) { kind = UntypedRune case token.STRING: kind = UntypedString + default: + unreachable() } x.mode = constant_ x.typ = Typ[kind] - x.val = val + x.val = constant.MakeFromLiteral(lit, tok, 0) } // isNil reports whether x is the nil value. @@ -229,7 +224,7 @@ func (x *operand) assignableTo(conf *Config, T Type, reason *string) bool { return true } if x.mode == constant_ { - return representableConst(x.val, conf, t.kind, nil) + return representableConst(x.val, conf, t, nil) } // The result of a comparison is an untyped boolean, // but may not be a constant. @@ -276,11 +271,3 @@ func (x *operand) assignableTo(conf *Config, T Type, reason *string) bool { return false } - -// isInteger reports whether x is value of integer type -// or an untyped constant representable as an integer. -func (x *operand) isInteger() bool { - return x.mode == invalid || - isInteger(x.typ) || - isUntyped(x.typ) && x.mode == constant_ && representableConst(x.val, nil, UntypedInt, nil) // no *Config required for UntypedInt -} diff --git a/src/go/types/stdlib_test.go b/src/go/types/stdlib_test.go index 6ff90042434338..055bd96911ed81 100644 --- a/src/go/types/stdlib_test.go +++ b/src/go/types/stdlib_test.go @@ -144,14 +144,9 @@ func TestStdFixed(t *testing.T) { testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"), "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore - "bug459.go", // possibly incorrect test - see issue 6703 (pending spec clarification) - "issue3924.go", // possibly incorrect test - see issue 6671 (pending spec clarification) - "issue6889.go", // gc-specific test - "issue7746.go", // large constants - consumes too much memory - "issue11326.go", // large constants - "issue11326b.go", // large constants - "issue11362.go", // canonical import path check - "issue13471.go", // large constants - remove once issue 11327 is fixed + "issue6889.go", // gc-specific test + "issue7746.go", // large constants - consumes too much memory + "issue11362.go", // canonical import path check ) } diff --git a/src/go/types/testdata/const0.src b/src/go/types/testdata/const0.src index c4419ab6fe852e..716a5907ce3290 100644 --- a/src/go/types/testdata/const0.src +++ b/src/go/types/testdata/const0.src @@ -280,3 +280,16 @@ func _() { var y = iota _ = y } + +// constant arithmetic precision and rounding must lead to expected (integer) results +var _ = []int64{ + 0.0005 * 1e9, + 0.001 * 1e9, + 0.005 * 1e9, + 0.01 * 1e9, + 0.05 * 1e9, + 0.1 * 1e9, + 0.5 * 1e9, + 1 * 1e9, + 5 * 1e9, +} diff --git a/src/go/types/testdata/decls0.src b/src/go/types/testdata/decls0.src index 21baafe2794416..d4df386b1322b4 100644 --- a/src/go/types/testdata/decls0.src +++ b/src/go/types/testdata/decls0.src @@ -50,7 +50,10 @@ func _() { var init int; _ = init } // invalid array types type ( iA0 [... /* ERROR "invalid use of '...'" */ ]byte - iA1 [1 /* ERROR "invalid array length" */ <<100]int + // The error message below could be better. At the moment + // we believe an integer that is too large is not an integer. + // But at least we get an error. + iA1 [1 /* ERROR "must be integer" */ <<100]int iA2 [- /* ERROR "invalid array length" */ 1]complex128 iA3 ["foo" /* ERROR "must be integer" */ ]string iA4 [float64 /* ERROR "must be integer" */ (0)]int diff --git a/src/go/types/testdata/errors.src b/src/go/types/testdata/errors.src index 45bd45a13a4d50..29fcd8fe1da6de 100644 --- a/src/go/types/testdata/errors.src +++ b/src/go/types/testdata/errors.src @@ -21,8 +21,8 @@ func f(x int, m map[string]int) { const c2 float32 = 0.5 0 /* ERROR "0 \(untyped int constant\) is not used" */ c1 /* ERROR "c1 \(untyped int constant 991\) is not used" */ - c2 /* ERROR "c2 \(constant 1/2 of type float32\) is not used" */ - c1 /* ERROR "c1 \+ c2 \(constant 1983/2 of type float32\) is not used" */ + c2 + c2 /* ERROR "c2 \(constant 0.5 of type float32\) is not used" */ + c1 /* ERROR "c1 \+ c2 \(constant 991.5 of type float32\) is not used" */ + c2 // variables x /* ERROR "x \(variable of type int\) is not used" */ diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index c744eeaa0c785b..931b9247124f36 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -373,16 +373,19 @@ func (check *Checker) arrayLength(e ast.Expr) int64 { } return 0 } - if !x.isInteger() { - check.errorf(x.pos(), "array length %s must be integer", &x) - return 0 - } - n, ok := constant.Int64Val(x.val) - if !ok || n < 0 { - check.errorf(x.pos(), "invalid array length %s", &x) - return 0 + if isUntyped(x.typ) || isInteger(x.typ) { + if val := constant.ToInt(x.val); val.Kind() == constant.Int { + if representableConst(val, check.conf, Typ[Int], nil) { + if n, ok := constant.Int64Val(val); ok && n >= 0 { + return n + } + check.errorf(x.pos(), "invalid array length %s", &x) + return 0 + } + } } - return n + check.errorf(x.pos(), "array length %s must be integer", &x) + return 0 } func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, variadic bool) {