Skip to content

Fix cube roots: real odd roots of negative bases - #82

Merged
aantthony merged 1 commit into
mainfrom
fix-cube-roots-issue-15
Aug 8, 2026
Merged

Fix cube roots: real odd roots of negative bases#82
aantthony merged 1 commit into
mainfrom
fix-cube-roots-issue-15

Conversation

@aantthony

Copy link
Copy Markdown
Owner

Summary

Closes #15.

(-8)^(1/3) (and any negative base with a fractional exponent) previously evaluated to NaN everywhere — both in the CPU evaluator (Math.pow(-8, 1/3) is NaN in JS) and in the rendered curve (GLSL's pow() is undefined for negative bases). The only existing special case was for exponents that are (numerically) exact integers.

This matches how graphing calculators like Desmos handle it: for a negative base a and exponent b, find b's rational form p/q in lowest terms via a tolerance search over small denominators. If q is odd, the root is real: sign * |a|^b, where sign is negative iff the reduced numerator p is odd. If q is even (an even root, e.g. (-4)^(1/2)), or no small-denominator match is found (an irrational-looking exponent), the result stays NaN, same as before.

Changes

  • lib/expr.ts: added realPow(a, b), used by evaluate()'s ^ case instead of a bare Math.pow. Handles the CPU-side path (hover values, root-finding, etc).
  • lib/glsl.ts: rewrote the eq_pow() GLSL helper with the same rational-exponent algorithm (GLSL has no big-int/rational type, so it's implemented with a fixed denominator search q = 1..12 and the existing eq_gcd helper instead of a real gcd loop). Used for rendering curves/surfaces.
  • lib/expr.test.ts: added a describe('negative base with fractional exponent (real odd roots)') block covering cube roots, p/q with even numerator, negative exponents, even roots staying undefined, and that a typed decimal like 0.33333 is not snapped to 1/3.

Both implementations use the same tolerance (1e-6) and max denominator (12), and are commented as needing to stay in sync since GLSL source text and TS can't share code directly.

Examples verified

Input Result
8^(1/3) 2 (unaffected, positive base)
(-8)^(1/3) -2
(-8)^(2/3) 4
(-1)^(1/3) -1
(-8)^(-1/3) -0.5
(-4)^(1/2) NaN (even root, correctly undefined)
(-8)^(1/4) NaN (q=4 even)
(-8)^(0.33333) NaN (deliberately not snapped — see tolerance note below)
(-2)^3, (-2)^2 -8, 4 (existing integer-exponent behavior unchanged)

On the tolerance choice: 1e-6 is tight enough that an exponent entered as an actual fraction (1/3 parses to 0.3333333333333333, ~1e-16 from the true rational) always snaps, while a typed decimal approximation like 0.33333 (~3.3e-6 away from 1/3) is left undefined rather than silently guessed at. This is documented in a comment on realPow().

Test plan

  • npx vitest run — all 495 tests pass (including the new cases)
  • npx tsc --noEmit and npx tsc -p web --noEmit — clean
  • npx tsc -p worker --noEmit has pre-existing errors in this sandbox unrelated to this change (missing wrangler types-generated ambient globals — confirmed identical errors on a clean checkout with wrangler unavailable in this environment)

🤖 Generated with Claude Code

Math.pow(-8, 1/3) and the GLSL pow() builtin both return NaN, so any
negative base with a fractional exponent rendered/evaluated as
undefined even for real odd roots like cube roots. Graphing
calculators (Desmos, etc.) instead find the exponent's rational form
p/q in lowest terms via a tolerance search over small denominators,
and return a real result when q is odd (sign * |a|^b, sign negative
iff p is odd). Even roots of negative numbers (e.g. (-4)^(1/2)) stay
undefined.

Implemented in both the CPU evaluator (realPow() in lib/expr.ts) and
the GLSL shader codegen (eq_pow() in lib/glsl.ts) so hover values,
root-finding, and the rendered curve all agree. The two are documented
as needing to stay in sync since GLSL can't share TS source.

Fixes #15
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
equation 8ebdf76 Commit Preview URL

Branch Preview URL
Aug 03 2026, 11:52 AM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds real odd-root support for negative bases in CPU and GLSL evaluation.

Changes:

  • Introduces realPow() with rational-exponent detection.
  • Updates GLSL power handling.
  • Adds evaluator tests for odd and even roots.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
lib/expr.ts Adds CPU-side real-power evaluation.
lib/glsl.ts Adds equivalent shader-side handling.
lib/expr.test.ts Tests negative-base fractional powers.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/expr.ts
case '*': return a * b;
case '/': return a / b;
case '^': return Math.pow(a, b);
case '^': return realPow(a, b);
@aantthony
aantthony merged commit 3e3c8a0 into main Aug 8, 2026
3 checks passed
aantthony added a commit that referenced this pull request Aug 8, 2026
…#84)

The stack VM behind og previews and MCP validation still used a bare
Math.pow, so (-8)^(1/3) rendered as undefined in previews while the app
(fixed in #82) draws -2.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cube roots

2 participants