Write exact results past the range of a double (closes #16) #18
@@ -438,8 +438,11 @@ container.
|
|||||||
`4.9406564584124654e-324`. Refused as too large or too small: any
|
`4.9406564584124654e-324`. Refused as too large or too small: any
|
||||||
number whose numerator or denominator reaches 4096 bits, wherever it
|
number whose numerator or denominator reaches 4096 bits, wherever it
|
||||||
appears, as `go/constant` rounds a fraction that grows that large
|
appears, as `go/constant` rounds a fraction that grows that large
|
||||||
(`2^4095`, `1e-1300 + 1`); and a power computed as a double whose base
|
(`2^4095`, `1e-1300 + 1`); and a power computed as a double whose
|
||||||
or result is outside the normal range of a double (`1e-400^0.5`).
|
result is outside the normal range of a double (`2^1500.5`). The base
|
||||||
|
of such a power may be outside that range: square roots taken from its
|
||||||
|
exact value bring it inside first, so `(2^1200)^0.5` is
|
||||||
|
`4.149515568880993e+180` and `1e-400^0.5` is `1e-200`.
|
||||||
- **Failure is an exit.** If the chat client exits or the connection to
|
- **Failure is an exit.** If the chat client exits or the connection to
|
||||||
it drops, the bot exits with an error and the container's restart
|
it drops, the bot exits with an error and the container's restart
|
||||||
policy starts both again. `SIGTERM` stops the bot, which stops the
|
policy starts both again. `SIGTERM` stops the bot, which stops the
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,8 @@ with no deprecation warning.
|
|||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-09-29 Exact results past the range of a double, such as `2^1200`,
|
- 2026-09-29 Exact results past the range of a double, such as `2^1200`,
|
||||||
are written to 17 significant digits instead of being refused
|
are written to 17 significant digits instead of being refused, and a
|
||||||
|
fractional power of such a number, such as `(2^1200)^0.5`, is answered
|
||||||
- 2026-09-29 `POST`, `GET` and `DELETE` on a chat's webhooks, under
|
- 2026-09-29 `POST`, `GET` and `DELETE` on a chat's webhooks, under
|
||||||
`/api/v1/chats/{id}/webhooks`, kept in `$DATA_DIR/webhooks.json`
|
`/api/v1/chats/{id}/webhooks`, kept in `$DATA_DIR/webhooks.json`
|
||||||
- 2026-09-29 `GET` and `POST /api/v1/chats/{id}/messages`: a chat's
|
- 2026-09-29 `GET` and `POST /api/v1/chats/{id}/messages`: a chat's
|
||||||
|
|||||||
+21
-5
@@ -52,7 +52,9 @@ const (
|
|||||||
// significantDigits significant digits, the most the shortest form of a
|
// significantDigits significant digits, the most the shortest form of a
|
||||||
// double takes. It is rounded to them from a float of floatPrecision
|
// 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
|
// 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.
|
// digits, so that the float rounds as the exact result would. The square
|
||||||
|
// roots of a power's base past that range are taken in such a float too:
|
||||||
|
// see nonNegativePower.
|
||||||
const (
|
const (
|
||||||
significantDigits = 17
|
significantDigits = 17
|
||||||
floatPrecision = bitLimit + 64
|
floatPrecision = bitLimit + 64
|
||||||
@@ -404,11 +406,25 @@ func nonNegativePower(x, y, n constant.Value) (constant.Value, error) {
|
|||||||
|
|
||||||
xf, _ := constant.Float64Val(x)
|
xf, _ := constant.Float64Val(x)
|
||||||
yf, _ := constant.Float64Val(y)
|
yf, _ := constant.Float64Val(y)
|
||||||
f := math.Pow(xf, yf)
|
|
||||||
|
|
||||||
// Neither x nor x^y is zero. If either is not a normal double, it
|
// x^y is (√x)^(2y). An x outside the normal range of a double, such
|
||||||
// has lost digits, or all of them.
|
// as 2^1200, would lose digits as a double, or all of them, so square
|
||||||
if !normal(xf) || !normal(f) {
|
// roots taken from its exact value bring it into that range first. As
|
||||||
|
// x is between 2^-4096 and 2^4096 (see exact), three at most are
|
||||||
|
// needed.
|
||||||
|
r, _ := constant.Val(x).(*big.Rat)
|
||||||
|
root := new(big.Float).SetPrec(floatPrecision).SetRat(r)
|
||||||
|
|
||||||
|
for !normal(xf) {
|
||||||
|
root.Sqrt(root)
|
||||||
|
xf, _ = root.Float64()
|
||||||
|
yf *= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// x^y is not zero. If it is not a normal double, it has lost digits,
|
||||||
|
// or all of them.
|
||||||
|
f := math.Pow(xf, yf)
|
||||||
|
if !normal(f) {
|
||||||
return nil, ErrOutOfRange
|
return nil, ErrOutOfRange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,21 @@ func TestEvaluatePowers(t *testing.T) {
|
|||||||
// A power computed in float64 carries its rounding into the exact
|
// A power computed in float64 carries its rounding into the exact
|
||||||
// arithmetic after it, past the range of a double as within it.
|
// arithmetic after it, past the range of a double as within it.
|
||||||
"2^0.5 * 1e400": "1.4142135623730951e+400",
|
"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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,15 +267,17 @@ func TestEvaluateOutOfRange(t *testing.T) {
|
|||||||
"2^5000": calc.ErrOutOfRange,
|
"2^5000": calc.ErrOutOfRange,
|
||||||
"(-2)^5001": calc.ErrOutOfRange,
|
"(-2)^5001": calc.ErrOutOfRange,
|
||||||
"0.5^-5000": calc.ErrOutOfRange,
|
"0.5^-5000": calc.ErrOutOfRange,
|
||||||
// Powers computed in float64 whose base or result is not a
|
// Powers computed in float64 whose result is not a normal double,
|
||||||
// normal double, and so has lost digits, or all of them.
|
// and so has lost digits, or all of them, whatever the size of
|
||||||
|
// the base.
|
||||||
"2^-1073.5 * 2^1073": calc.ErrOutOfRange,
|
"2^-1073.5 * 2^1073": calc.ErrOutOfRange,
|
||||||
"1e400^-0.001": calc.ErrOutOfRange,
|
"2^1500.5": calc.ErrOutOfRange,
|
||||||
"1e-400^0.001": calc.ErrOutOfRange,
|
"(2^1200)^0.9": calc.ErrOutOfRange,
|
||||||
"1e-310^0.5": 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,
|
"(0.5^1100)^4 / (0.5^1100)^4": calc.ErrOutOfRange,
|
||||||
"(1/3)^1e400": calc.ErrOutOfRange,
|
"(1/3)^1e400": calc.ErrOutOfRange,
|
||||||
"(2^1200)^0.5": calc.ErrOutOfRange,
|
|
||||||
// go/constant holds numbers of this size rounded. A sum of them
|
// go/constant holds numbers of this size rounded. A sum of them
|
||||||
// can lose the answer (this one would be 0), and so can a
|
// can lose the answer (this one would be 0), and so can a
|
||||||
// remainder or the sign of -1 to such a power.
|
// remainder or the sign of -1 to such a power.
|
||||||
@@ -343,6 +360,10 @@ func TestEvaluateBoundsWork(t *testing.T) {
|
|||||||
{in: "3^2583", want: "2.5363018640659988e+1232"},
|
{in: "3^2583", want: "2.5363018640659988e+1232"},
|
||||||
{in: "2^-4094", want: "3.8299909843808741e-1233"},
|
{in: "2^-4094", want: "3.8299909843808741e-1233"},
|
||||||
{in: "-1/3^2583", want: "-3.9427483540814775e-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 {
|
for _, c := range cases {
|
||||||
|
|||||||
Reference in New Issue
Block a user