Every number a fraction under the 4096-bit limit (closes #3)
check / check (push) Successful in 1m11s
check / check (push) Successful in 1m11s
go/constant never rounds an integer, and x^0 returned one, so whole numbers built from it escaped the 4096-bit limit: (((2^0+2^0)^4096)^4096)^4096 asked for about 69 billion bits and stalled the bot. A power now starts from the fraction 1, and a number is exact only as a fraction whose numerator and denominator are below 4096 bits, which also refuses a literal that go/constant reads exactly past the limit, such as 1e-1233. The bounded-work test holds the tower and the other short inputs tried against the change. Model: opus-5-5
This commit is contained in:
@@ -172,18 +172,19 @@ container.
|
|||||||
computed as a double, so `2^0.5` is `1.4142135623730951`. A negative
|
computed as a double, so `2^0.5` is `1.4142135623730951`. A negative
|
||||||
number to a fractional power is refused, as having no real result.
|
number to a fractional power is refused, as having no real result.
|
||||||
Numbers are read as decimal, so `010` is ten. Input over 256 bytes is
|
Numbers are read as decimal, so `010` is ten. Input over 256 bytes is
|
||||||
refused and exact powers are capped, so a message cannot make the bot
|
refused, exact powers are capped, and every number is held as a
|
||||||
do unbounded work. Whole numbers below 10<sup>21</sup> are written
|
fraction, whole numbers too, under the 4096-bit limit below, so a
|
||||||
exactly; other results in the shortest form that reads back as the
|
message cannot make the bot do unbounded work. Whole numbers below
|
||||||
same double, in exponent notation from 10<sup>21</sup> up and below
|
10<sup>21</sup> are written exactly; other results in the shortest
|
||||||
10<sup>-6</sup>. Refused as too large or too small: any number whose
|
form that reads back as the same double, in exponent notation from
|
||||||
numerator or denominator reaches 4096 bits, wherever it appears, as
|
10<sup>21</sup> up and below 10<sup>-6</sup>. Refused as too large or
|
||||||
`go/constant` could hold it only rounded (`1e-1300 + 1`); a power
|
too small: any number whose numerator or denominator reaches 4096
|
||||||
computed as a double whose base or result is outside the normal range
|
bits, wherever it appears, as `go/constant` rounds a fraction that
|
||||||
of a double, about 2.2e-308 to 1.8e308 in magnitude, where a double
|
grows that large (`1e-1300 + 1`); a power computed as a double whose
|
||||||
keeps all its digits (`1e-400^0.5`); and a result other than zero
|
base or result is outside the normal range of a double, about 2.2e-308
|
||||||
outside that range, as it is written through a double (`1e400`,
|
to 1.8e308 in magnitude, where a double keeps all its digits
|
||||||
`2^-1400`).
|
(`1e-400^0.5`); and a result other than zero outside that range, as it
|
||||||
|
is written through a double (`1e400`, `2^-1400`).
|
||||||
- **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
|
||||||
|
|||||||
+33
-23
@@ -21,11 +21,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxInputLength caps an expression, in bytes, and maxExactExponent caps
|
// MaxInputLength caps an expression, in bytes. With maxExactExponent,
|
||||||
// a power computed exactly, so that a message cannot make the bot do
|
// which caps a power computed exactly, and bitLimit, which caps every
|
||||||
// unbounded work.
|
// number, it keeps a message from making the bot do unbounded work.
|
||||||
const MaxInputLength = 256
|
const MaxInputLength = 256
|
||||||
|
|
||||||
|
// bitLimit caps the numerator and denominator of every number: see
|
||||||
|
// exact.
|
||||||
|
const bitLimit = 4096
|
||||||
|
|
||||||
// maxExactExponent is the largest exponent, either way, of a power
|
// maxExactExponent is the largest exponent, either way, of a power
|
||||||
// computed exactly. Past it, x^n has a numerator or denominator of more
|
// computed exactly. Past it, x^n has a numerator or denominator of more
|
||||||
// than 4096 bits, which go/constant holds only rounded, unless x is 0 or
|
// than 4096 bits, which go/constant holds only rounded, unless x is 0 or
|
||||||
@@ -245,11 +249,11 @@ func number(tok string) (constant.Value, error) {
|
|||||||
return nil, ErrNotArithmetic
|
return nil, ErrNotArithmetic
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read as FLOAT, which makes every literal decimal: as INT, a
|
// Read as FLOAT, which makes every literal decimal and a fraction
|
||||||
// leading zero would make it octal.
|
// (see exact): as INT, a leading zero would make it octal.
|
||||||
v := constant.MakeFromLiteral(tok, token.FLOAT, 0)
|
v := constant.MakeFromLiteral(tok, token.FLOAT, 0)
|
||||||
|
|
||||||
// A literal such as 1e1300 or 1e-1300 is held rounded: see exact.
|
// A literal such as 1e1300 or 1e-1233 is past bitLimit: see exact.
|
||||||
if !exact(v) {
|
if !exact(v) {
|
||||||
return nil, ErrOutOfRange
|
return nil, ErrOutOfRange
|
||||||
}
|
}
|
||||||
@@ -403,10 +407,13 @@ func nonNegativePower(x, y, n constant.Value) (constant.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// exactPower computes x^e by repeated squaring. x is not zero if e is
|
// exactPower computes x^e by repeated squaring. x is not zero if e is
|
||||||
// negative. Each step's numbers stay small: go/constant holds one whose
|
// negative. It starts from 1 as a fraction, a Float to go/constant, so
|
||||||
// numerator or denominator reaches 4096 bits as a 512-bit float.
|
// that x^0 is a fraction like every other number (see exact). Each
|
||||||
|
// step's numbers stay small: go/constant holds one whose numerator or
|
||||||
|
// denominator reaches 4096 bits as a 512-bit float.
|
||||||
func exactPower(x constant.Value, e int64) constant.Value {
|
func exactPower(x constant.Value, e int64) constant.Value {
|
||||||
result := constant.MakeInt64(1)
|
one := constant.MakeFloat64(1)
|
||||||
|
result := one
|
||||||
|
|
||||||
for n := max(e, -e); n > 0; n >>= 1 {
|
for n := max(e, -e); n > 0; n >>= 1 {
|
||||||
if n&1 == 1 {
|
if n&1 == 1 {
|
||||||
@@ -417,26 +424,29 @@ func exactPower(x constant.Value, e int64) constant.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if e < 0 {
|
if e < 0 {
|
||||||
result = constant.BinaryOp(constant.MakeInt64(1), token.QUO, result)
|
result = constant.BinaryOp(one, token.QUO, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// exact reports whether go/constant holds v exactly. It holds a number
|
// exact reports whether v is a fraction whose numerator and denominator
|
||||||
// as a fraction until its numerator or denominator reaches 4096 bits,
|
// are both below bitLimit bits, as every number here must be, so that
|
||||||
// then as a 512-bit float, and past that float's range as Unknown. A
|
// each step of arithmetic stays small. go/constant never rounds an
|
||||||
// number not held exactly is refused wherever it appears: a sum can lose
|
// integer, however large, so every number is made a fraction: literals
|
||||||
// the answer entirely (7^1000*7^1000 + 5 - 7^1000*7^1000 would be 0), and
|
// are read as FLOAT, and a power starts from the fraction 1. It rounds
|
||||||
// a remainder, or whether an exponent is whole or odd, cannot be read
|
// a fraction that grows past the limit, to a 512-bit float and past
|
||||||
// from one.
|
// that float's range to Unknown, but not one it reads from a literal,
|
||||||
|
// such as 1e-1233, so the limit is checked here.
|
||||||
|
//
|
||||||
|
// A number that is not exact is refused wherever it appears: a sum can
|
||||||
|
// lose the answer entirely (7^1000*7^1000 + 5 - 7^1000*7^1000 would be
|
||||||
|
// 0), and a remainder, or whether an exponent is whole or odd, cannot be
|
||||||
|
// read from one.
|
||||||
func exact(v constant.Value) bool {
|
func exact(v constant.Value) bool {
|
||||||
switch constant.Val(v).(type) {
|
r, ok := constant.Val(v).(*big.Rat)
|
||||||
case int64, *big.Int, *big.Rat:
|
|
||||||
return true
|
return ok && r.Num().BitLen() < bitLimit && r.Denom().BitLen() < bitLimit
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal reports whether f is a normal double, finite and at least
|
// normal reports whether f is a normal double, finite and at least
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ func TestEvaluateModulo(t *testing.T) {
|
|||||||
// Both operands and their quotient are held exactly, but y times
|
// Both operands and their quotient are held exactly, but y times
|
||||||
// the whole part of x/y is too large to be.
|
// the whole part of x/y is too large to be.
|
||||||
"(5^860*3^630/7) % (5^860/2^998/2^998)": "0.5179219763783696",
|
"(5^860*3^630/7) % (5^860/2^998/2^998)": "0.5179219763783696",
|
||||||
|
// A whole number made from x^0, just below the 4096-bit limit.
|
||||||
|
"(3^0 + 3^0 + 3^0)^2583 % 10": "7",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +245,11 @@ func TestEvaluateOutOfRange(t *testing.T) {
|
|||||||
"0.1^800 * 0.1^800": calc.ErrOutOfRange,
|
"0.1^800 * 0.1^800": calc.ErrOutOfRange,
|
||||||
// Both operands are held exactly, but their quotient is not.
|
// Both operands are held exactly, but their quotient is not.
|
||||||
"3^1365 % 7^-1000": calc.ErrOutOfRange,
|
"3^1365 % 7^-1000": calc.ErrOutOfRange,
|
||||||
|
// The same limit for a whole number made from x^0, which
|
||||||
|
// go/constant would hold as an integer and never round, and for
|
||||||
|
// a literal it reads exactly as a fraction past the limit.
|
||||||
|
"(2^0 + 2^0)^4095 % 10": calc.ErrOutOfRange,
|
||||||
|
"1e-1233 * 0": calc.ErrOutOfRange,
|
||||||
// go/constant reads this literal as 0.
|
// go/constant reads this literal as 0.
|
||||||
"1e-999999999": calc.ErrOutOfRange,
|
"1e-999999999": calc.ErrOutOfRange,
|
||||||
"1 / 1e-999999999": calc.ErrOutOfRange,
|
"1 / 1e-999999999": calc.ErrOutOfRange,
|
||||||
@@ -288,6 +295,20 @@ func TestEvaluateBoundsWork(t *testing.T) {
|
|||||||
{in: strings.Repeat("9^", 127) + "9", err: calc.ErrOutOfRange},
|
{in: strings.Repeat("9^", 127) + "9", err: calc.ErrOutOfRange},
|
||||||
// The largest power of 3 computed exactly, as often as fits.
|
// The largest power of 3 computed exactly, as often as fits.
|
||||||
{in: "0" + strings.Repeat("*3^2583", 36), want: "0"},
|
{in: "0" + strings.Repeat("*3^2583", 36), want: "0"},
|
||||||
|
// Whole numbers made from x^0, through each operation. Held as
|
||||||
|
// integers, which go/constant never rounds, they would escape
|
||||||
|
// the 4096-bit limit: the first needs about 69 billion bits.
|
||||||
|
{in: "(((2^0+2^0)^4096)^4096)^4096", err: calc.ErrOutOfRange},
|
||||||
|
{in: "(((0^0+0^0)^4096)^4096)^4096", err: calc.ErrOutOfRange},
|
||||||
|
{in: "(((-2^0-2^0)^4096)^4096)^4096", err: calc.ErrOutOfRange},
|
||||||
|
{in: "((2^0+2^0)^4000*(2^0+2^0)^4000)^4096", err: calc.ErrOutOfRange},
|
||||||
|
{in: "((((2^0+2^0)/2^0)^4096)^4096)^4096", err: calc.ErrOutOfRange},
|
||||||
|
{in: "((((2^0+2^0) % 3)^4096)^4096)^4096", err: calc.ErrOutOfRange},
|
||||||
|
{in: "(((2^0+2^0)^4096)^4096)^4096 * 0", err: calc.ErrOutOfRange},
|
||||||
|
// A fraction whose numerator and denominator are both just below
|
||||||
|
// 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},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
|||||||
Reference in New Issue
Block a user