Skip to content

Commit d39685e

Browse files
committed
[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 <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]>
1 parent 34704e3 commit d39685e

File tree

1 file changed

+59
-38
lines changed

1 file changed

+59
-38
lines changed

src/go/constant/value.go

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ const prec = 512
7272
// too large (incl. infinity), that could be recorded in unknownVal.
7373
// See also #20583 and #42695 for use cases.
7474

75+
// Representation of values:
76+
//
77+
// Values of Int and Float Kind have two different representations each: int64Val
78+
// and intVal, and ratVal and floatVal. When possible, the "smaller", respectively
79+
// more precise (for Floats) representation is chosen. However, once a Float value
80+
// is represented as a floatVal, any subsequent results remain floatVals (unless
81+
// explicitly converted); i.e., no attempt is made to convert a floatVal back into
82+
// a ratVal. The reasoning is that all representations but floatVal are mathematically
83+
// exact, but once that precision is lost (by moving to floatVal), moving back to
84+
// a different representation implies a precision that's not actually there.
85+
7586
type (
7687
unknownVal struct{}
7788
boolVal bool
@@ -263,14 +274,8 @@ func i64tor(x int64Val) ratVal { return ratVal{newRat().SetInt64(int64(x))} }
263274
func i64tof(x int64Val) floatVal { return floatVal{newFloat().SetInt64(int64(x))} }
264275
func itor(x intVal) ratVal { return ratVal{newRat().SetInt(x.val)} }
265276
func itof(x intVal) floatVal { return floatVal{newFloat().SetInt(x.val)} }
266-
267-
func rtof(x ratVal) floatVal {
268-
a := newFloat().SetInt(x.val.Num())
269-
b := newFloat().SetInt(x.val.Denom())
270-
return floatVal{a.Quo(a, b)}
271-
}
272-
273-
func vtoc(x Value) complexVal { return complexVal{x, int64Val(0)} }
277+
func rtof(x ratVal) floatVal { return floatVal{newFloat().SetRat(x.val)} }
278+
func vtoc(x Value) complexVal { return complexVal{x, int64Val(0)} }
274279

275280
func makeInt(x *big.Int) Value {
276281
if x.IsInt64() {
@@ -279,21 +284,15 @@ func makeInt(x *big.Int) Value {
279284
return intVal{x}
280285
}
281286

282-
// Permit fractions with component sizes up to maxExp
283-
// before switching to using floating-point numbers.
284-
const maxExp = 4 << 10
285-
286287
func makeRat(x *big.Rat) Value {
287288
a := x.Num()
288289
b := x.Denom()
289-
if a.BitLen() < maxExp && b.BitLen() < maxExp {
290+
if smallInt(a) && smallInt(b) {
290291
// ok to remain fraction
291292
return ratVal{x}
292293
}
293294
// components too large => switch to float
294-
fa := newFloat().SetInt(a)
295-
fb := newFloat().SetInt(b)
296-
return floatVal{fa.Quo(fa, fb)}
295+
return floatVal{newFloat().SetRat(x)}
297296
}
298297

299298
var floatVal0 = floatVal{newFloat()}
@@ -306,6 +305,9 @@ func makeFloat(x *big.Float) Value {
306305
if x.IsInf() {
307306
return unknownVal{}
308307
}
308+
// No attempt is made to "go back" to ratVal, even if possible,
309+
// to avoid providing the illusion of a mathematically exact
310+
// representation.
309311
return floatVal{x}
310312
}
311313

@@ -318,7 +320,7 @@ func makeComplex(re, im Value) Value {
318320

319321
func makeFloatFromLiteral(lit string) Value {
320322
if f, ok := newFloat().SetString(lit); ok {
321-
if smallRat(f) {
323+
if smallFloat(f) {
322324
// ok to use rationals
323325
if f.Sign() == 0 {
324326
// Issue 20228: If the float underflowed to zero, parse just "0".
@@ -337,14 +339,34 @@ func makeFloatFromLiteral(lit string) Value {
337339
return nil
338340
}
339341

340-
// smallRat reports whether x would lead to "reasonably"-sized fraction
342+
// Permit fractions with component sizes up to maxExp
343+
// before switching to using floating-point numbers.
344+
const maxExp = 4 << 10
345+
346+
// smallInt reports whether x would lead to "reasonably"-sized fraction
347+
// if converted to a *big.Rat.
348+
func smallInt(x *big.Int) bool {
349+
return x.BitLen() < maxExp
350+
}
351+
352+
// smallFloat64 reports whether x would lead to "reasonably"-sized fraction
341353
// if converted to a *big.Rat.
342-
func smallRat(x *big.Float) bool {
343-
if !x.IsInf() {
344-
e := x.MantExp(nil)
345-
return -maxExp < e && e < maxExp
354+
func smallFloat64(x float64) bool {
355+
if math.IsInf(x, 0) {
356+
return false
357+
}
358+
_, e := math.Frexp(x)
359+
return -maxExp < e && e < maxExp
360+
}
361+
362+
// smallFloat reports whether x would lead to "reasonably"-sized fraction
363+
// if converted to a *big.Rat.
364+
func smallFloat(x *big.Float) bool {
365+
if x.IsInf() {
366+
return false
346367
}
347-
return false
368+
e := x.MantExp(nil)
369+
return -maxExp < e && e < maxExp
348370
}
349371

350372
// ----------------------------------------------------------------------------
@@ -377,7 +399,10 @@ func MakeFloat64(x float64) Value {
377399
if math.IsInf(x, 0) || math.IsNaN(x) {
378400
return unknownVal{}
379401
}
380-
return ratVal{newRat().SetFloat64(x + 0)} // convert -0 to 0
402+
if smallFloat64(x) {
403+
return ratVal{newRat().SetFloat64(x + 0)} // convert -0 to 0
404+
}
405+
return floatVal{newFloat().SetFloat64(x + 0)}
381406
}
382407

383408
// MakeFromLiteral returns the corresponding integer, floating-point,
@@ -733,7 +758,7 @@ func Num(x Value) Value {
733758
case ratVal:
734759
return makeInt(x.val.Num())
735760
case floatVal:
736-
if smallRat(x.val) {
761+
if smallFloat(x.val) {
737762
r, _ := x.val.Rat(nil)
738763
return makeInt(r.Num())
739764
}
@@ -755,7 +780,7 @@ func Denom(x Value) Value {
755780
case ratVal:
756781
return makeInt(x.val.Denom())
757782
case floatVal:
758-
if smallRat(x.val) {
783+
if smallFloat(x.val) {
759784
r, _ := x.val.Rat(nil)
760785
return makeInt(r.Denom())
761786
}
@@ -828,7 +853,7 @@ func ToInt(x Value) Value {
828853
// avoid creation of huge integers
829854
// (Existing tests require permitting exponents of at least 1024;
830855
// allow any value that would also be permissible as a fraction.)
831-
if smallRat(x.val) {
856+
if smallFloat(x.val) {
832857
i := newInt()
833858
if _, acc := x.val.Int(i); acc == big.Exact {
834859
return makeInt(i)
@@ -871,14 +896,16 @@ func ToInt(x Value) Value {
871896
func ToFloat(x Value) Value {
872897
switch x := x.(type) {
873898
case int64Val:
874-
return i64tor(x)
899+
return i64tor(x) // x is always a small int
875900
case intVal:
876-
return itor(x)
901+
if smallInt(x.val) {
902+
return itor(x)
903+
}
904+
return itof(x)
877905
case ratVal, floatVal:
878906
return x
879907
case complexVal:
880-
if im := ToInt(x.im); im.Kind() == Int && Sign(im) == 0 {
881-
// imaginary component is 0
908+
if Sign(x.im) == 0 {
882909
return ToFloat(x.re)
883910
}
884911
}
@@ -889,13 +916,7 @@ func ToFloat(x Value) Value {
889916
// Otherwise it returns an Unknown.
890917
func ToComplex(x Value) Value {
891918
switch x := x.(type) {
892-
case int64Val:
893-
return vtoc(i64tof(x))
894-
case intVal:
895-
return vtoc(itof(x))
896-
case ratVal:
897-
return vtoc(x)
898-
case floatVal:
919+
case int64Val, intVal, ratVal, floatVal:
899920
return vtoc(x)
900921
case complexVal:
901922
return x

0 commit comments

Comments
 (0)