Floating Point Gotchas in UI and Animation

In Quelly’s post in the Tip Calculator discussion, she pointed out that JavaScript stores an approximation of 0.1. That matters outside currency too: an animation’s stopping condition can fail if it expects repeated decimal additions to land exactly on a target.

Here’s a small example you can try in the console:

let progress = 0;

for (let step = 0; step < 10; step++) {
  progress += 0.1;
}

console.log(progress);       // 0.9999999999999999
console.log(progress === 1); // false

The difference is far too small to see as a pixel-sized gap, but progress === 1 is still false. For a duration-based animation, calculate progress from elapsed time, clamp it to 1, and explicitly assign the target position when the duration ends. That also avoids tying the animation’s speed to the number of frames rendered.

Fractional CSS pixels are a separate issue. width: calc(100% / 3) does not, by itself, demonstrate a floating-point bug or imply visible gaps. Layout, zoom, and rasterization can affect how edges look; rounding every animation position to a whole pixel can make slow movement less smooth.

Edit: The original opener sounded like a reply to Quelly in this thread and incorrectly presented the CSS thirds example as evidence of floating-point error. The source link and example above correct that.

That calc(100% / 3) one has bitten me too — you stare at a row that looks fine until you zoom in and there’s a hairline gap on one side and not the other.

The place I notice it most is animation. If you’re easing something across the screen and you keep adding a fractional step each frame, it can land at like 299.9999 instead of 300, and then it snaps or jitters right at the end. Rounding to whole pixels for the final position, or just letting the animation finish at the exact target value instead of accumulating steps, usually cleans that up.

Same thing with canvas and progress bars. A bar at 33.333% repeated a few times doesn’t quite add up to a full bar, and on a high-DPI screen those subpixel leftovers can look blurry. I’ve started snapping the drawn width to the nearest pixel and keeping the math in integers where I can, then only converting to a percentage for display.

Curious if anyone else has a go-to rule for when to round and when to leave the float alone?