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:
+33
-23
@@ -21,11 +21,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MaxInputLength caps an expression, in bytes, and maxExactExponent caps
|
||||
// a power computed exactly, so that a message cannot make the bot do
|
||||
// unbounded work.
|
||||
// MaxInputLength caps an expression, in bytes. With maxExactExponent,
|
||||
// which caps a power computed exactly, and bitLimit, which caps every
|
||||
// number, it keeps a message from making the bot do unbounded work.
|
||||
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
|
||||
// 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
|
||||
@@ -245,11 +249,11 @@ func number(tok string) (constant.Value, error) {
|
||||
return nil, ErrNotArithmetic
|
||||
}
|
||||
|
||||
// Read as FLOAT, which makes every literal decimal: as INT, a
|
||||
// leading zero would make it octal.
|
||||
// Read as FLOAT, which makes every literal decimal and a fraction
|
||||
// (see exact): as INT, a leading zero would make it octal.
|
||||
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) {
|
||||
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
|
||||
// negative. Each step's numbers stay small: go/constant holds one whose
|
||||
// numerator or denominator reaches 4096 bits as a 512-bit float.
|
||||
// negative. It starts from 1 as a fraction, a Float to go/constant, so
|
||||
// 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 {
|
||||
result := constant.MakeInt64(1)
|
||||
one := constant.MakeFloat64(1)
|
||||
result := one
|
||||
|
||||
for n := max(e, -e); n > 0; n >>= 1 {
|
||||
if n&1 == 1 {
|
||||
@@ -417,26 +424,29 @@ func exactPower(x constant.Value, e int64) constant.Value {
|
||||
}
|
||||
|
||||
if e < 0 {
|
||||
result = constant.BinaryOp(constant.MakeInt64(1), token.QUO, result)
|
||||
result = constant.BinaryOp(one, token.QUO, result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// exact reports whether go/constant holds v exactly. It holds a number
|
||||
// as a fraction until its numerator or denominator reaches 4096 bits,
|
||||
// then as a 512-bit float, and past that float's range as Unknown. A
|
||||
// number not held exactly 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.
|
||||
// exact reports whether v is a fraction whose numerator and denominator
|
||||
// are both below bitLimit bits, as every number here must be, so that
|
||||
// each step of arithmetic stays small. go/constant never rounds an
|
||||
// integer, however large, so every number is made a fraction: literals
|
||||
// are read as FLOAT, and a power starts from the fraction 1. It rounds
|
||||
// a fraction that grows past the limit, to a 512-bit float and past
|
||||
// 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 {
|
||||
switch constant.Val(v).(type) {
|
||||
case int64, *big.Int, *big.Rat:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
r, ok := constant.Val(v).(*big.Rat)
|
||||
|
||||
return ok && r.Num().BitLen() < bitLimit && r.Denom().BitLen() < bitLimit
|
||||
}
|
||||
|
||||
// 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
|
||||
// the whole part of x/y is too large to be.
|
||||
"(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,
|
||||
// Both operands are held exactly, but their quotient is not.
|
||||
"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.
|
||||
"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},
|
||||
// The largest power of 3 computed exactly, as often as fits.
|
||||
{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 {
|
||||
|
||||
Reference in New Issue
Block a user