Charts · 20
Floating point, at the pixel
Three columns at 33.333% leave a hairline of background showing. A card that animates in on a transform has a border that looks slightly out of focus. A basket adds three items at £0.10 and shows £0.30000000000000004. None of these is a bug in your code; they are what happens when numbers that cannot be represented meet a grid that can only be whole. Drag the widths and watch the browser measure itself.
1 · The seam
3 × 33.3333% of 1001px = 333.667px each. Measured: = 0.000px (-1001.000px short: a seam). Edges land at : on a 1× screen the browser can only paint at multiples of 1.00px, so each fractional edge is either snapped or blended.
2 · The fuzzy border
The same 1px border three times. A transform is applied at raster time and can sit between device pixels, so at 0.50px the edge is anti-aliased across two of them and reads as blurred. Layout positions (margin, left) are snapped to the pixel grid before painting, so the third box stays crisp. Zoom in with the OS magnifier to see it; on a 1× screen it is obvious at 0.5, on a 2× screen it is subtler because the grid is finer.
3 · The price
0.1 + 0.20.300000000000000040.1 + 0.70.79999999999999993 × 0.10.30000000000000004(1.005).toFixed(2)1.0019.99 × 359.970.1 × 3 in cents0.3100 / 3 × 31002⁵³ + 19007199254740992A double has 53 bits of mantissa: every integer up to 2⁵³ exactly, and almost no decimal fractions at all, because a tenth is a repeating fraction in binary the way a third is in decimal. 1.005 is stored as 1.00499999999999989…, so toFixed(2) honestly rounds it down. Money goes in integer minor units, or a decimal type; never in a float.
4 · The bits
0positive011111110112^-4100110011001100110011001100110011001100110011001101052 bits, plus the implicit leading 1Stored value, to 20 significant figures: 0.10000000000000000555. Not the number you typed: the nearest double to it.