Floating Point

Everybody knows 0.1 + 0.2 does not give 0.3, and nearly everybody blames the addition. Add the two stored values exactly, in whole numbers, with nothing rounded at all, and you land past 0.3 anyway. The addition was the second mistake.

A number, what the machine kept of it, and what that costs

1 What the machine stored when you typed it

Type any decimal. Nothing here is rounded for display: a double is a whole number times a power of two, so its decimal expansion always ends, and the page writes out every digit of it.

you typed
0.1
it holds
0.1000000000000000055511151231257827021181583404541015625
which is not what you typed

Sixty-four bits: one of sign, eleven of exponent, fifty-two of fraction. With the bias taken off, the exponent field reads -4, and that is the power of two multiplying the leading 1.fraction. Counted as a whole number instead, the significand is 7205759403792794 and its scale is 2^-56. That pair multiplies back to the value above, exactly.

2 Its neighbours, and the gap at that size

There is no number between a double and the next one. The set is finite, so it can be counted rather than described.

the double below
0.09999999999999999167332731531132594682276248931884765625
the double above
0.10000000000000001942890293094023945741355419158935546875
the gap here
0.00000000000000001387778780781445675529539585113525390625

Doubles between 1 and 2: 4,503,599,627,370,496. Doubles between 0.1 and 0.2: 4,503,599,627,370,496. the same count, in an interval a tenth of the width. Not because a tenth to a fifth is one power of two, which it is not: 0.1 sits in one and 0.2 in the next. It is because the stored 0.2 is exactly twice the stored 0.1, and doubling a value adds one to the exponent field, which moves the bit pattern by exactly two to the fifty-second. Any two values a factor of two apart have that many representable numbers between them.

3 Two roundings, and which one did the damage

The sum below is computed in whole numbers from the two stored values, so it is what the machine would return if it had unlimited room.

first holds
0.1000000000000000055511151231257827021181583404541015625
second holds
0.200000000000000011102230246251565404236316680908203125
their exact sum
0.3000000000000000166533453693773481063544750213623046875
what you get
0.3000000000000000444089209850062616169452667236328125

which holds
0.299999999999999988897769753748434595763683319091796875
and they are
not equal

the addition was exact: it returned the sum of the stored values with nothing to round

4 Where the counting stops

Whole numbers are exact until the gap between neighbours grows past one. Slide up to that edge and one step over it.

the number
9,007,199,254,740,992
one more
9,007,199,254,740,992
the gap here
2

these are the same number: 2^53 + 1 is not representable

Why the addition is not the culprit

The usual telling stops one step early. It says 0.1 is not exactly representable, which is true, and then leaves you thinking the addition went wrong.

Watch what actually happens. Reading the text 0.1 rounds it to the nearest double, which is a little above a tenth. Reading 0.2 rounds it to a little above a fifth. Those two stored values are now fixed. Add them the way a mathematician would, in whole numbers with nothing discarded, which is what the third readout above does, and the answer is already larger than three tenths. That is before the machine has added anything.

Then the machine adds, and it does the one thing the standard promises: it returns the representable value nearest the true sum of its operands. That is called correct rounding, and it is not the same as exact. It rounds once, in the direction the arithmetic demands, and lands on a double further from three tenths than the true sum was. Two roundings, and the second is the small one.

So the machine is not sloppy and the addition is not broken. The answer is wrong because the question was asked in a notation the machine does not have. That is the same shape as a scratch that is not damage until it reaches a decoder, and the opposite shape to a check that is exact right up to a boundary and useless one step past it.

Why the gaps are where they are

A double is a whole number times a power of two. Between any power of two and the next one, the exponent is fixed and only the fifty-two fraction bits vary, so every such interval holds the same count: two to the fifty-second, whatever its width. That is why one and two are as finely divided as a tenth and a fifth, and why the absolute gap grows as the numbers do until, somewhere above two to the fifty-third, it passes one and consecutive whole numbers stop being distinguishable.

What is real here, and what is not

There is no simulation on this page, which is unusual here

Every other machine on this site models something and says where the model stops. This one has nothing to model. A JavaScript Number is a double: ECMA-262 defines the Number value as a "double-precision 64-bit binary format IEEE 754-2019 value", so the numbers above are the browser's own, and the bits are read straight out of them with a DataView. The arithmetic the page shows you is the arithmetic it is running.

The exact decimals are exact, and that is a fact about the format

A double is an integer significand times a power of two, so for a negative exponent its value is that integer divided by a power of two, which always terminates in decimal. The page computes those digits with BigInt: multiply by five to the same power and place the point. Nothing here uses toFixed or toPrecision, because both round, and a rounded digit in a readout about rounding would be the one thing this page must not do.

The exact sum is computed in whole numbers, not in floats

Adding the two stored values with the machine's own addition would round, which is the thing being measured. So the sum is done by shifting both significands to a common exponent and adding the integers, in BigInt. That result has no rounding in it at all, and the difference between it and what the machine returns is the rounding, isolated.

The counts are counted, not derived from a formula

The claim that an interval a tenth of the width holds the same number of doubles would be worth nothing as an assertion. Adjacent doubles are adjacent integers when their bit patterns are read as unsigned, for finite positive values, so the count between two of them is a subtraction on those patterns. The page does that subtraction rather than printing two to the fifty-second and hoping.

This is binary64 only, and only the default rounding mode

The standard defines binary32, binary128, decimal formats, and five rounding modes. A browser gives you one of each: binary64, rounding to nearest with ties to even. Everything above is true of that one and is not claimed of the others. Subnormals are handled in the decoder here but there is no control that reaches them.

Nothing on this page dates from 1985

The date is the standard's approval. The format was in silicon before that, in the Intel 8087 of 1980, which implemented a draft; the committee ran from 1977 and the proposal it settled on came from Kahan, Coonen and Stone in 1978. No hardware is simulated here and none of the arithmetic is period. What is from 1985 is the agreement that this is what a number means.

Sources