Skip to content

Don't break a flex line on floating-point rounding noise - #2022

Open
jwatzman wants to merge 1 commit into
react:mainfrom
jwatzman:fix-flexwrap-content-sized-rounding
Open

Don't break a flex line on floating-point rounding noise#2022
jwatzman wants to merge 1 commit into
react:mainfrom
jwatzman:fix-flexwrap-content-sized-rounding

Conversation

@jwatzman

@jwatzman jwatzman commented Sep 3, 2026

Copy link
Copy Markdown

A content-sized wrapping row sizes itself to the max-content sum of its items, then recovers the space available to those items by subtracting its padding and border back off (calculateAvailableInnerDimension). In float32 that add-then-subtract round-trip can land a fraction of an ulp below the sum it came from.

For an item with a measure function the measurement cache still returns the max-content measurement in that situation: oldSizeIsMaxContentAndStillFits accepts a cached width up to the inexactEquals epsilon (1e-4) wider than the space now available. calculateFlexLine then compared that basis against availableInnerMainDim with a bare >, so the basis the cache had just accepted as fitting read as overflow and the line broke.

Because the container had already been sized for a single line, the result was a box one line tall with its items laid out on two, overflowing its own padding and border. In React Native this reproduced on both iOS and Android and was extremely sensitive to the measured text width, since only some values leave a residue inside the window.

Make the overflow test tolerant of the same epsilon the cache uses, so the two agree. Items without a measure function are unaffected: they take their exact style width in both passes and never enter that cache path.

The regression test sweeps fractional measured widths whose content sum sits just under 128 while the outer width sits just over it, so adding padding and border crosses a float32 binade and the subtraction cannot always recover the original value.

A content-sized wrapping row sizes itself to the max-content sum of its
items, then recovers the space available to those items by subtracting its
padding and border back off (calculateAvailableInnerDimension). In float32
that add-then-subtract round-trip can land a fraction of an ulp below the
sum it came from.

For an item with a measure function the measurement cache still returns the
max-content measurement in that situation: oldSizeIsMaxContentAndStillFits
accepts a cached width up to the inexactEquals epsilon (1e-4) wider than the
space now available. calculateFlexLine then compared that basis against
availableInnerMainDim with a bare `>`, so the basis the cache had just
accepted as fitting read as overflow and the line broke.

Because the container had already been sized for a single line, the result
was a box one line tall with its items laid out on two, overflowing its own
padding and border. In React Native this reproduced on both iOS and Android
and was extremely sensitive to the measured text width, since only some
values leave a residue inside the window.

Make the overflow test tolerant of the same epsilon the cache uses, so the
two agree. Items without a measure function are unaffected: they take their
exact style width in both passes and never enter that cache path.

The regression test sweeps fractional measured widths whose content sum sits
just under 128 while the outer width sits just over it, so adding padding and
border crosses a float32 binade and the subtraction cannot always recover the
original value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed label Sep 3, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant