Skip to content

Compute cubic bézier extrema without cancellation - #985

Merged
nical merged 1 commit into
nical:mainfrom
b84330808:cubic-extremum-precision
Nov 5, 2026
Merged

nical merged 1 commit into
nical:mainfrom
b84330808:cubic-extremum-precision

Conversation

@b84330808

Copy link
Copy Markdown
Contributor

CubicBezierSegment::for_each_local_extremum solves the derivative with the textbook quadratic formula. When the derivative's leading coefficient is small compared to the other two, -b ± sqrt(discriminant) subtracts nearly equal values and the f32 result loses most of its precision.

Example: y control points 0.001, 3000, 4000, 3000. The y maximum is at t = 0.75 (y = 3375), but y_maximum_t returned 0.8138, so bounding_box().max.y was 3350.6 and the box didn't contain the curve.

This switches to the q / a and c / q form that cubic_polynomial_roots uses since #978, and adds a regression test for that curve. Found by fuzzing bounding_box against sampled points; with the change, 900k random curves found no point outside the box.

I used Claude Code to write this change.

`for_each_local_extremum` solved the derivative with the textbook
quadratic formula. When its leading coefficient is small compared to the
others, `-b + sqrt(discriminant)` subtracts two close values and the f32
result loses most of its precision. For a curve whose y control points are
0.001, 3000, 4000 and 3000, `y_maximum_t` returned 0.8138 instead of 0.75,
so `bounding_box` stopped at y = 3350.6 while the curve reaches 3375.

Use the same q / a and c / q form as `cubic_polynomial_roots`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@nical

nical commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Thanks!

@nical
nical merged commit c3e869f into nical:main Nov 5, 2026
3 checks passed
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.

2 participants