Powers and remainders in the calculator (closes #3) #11

Merged
clawbot merged 4 commits from issue-3-power-modulo into next 2026-09-29 04:28:16 +02:00
Collaborator

For #3.

What changed

  • internal/calc has its own parser, as go/parser has no power operator; go/constant still computes. Decimal-only numbers, the 256-byte cap and the refusals stay.
  • ^ (also **) binds tighter than * / % and a sign on its left, and groups to the right. % ranks with * and /, takes the divisor's sign, and is exact on decimals.
  • A number is exact (a fraction with numerator and denominator below 4096 bits), a normal double from float64 (about 2.2e-308 to 1.8e308), or zero, or it is refused, as is a result neither zero nor a normal double. Whole numbers are fractions too, since go/constant never rounds an integer. This limit and the input and exponent caps bound a message's work.
  • A whole exponent up to 4096 either way is exact when the result stays within the limit (2^-1400 does); otherwise, and for a fractional exponent, math.Pow computes it.
  • ErrTooLarge became ErrOutOfRange, whose reply covers too large and too small; a negative base with a fractional exponent gets its own reply; the welcome and "only arithmetic" replies mention ^ and %.

Worth knowing

  • A doubled sign (--2) and line breaks between tokens now parse.
  • 1.0000001^99999 is 1.01005006557947, from math.Pow, about 5e-12 off.
  • 1e-999999999 (read by go/constant as 0) and 1e-1233 (read exactly past the limit) are refused.

Judgement call: a result below about 2.2e-308 is refused even when exact (1e-310, which next answers), as 2^-1074 would read 5e-324.

Model: opus-5-5

For https://git.eeqj.de/sneak/simplexcalc/issues/3. **What changed** - `internal/calc` has its own parser, as `go/parser` has no power operator; `go/constant` still computes. Decimal-only numbers, the 256-byte cap and the refusals stay. - `^` (also `**`) binds tighter than `* / %` and a sign on its left, and groups to the right. `%` ranks with `*` and `/`, takes the divisor's sign, and is exact on decimals. - A number is exact (a fraction with numerator and denominator below 4096 bits), a normal double from float64 (about 2.2e-308 to 1.8e308), or zero, or it is refused, as is a result neither zero nor a normal double. Whole numbers are fractions too, since `go/constant` never rounds an integer. This limit and the input and exponent caps bound a message's work. - A whole exponent up to 4096 either way is exact when the result stays within the limit (`2^-1400` does); otherwise, and for a fractional exponent, `math.Pow` computes it. - `ErrTooLarge` became `ErrOutOfRange`, whose reply covers too large and too small; a negative base with a fractional exponent gets its own reply; the welcome and "only arithmetic" replies mention `^` and `%`. **Worth knowing** - A doubled sign (`--2`) and line breaks between tokens now parse. - `1.0000001^99999` is `1.01005006557947`, from `math.Pow`, about 5e-12 off. - `1e-999999999` (read by `go/constant` as 0) and `1e-1233` (read exactly past the limit) are refused. Judgement call: a result below about 2.2e-308 is refused even when exact (`1e-310`, which `next` answers), as `2^-1074` would read `5e-324`. Model: opus-5-5
clawbot added the needs-review label 2026-09-29 02:14:17 +02:00
clawbot self-assigned this 2026-09-29 02:14:17 +02:00
clawbot added 1 commit 2026-09-29 02:14:18 +02:00
Powers and remainders in the calculator (closes #3)
check / check (push) Successful in 57s
aeb040d156
internal/calc gets its own tokenizer and precedence-climbing parser in
place of go/parser, which cannot express a power operator; go/constant
still computes. ^ (also **) binds tighter than * / % and a sign on its
left, and groups to the right. % takes the sign of the divisor and is
exact on decimals. A whole exponent is computed exactly while the
result's numerator and denominator stay within 4096 bits; otherwise,
and for a fractional exponent, in float64. A remainder, or the sign of
a negative base's power, that depends on a value go/constant holds
rounded is refused as too large. The welcome text and the replies
mention the new operators; a negative base with a fractional exponent
gets its own reply.

Model: opus-5-5
Author
Collaborator

FAIL: needs-rework

  1. internal/calc/calc.go, modulo: the guard checks only the quotient. When both operands and the quotient are held exactly, the product y * whole can still pass 4096 bits and be rounded, and subtracting it from x loses the answer: (5^860*3^630/7) % (5^860/2^998/2^998) replies 0, but the remainder is 0.5179219763783696. That breaks "% works on decimals, exactly" from #3, and the commit message's claim that such remainders are refused. Acceptable: when the operands are exact, the code never subtracts a rounded value (for instance, compute the remainder as y times the quotient minus its floor, which is exact up to one final rounding and already has the sign of y), or it refuses such a case. Add this example to the tests.
  2. PR body: 1.0000001^99999 is about 5e-12 from the true value (1.0100500655741…), not 5e-13. Acceptable: correct the figure.

Judgement call, not raised: values computed in float64 carry float64 error into % and into a negative base's sign (1.01^5000 % 7 replies 0; (-1)^(1.01^5000) replies 1). That follows from the issue's float64 decision.

Model: opus-5-5

**FAIL: needs-rework** 1. `internal/calc/calc.go`, `modulo`: the guard checks only the quotient. When both operands and the quotient are held exactly, the product `y * whole` can still pass 4096 bits and be rounded, and subtracting it from `x` loses the answer: `(5^860*3^630/7) % (5^860/2^998/2^998)` replies `0`, but the remainder is 0.5179219763783696. That breaks "`%` works on decimals, exactly" from https://git.eeqj.de/sneak/simplexcalc/issues/3, and the commit message's claim that such remainders are refused. Acceptable: when the operands are exact, the code never subtracts a rounded value (for instance, compute the remainder as `y` times the quotient minus its floor, which is exact up to one final rounding and already has the sign of `y`), or it refuses such a case. Add this example to the tests. 2. PR body: `1.0000001^99999` is about 5e-12 from the true value (1.0100500655741…), not 5e-13. Acceptable: correct the figure. Judgement call, not raised: values computed in float64 carry float64 error into `%` and into a negative base's sign (`1.01^5000 % 7` replies `0`; `(-1)^(1.01^5000)` replies `1`). That follows from the issue's float64 decision. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-09-29 02:31:31 +02:00
clawbot added 1 commit 2026-09-29 02:42:57 +02:00
x % y is now y times the fractional part of x/y. The old form, x minus
y times the whole part of x/y, passed through a product that go/constant
could hold only rounded even when both operands and the remainder were
exact, and then replied 0.

A number whose numerator or denominator reaches 4096 bits, which
go/constant holds rounded, is now refused as too large wherever it
appears, literals included. A sum of such numbers could lose the answer,
and a rounded exponent near a whole number was computed as an exact
power. This replaces the separate check on a negative base's exponent.

Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-09-29 02:43:31 +02:00
Author
Collaborator

Rework in 3a8f1cc334:

  1. Fixed: the remainder is now the divisor times the fractional part of the quotient, so no rounded product is subtracted; the example is in the tests.
  2. Corrected in the PR body.

Also closed, as asked: any number go/constant would hold rounded is now refused wherever it appears. That covers sums (7^1000*7^1000 + 5 - 7^1000*7^1000 replied 0) and a rounded exponent close to a whole number, which was computed as an exact power. Judgement call on the reply for such numbers is in the PR body.

Model: opus-5-5

Rework in https://git.eeqj.de/sneak/simplexcalc/commit/3a8f1cc33479411c378dbf520a18b6922b7ca764: 1. Fixed: the remainder is now the divisor times the fractional part of the quotient, so no rounded product is subtracted; the example is in the tests. 2. Corrected in the PR body. Also closed, as asked: any number `go/constant` would hold rounded is now refused wherever it appears. That covers sums (`7^1000*7^1000 + 5 - 7^1000*7^1000` replied `0`) and a rounded exponent close to a whole number, which was computed as an exact power. Judgement call on the reply for such numbers is in the PR body. Model: opus-5-5
Author
Collaborator

FAIL: needs-rework

  1. internal/calc/calc.go, nonNegativePower: a power computed in float64 that underflows comes back as 0, or with few correct digits, and the arithmetic after it treats that as exact. 2^-1400 * 2^1365 * 2^35 replies 0 (the answer is 1); 0.3^900 * 10^470 replies 0 (0.2565…); 1e400^-0.001 and 1e-400^0.001 reply 0 (0.398…), because the base is outside a double's range; (0.5^1100)^4 / (0.5^1100)^4 replies division by zero; 2^-1073.5 * 2^1073 replies 0.5 (0.7071…). A float64 result that overflows is refused; one that underflows is not. The size estimate also sends 2^-1400, an exact result of about 1400 bits, to float64, though #3 computes a whole exponent exactly unless the result would pass the limit. Acceptable: when the base is not zero and the float64 result is 0 or below the smallest normal double (about 2.2e-308), the power is refused as an overflow is; these examples in the tests.
  2. internal/bot/bot.go reply for calc.ErrTooLarge, as now reached through rounded: a number refused for being too small to hold gets "The result is too large for me.", which is false: 1e-1300, 1e-700 * 1e-700, 0.1^800 * 0.1^800, and 1e-1300 + 1, which replies 1 on next. The fix for 1 adds more of these. Acceptable: such a refusal gets a sentence that is true for it (its own, or one that covers too large and too small alike), with the README bullet and the tests to match.

Judgement call, not raised: float64 rounding still carries into later arithmetic, as noted in the first review ((1 + 1e-400)^1e400 replies 1; the answer is e).

Model: opus-5-5

**FAIL: needs-rework** 1. `internal/calc/calc.go`, `nonNegativePower`: a power computed in float64 that underflows comes back as `0`, or with few correct digits, and the arithmetic after it treats that as exact. `2^-1400 * 2^1365 * 2^35` replies `0` (the answer is `1`); `0.3^900 * 10^470` replies `0` (0.2565…); `1e400^-0.001` and `1e-400^0.001` reply `0` (0.398…), because the base is outside a double's range; `(0.5^1100)^4 / (0.5^1100)^4` replies division by zero; `2^-1073.5 * 2^1073` replies `0.5` (0.7071…). A float64 result that overflows is refused; one that underflows is not. The size estimate also sends `2^-1400`, an exact result of about 1400 bits, to float64, though https://git.eeqj.de/sneak/simplexcalc/issues/3 computes a whole exponent exactly unless the result would pass the limit. Acceptable: when the base is not zero and the float64 result is 0 or below the smallest normal double (about 2.2e-308), the power is refused as an overflow is; these examples in the tests. 2. `internal/bot/bot.go` reply for `calc.ErrTooLarge`, as now reached through `rounded`: a number refused for being too small to hold gets "The result is too large for me.", which is false: `1e-1300`, `1e-700 * 1e-700`, `0.1^800 * 0.1^800`, and `1e-1300 + 1`, which replies `1` on `next`. The fix for 1 adds more of these. Acceptable: such a refusal gets a sentence that is true for it (its own, or one that covers too large and too small alike), with the README bullet and the tests to match. Judgement call, not raised: float64 rounding still carries into later arithmetic, as noted in the first review (`(1 + 1e-400)^1e400` replies `1`; the answer is e). Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-09-29 02:59:35 +02:00
clawbot added 1 commit 2026-09-29 03:23:50 +02:00
A power computed in float64 is refused unless its base and its result
are normal doubles, and so is a result other than zero that is not one:
below about 2.2e-308 a double keeps fewer digits, and at 0 or infinity
none. A literal that go/constant reads as 0 though it is not, such as
1e-999999999, is refused too.

A whole exponent up to 4096 either way is computed exactly and kept
when go/constant holds the result exactly, so 2^-1400 is exact.

ErrTooLarge becomes ErrOutOfRange, and its reply, "That needs a number
too large or too small for me.", is true of both.

Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-09-29 03:24:25 +02:00
Author
Collaborator

Rework in a1125e59dd:

  1. Fixed: a power computed by math.Pow is refused unless its base and its result are normal doubles, and a whole exponent is exact whenever go/constant holds the result exactly; the examples are in the tests.
  2. Fixed: the reply is now "That needs a number too large or too small for me.", with the README bullet and the tests to match.

Also fixed under the same rule: 2^-1074 replied 5e-324, and go/constant read 1e-999999999 as 0, so 1 / 1e-999999999 replied division by zero, as it does on next.

Judgement call: a result below about 2.2e-308 is refused even when exact, such as 1e-310, which next answers.

Model: opus-5-5

Rework in https://git.eeqj.de/sneak/simplexcalc/commit/a1125e59ddd4ab00518b243b4da7118aa0192817: 1. Fixed: a power computed by `math.Pow` is refused unless its base and its result are normal doubles, and a whole exponent is exact whenever `go/constant` holds the result exactly; the examples are in the tests. 2. Fixed: the reply is now "That needs a number too large or too small for me.", with the README bullet and the tests to match. Also fixed under the same rule: `2^-1074` replied `5e-324`, and `go/constant` read `1e-999999999` as 0, so `1 / 1e-999999999` replied division by zero, as it does on `next`. Judgement call: a result below about 2.2e-308 is refused even when exact, such as `1e-310`, which `next` answers. Model: opus-5-5
Author
Collaborator

FAIL: needs-rework

  1. internal/calc/calc.go, exactPower and exact: the bound on work does not hold for whole numbers built from x^0. For a zero exponent exactPower returns go/constant's integer 1, and sums, products and whole powers of such integers stay integers, which go/constant never rounds, so the 4096-bit limit never applies to them. (((2^0+2^0)^4096)^4096)^4096, 28 bytes, asks for an exact number of about 69 billion bits; the bot never answers, and as it computes replies in its message loop, it stops replying to everyone. That breaks the bounded-work decision on #3. The same numbers make the exact and maxExactExponent comments and the README's "any number whose numerator or denominator reaches 4096 bits, wherever it appears" untrue ((2^0+2^0)^4095 % 10 is answered, 2^4095 % 10 is refused). Acceptable: every number is held as a fraction, so the limit applies wherever a number came from (for instance, a zero exponent returns the fraction 1, or results pass through constant.ToFloat); the tower is in the bounded-work test and is answered at once.

Judgement call, not raised: a product whose exact fraction passes the limit is refused though its value is well inside the normal range, while the equal power is answered in float64 (1.1^1000 * 1.1^1000 is refused; 1.1^2000 is 6.1005686194496e+82). That follows the decision to refuse anything neither exact nor computed in float64.

Judgement call: the PR body is 261 words, taken as within about 250.

Model: opus-5-5

**FAIL: needs-rework** 1. `internal/calc/calc.go`, `exactPower` and `exact`: the bound on work does not hold for whole numbers built from `x^0`. For a zero exponent `exactPower` returns `go/constant`'s integer 1, and sums, products and whole powers of such integers stay integers, which `go/constant` never rounds, so the 4096-bit limit never applies to them. `(((2^0+2^0)^4096)^4096)^4096`, 28 bytes, asks for an exact number of about 69 billion bits; the bot never answers, and as it computes replies in its message loop, it stops replying to everyone. That breaks the bounded-work decision on https://git.eeqj.de/sneak/simplexcalc/issues/3. The same numbers make the `exact` and `maxExactExponent` comments and the README's "any number whose numerator or denominator reaches 4096 bits, wherever it appears" untrue (`(2^0+2^0)^4095 % 10` is answered, `2^4095 % 10` is refused). Acceptable: every number is held as a fraction, so the limit applies wherever a number came from (for instance, a zero exponent returns the fraction 1, or results pass through `constant.ToFloat`); the tower is in the bounded-work test and is answered at once. Judgement call, not raised: a product whose exact fraction passes the limit is refused though its value is well inside the normal range, while the equal power is answered in float64 (`1.1^1000 * 1.1^1000` is refused; `1.1^2000` is `6.1005686194496e+82`). That follows the decision to refuse anything neither exact nor computed in float64. Judgement call: the PR body is 261 words, taken as within about 250. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-09-29 03:40:11 +02:00
clawbot added 1 commit 2026-09-29 04:09:51 +02:00
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
clawbot added needs-review and removed needs-rework labels 2026-09-29 04:10:01 +02:00
Author
Collaborator

Rework in 3f5e453a18:

  1. Fixed: every number is held as a fraction and checked against the 4096-bit limit wherever it comes from, whole numbers from x^0 included; the tower and the other short inputs tried against the change are in the bounded-work test.

Also closed under the same limit: a literal that go/constant reads exactly past it, such as 1e-1233, is now refused (1e-1233 * 0 replied 0).

Model: opus-5-5

Rework in https://git.eeqj.de/sneak/simplexcalc/commit/3f5e453a183d1d26f05baf71fbaa4ee7f3690535: 1. Fixed: every number is held as a fraction and checked against the 4096-bit limit wherever it comes from, whole numbers from `x^0` included; the tower and the other short inputs tried against the change are in the bounded-work test. Also closed under the same limit: a literal that `go/constant` reads exactly past it, such as `1e-1233`, is now refused (`1e-1233 * 0` replied `0`). Model: opus-5-5
Author
Collaborator

PASS: this meets #3 and the decision recorded on it, and every number, whole numbers from x^0 included, now stays under the 4096-bit limit.

Judgement call, not raised: a base that float64 rounds to exactly 1, raised to an exponent past a double's range, still replies 1 ((1+1/3^2583)^(3^2583); the answer is e), the case already on record as (1 + 1e-400)^1e400.

Model: opus-5-5

**PASS**: this meets https://git.eeqj.de/sneak/simplexcalc/issues/3 and the decision recorded on it, and every number, whole numbers from `x^0` included, now stays under the 4096-bit limit. Judgement call, not raised: a base that float64 rounds to exactly 1, raised to an exponent past a double's range, still replies `1` (`(1+1/3^2583)^(3^2583)`; the answer is e), the case already on record as `(1 + 1e-400)^1e400`. Model: opus-5-5
clawbot merged commit ac721390de into next 2026-09-29 04:28:16 +02:00
clawbot deleted branch issue-3-power-modulo 2026-09-29 04:28:16 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/simplexcalc#11