Write exact results past the range of a double (closes #16)
check / check (push) Successful in 1m43s

An exact result outside the normal range of a double, such as 2^1200,
10^400 or 2^-1200, was refused because format wrote every result
through float64. Such a result is now written from its exact value,
rounded to 17 significant digits with trailing zeros dropped, through a
big.Float precise enough to round as the exact value would. Results
inside the range are written as before, and a power computed in float64
must still be a normal double. The tests check the written text of these
results, the refusals just past the 4096-bit limit, and the time taken
to write the largest and smallest numbers under it.

Model: opus-5-5
This commit is contained in:
2026-09-29 06:18:19 +00:00
parent f53b666119
commit c4e4041cd9
5 changed files with 90 additions and 35 deletions
+27 -13
View File
@@ -48,6 +48,16 @@ const (
plainLower = 1e-6
)
// A result past the normal range of a double is written to
// significantDigits significant digits, the most the shortest form of a
// double takes. It is rounded to them from a float of floatPrecision
// bits, the bits a numerator or denominator can hold and 64 more for the
// digits, so that the float rounds as the exact result would.
const (
significantDigits = 17
floatPrecision = bitLimit + 64
)
// The precedence of the binary operators: the higher, the tighter the
// operator binds.
const (
@@ -82,8 +92,7 @@ var (
)
// Evaluate computes an arithmetic expression and returns its result as
// text: whole numbers without a decimal point, fractions in the
// shortest form that reads back as the same float64.
// text, written as format describes.
func Evaluate(input string) (string, error) {
s := strings.TrimSpace(input)
if len(s) > MaxInputLength {
@@ -106,7 +115,7 @@ func Evaluate(input string) (string, error) {
return "", ErrNotArithmetic
}
return format(v)
return format(v), nil
}
// tokenize splits an expression into operators, parentheses and
@@ -459,26 +468,31 @@ func normal(f float64) bool {
}
// format writes a result for a person to read. A whole number of
// ordinary size is written exactly, digit for digit; anything else goes
// through float64, whose shortest round-trip form is free of the noise
// (0.30000000000000004) that printing a binary fraction to a fixed
// precision produces. A result that is not zero must therefore be a
// normal double: 2^-1074 would be written 5e-324.
func format(v constant.Value) (string, error) {
// ordinary size is written exactly, digit for digit. Any other result in
// the normal range of a double goes through float64, whose shortest
// round-trip form is free of the noise (0.30000000000000004) that
// printing a binary fraction to a fixed precision produces. Past that
// range a double keeps fewer digits, or none (2^-1074 would be written
// 5e-324, and 2^1024 is infinite), so such a result is written from its
// exact value, to significantDigits.
func format(v constant.Value) string {
f, _ := constant.Float64Val(v)
if constant.Sign(v) != 0 && !normal(f) {
return "", ErrOutOfRange
// Every number here is exact: see exact.
r, _ := constant.Val(v).(*big.Rat)
return new(big.Float).SetPrec(floatPrecision).SetRat(r).Text('g', significantDigits)
}
abs := math.Abs(f)
if i := constant.ToInt(v); i.Kind() == constant.Int && abs < plainUpper {
return i.ExactString(), nil
return i.ExactString()
}
if abs >= plainUpper || abs < plainLower {
return strconv.FormatFloat(f, 'g', -1, 64), nil
return strconv.FormatFloat(f, 'g', -1, 64)
}
return strconv.FormatFloat(f, 'f', -1, 64), nil
return strconv.FormatFloat(f, 'f', -1, 64)
}