1
0
mirror of https://github.com/golang/go synced 2024-09-29 22:34:33 -06:00

[dev.typeparams] go/constant: choose internal float representations more consistently

go/constant represents a Float constant either as a rational number
(if numerator and denominator are small enough), or, as a "catch-all",
as a arbitrary-precision floating-point number.

This CL cleans up some of these transitions by factoring out more
of the decision logic and documents the rationale between the state
transitions better.

This CL also simplifies some unrelated code that was overly complex.

Updates #43908.

Change-Id: Iccdd2d6b7fb7ed13d68ed5e6d992d1bc56a065bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/286572
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Robert Griesemer 2021-01-25 15:39:37 -08:00
parent 34704e374f
commit d39685e5e9

View File

@ -72,6 +72,17 @@ const prec = 512
// too large (incl. infinity), that could be recorded in unknownVal.
// See also #20583 and #42695 for use cases.
// Representation of values:
//
// Values of Int and Float Kind have two different representations each: int64Val
// and intVal, and ratVal and floatVal. When possible, the "smaller", respectively
// more precise (for Floats) representation is chosen. However, once a Float value
// is represented as a floatVal, any subsequent results remain floatVals (unless
// explicitly converted); i.e., no attempt is made to convert a floatVal back into
// a ratVal. The reasoning is that all representations but floatVal are mathematically
// exact, but once that precision is lost (by moving to floatVal), moving back to
// a different representation implies a precision that's not actually there.
type (
unknownVal struct{}
boolVal bool
@ -263,13 +274,7 @@ 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 rtof(x ratVal) floatVal { return floatVal{newFloat().SetRat(x.val)} }
func vtoc(x Value) complexVal { return complexVal{x, int64Val(0)} }
func makeInt(x *big.Int) Value {
@ -279,21 +284,15 @@ func makeInt(x *big.Int) Value {
return intVal{x}
}
// 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 {
if smallInt(a) && smallInt(b) {
// ok to remain fraction
return ratVal{x}
}
// components too large => switch to float
fa := newFloat().SetInt(a)
fb := newFloat().SetInt(b)
return floatVal{fa.Quo(fa, fb)}
return floatVal{newFloat().SetRat(x)}
}
var floatVal0 = floatVal{newFloat()}
@ -306,6 +305,9 @@ func makeFloat(x *big.Float) Value {
if x.IsInf() {
return unknownVal{}
}
// No attempt is made to "go back" to ratVal, even if possible,
// to avoid providing the illusion of a mathematically exact
// representation.
return floatVal{x}
}
@ -318,7 +320,7 @@ func makeComplex(re, im Value) Value {
func makeFloatFromLiteral(lit string) Value {
if f, ok := newFloat().SetString(lit); ok {
if smallRat(f) {
if smallFloat(f) {
// ok to use rationals
if f.Sign() == 0 {
// Issue 20228: If the float underflowed to zero, parse just "0".
@ -337,15 +339,35 @@ func makeFloatFromLiteral(lit string) Value {
return nil
}
// smallRat reports whether x would lead to "reasonably"-sized fraction
// Permit fractions with component sizes up to maxExp
// before switching to using floating-point numbers.
const maxExp = 4 << 10
// smallInt reports whether x would lead to "reasonably"-sized fraction
// if converted to a *big.Rat.
func smallRat(x *big.Float) bool {
if !x.IsInf() {
e := x.MantExp(nil)
func smallInt(x *big.Int) bool {
return x.BitLen() < maxExp
}
// smallFloat64 reports whether x would lead to "reasonably"-sized fraction
// if converted to a *big.Rat.
func smallFloat64(x float64) bool {
if math.IsInf(x, 0) {
return false
}
_, e := math.Frexp(x)
return -maxExp < e && e < maxExp
}
// smallFloat reports whether x would lead to "reasonably"-sized fraction
// if converted to a *big.Rat.
func smallFloat(x *big.Float) bool {
if x.IsInf() {
return false
}
e := x.MantExp(nil)
return -maxExp < e && e < maxExp
}
// ----------------------------------------------------------------------------
// Factories
@ -377,8 +399,11 @@ func MakeFloat64(x float64) Value {
if math.IsInf(x, 0) || math.IsNaN(x) {
return unknownVal{}
}
if smallFloat64(x) {
return ratVal{newRat().SetFloat64(x + 0)} // convert -0 to 0
}
return floatVal{newFloat().SetFloat64(x + 0)}
}
// MakeFromLiteral returns the corresponding integer, floating-point,
// imaginary, character, or string value for a Go literal string. The
@ -733,7 +758,7 @@ func Num(x Value) Value {
case ratVal:
return makeInt(x.val.Num())
case floatVal:
if smallRat(x.val) {
if smallFloat(x.val) {
r, _ := x.val.Rat(nil)
return makeInt(r.Num())
}
@ -755,7 +780,7 @@ func Denom(x Value) Value {
case ratVal:
return makeInt(x.val.Denom())
case floatVal:
if smallRat(x.val) {
if smallFloat(x.val) {
r, _ := x.val.Rat(nil)
return makeInt(r.Denom())
}
@ -828,7 +853,7 @@ func ToInt(x Value) Value {
// avoid creation of huge integers
// (Existing tests require permitting exponents of at least 1024;
// allow any value that would also be permissible as a fraction.)
if smallRat(x.val) {
if smallFloat(x.val) {
i := newInt()
if _, acc := x.val.Int(i); acc == big.Exact {
return makeInt(i)
@ -871,14 +896,16 @@ func ToInt(x Value) Value {
func ToFloat(x Value) Value {
switch x := x.(type) {
case int64Val:
return i64tor(x)
return i64tor(x) // x is always a small int
case intVal:
if smallInt(x.val) {
return itor(x)
}
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
if Sign(x.im) == 0 {
return ToFloat(x.re)
}
}
@ -889,13 +916,7 @@ func ToFloat(x Value) Value {
// 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:
case int64Val, intVal, ratVal, floatVal:
return vtoc(x)
case complexVal:
return x