Answer exact results past the range of a double, such as 2^1200 (closes #16)
check / check (push) Successful in 1m18s

`2^1200` was refused although the calculator computed it exactly: writing a result went through a float64 and refused anything outside a double's range. An exact result under the 4096-bit limit is now written from its exact value, rounded to 17 significant digits past that range, and a fractional power of such a number is computed by first bringing its base into range with square roots taken from the exact value. Results computed in float64 must still be normal doubles; past the 4096-bit limit is still refused.

Disclosures: `1e-310` is answered again, reversing a call made in PR 11; a fractional power carries `math.Pow`'s rounding, which for a large base can reach the last digits shown.

Model: opus-5-5
This commit was merged in pull request #18.
This commit is contained in:
2026-09-29 09:56:54 +02:00
parent 397fc95149
commit 0b9121a806
5 changed files with 140 additions and 44 deletions
+71 -16
View File
@@ -52,6 +52,13 @@ func TestEvaluate(t *testing.T) {
"1234567.5": "1234567.5",
"-1 / 4": "-0.25",
"1e300 * 1e8": "1e+308",
// Past the normal range of a double, written from the exact value
// to 17 significant digits, trailing zeros dropped.
"1e400": "1e+400",
"1e300 * 1e300": "1e+600",
"1 / 2e-400": "5e+399",
"-1e-310": "-1e-310",
"1 / 3e400": "3.3333333333333333e-401",
})
}
@@ -82,6 +89,7 @@ func TestEvaluatePowers(t *testing.T) {
"0.1^2": "0.01",
"2^100 - 2^100 + 1": "1",
"2^64": "18446744073709551616",
"2^100": "1.2676506002282294e+30",
"0^0": "1",
"0^3": "0",
"1.5^2": "2.25",
@@ -99,6 +107,44 @@ func TestEvaluatePowers(t *testing.T) {
"2^-1400 * 2^1365 * 2^35": "1",
"0.3^900 * 10^470": "0.25652473503365386",
"2^1500 / 2^1000": "3.273390607896142e+150",
"(2^1200)/(2^1199)": "2",
"2^1200 % 7": "1",
// Results past the range of a double, written to 17 significant
// digits, up to the largest power of 2 under the 4096-bit limit.
"2^1200": "1.7218479456385751e+361",
"2**1200": "1.7218479456385751e+361",
"2^1024": "1.7976931348623159e+308",
"10^400": "1e+400",
"2^-1074": "4.9406564584124654e-324",
"2^-1200": "5.8077137562175032e-362",
"2^-1400": "3.6141491434385841e-422",
"0.5^1100": "7.3621518290228627e-332",
"1.5^2000": "1.5223626185737825e+352",
"(1/3)^-2000": "1.7478712517226516e+954",
"2^1200 - 2^1199": "8.6092397281928753e+360",
"-2^1201": "-3.4436958912771501e+361",
"(-2)^1201": "-3.4436958912771501e+361",
"2^2000 * 2^2000": "1.3182040934309431e+1204",
"10^1232": "1e+1232",
"2^4094": "2.6109722035328813e+1232",
// A power computed in float64 carries its rounding into the exact
// arithmetic after it, past the range of a double as within it.
"2^0.5 * 1e400": "1.4142135623730951e+400",
// A fractional power of an exact number outside the range of a
// double, taken from its exact value, up to the edges of that
// range.
"(2^1200)^0.5": "4.149515568880993e+180",
"(2^1024)^0.5": "1.3407807929942597e+154",
"(2^-1200)^0.5": "2.409919865102884e-181",
"1e400^0.5": "1e+200",
"1e-400^0.5": "1e-200",
"1e-310^0.5": "1e-155",
"(2^1200)^-0.5": "2.409919865102884e-181",
"(2^1200)^0.5 / 2^600": "1",
"1e400^-0.001": "0.39810717055349726",
"1e-400^0.001": "0.39810717055349726",
"(2^2047)^0.5": "1.2711610061536464e+308",
"(2^-2044)^0.5": "2.2250738585072014e-308",
})
}
@@ -205,31 +251,31 @@ func TestEvaluateRefuses(t *testing.T) {
}
// TestEvaluateOutOfRange: a number is held exactly, or computed in
// float64 as a normal double, and a result is written as a normal
// double. Anything else is refused.
// float64 as a normal double. Anything else is refused.
func TestEvaluateOutOfRange(t *testing.T) {
t.Parallel()
expectErrors(t, map[string]error{
// Results that are not normal doubles: 2^-1074 would be written
// 5e-324.
"1e400": calc.ErrOutOfRange,
"1e300 * 1e300": calc.ErrOutOfRange,
// Just past the 4096-bit limit, which 2^4094 and 10^1232 are
// under, and far past it.
"2^4095": calc.ErrOutOfRange,
"-2^4095": calc.ErrOutOfRange,
"2^-4095": calc.ErrOutOfRange,
"10^1233": calc.ErrOutOfRange,
"2^4094 * 2": calc.ErrOutOfRange,
"1e999999999 * 1e999999999": calc.ErrOutOfRange,
"1 / 1e-400": calc.ErrOutOfRange,
"2^1024": calc.ErrOutOfRange,
"2^5000": calc.ErrOutOfRange,
"(-2)^5001": calc.ErrOutOfRange,
"0.5^-5000": calc.ErrOutOfRange,
"2^-1074": calc.ErrOutOfRange,
"2^-1400": calc.ErrOutOfRange,
"-1e-310": calc.ErrOutOfRange,
// Powers computed in float64 whose base or result is not a
// normal double, and so has lost digits, or all of them.
// Powers computed in float64 whose result is not a normal double,
// and so has lost digits, or all of them, whatever the size of
// the base.
"2^-1073.5 * 2^1073": calc.ErrOutOfRange,
"1e400^-0.001": calc.ErrOutOfRange,
"1e-400^0.001": calc.ErrOutOfRange,
"1e-310^0.5": calc.ErrOutOfRange,
"2^1500.5": calc.ErrOutOfRange,
"(2^1200)^0.9": calc.ErrOutOfRange,
"1e-400^0.9": calc.ErrOutOfRange,
"(2^2048)^0.5": calc.ErrOutOfRange,
"(2^-2046)^0.5": calc.ErrOutOfRange,
"(0.5^1100)^4 / (0.5^1100)^4": calc.ErrOutOfRange,
"(1/3)^1e400": calc.ErrOutOfRange,
// go/constant holds numbers of this size rounded. A sum of them
@@ -309,6 +355,15 @@ func TestEvaluateBoundsWork(t *testing.T) {
// the limit, and a literal whose exponent is too large to read.
{in: "(3^2583/5^1760)^4096", err: calc.ErrOutOfRange},
{in: "1e99999999999999999999", err: calc.ErrOutOfRange},
// Results just below the limit, written from their exact value.
{in: "2^4094", want: "2.6109722035328813e+1232"},
{in: "3^2583", want: "2.5363018640659988e+1232"},
{in: "2^-4094", want: "3.8299909843808741e-1233"},
{in: "-1/3^2583", want: "-3.9427483540814775e-1233"},
// Fractional powers of numbers just below the limit, whose bases
// take the most square roots to bring into the range of a double.
{in: "(2^-4094)^0.125", want: "8.869511863657883e-155"},
{in: "(1/3^2583)^0.5", err: calc.ErrOutOfRange},
}
for _, c := range cases {