Floating-Point Explorer

The number you typed, and the one you got.

  • Exact stored value
  • 16, 32 and 64 bit
  • Neighbours and ULP
A wooden ruler-like bar with brass sliders in three groups lying on a desk beside a small magnifying glass in soft daylight.

The guide

Exact about what it stores, approximate about what you meant: how binary floating point really works

A float is a precise binary fraction with a fixed number of bits, and every famous surprise follows from rounding a decimal into that grid once.

  • 0.4 ulp how far the stored 0.1 sits from one tenth in binary64
  • 2 to the minus 56 the gap between neighbouring binary64 values near 0.1
  • 17 digits always enough to bring a binary64 value back from text
  • 1985 year the first IEEE 754 standard was published
  • 1 + 11 + 52 bits sign, exponent and fraction fields of binary64
Read the full guide Fold the guide 8 min · 17 sources

Type 0.1 into almost any programming language and it prints 0.1 straight back. That politeness hides a widely misunderstood fact. The number the machine holds is not one tenth. It is a slightly larger binary fraction, chosen by a rule.

Python’s documentation spells it out with unusual candour. The stored value of 0.1 in double precision is the fraction 3602879701896397 divided by 2 to the power 55, and written out in full it is 0.1000000000000000055511151231257827021181583404541015625. Nothing about that is vague or noisy. It is an exact number with an exact decimal expansion. It just is not the number you typed.

Why one tenth will not fit

A decimal fraction ends when its denominator divides a power of ten. A binary fraction ends only when its denominator is a power of two. One tenth has a factor of five underneath it, so in base two its digits repeat forever, the way one third does in base ten. Wikipedia’s article on the single-precision format makes the same point plainly: 0.375 is exactly 0.011 in binary, while 0.1 can only be approximated.

So the machine keeps a fixed number of binary digits and rounds the rest away. In binary32 the result is exactly 0.100000001490116119384765625, a figure the Wikipedia article on floating-point arithmetic quotes digit for digit. In binary64 the error shrinks to about 5.6 times 10 to the minus 18. It never reaches zero, because no finite string of bits can hold a factor of five.

This reverses a common intuition. Floats are often described as fuzzy, as if each one were a small cloud of uncertainty. In fact every finite float is a single rational number known to the last digit, and every one of them, however small, can be printed exactly in decimal, because a power of two in the denominator always ends. The approximation happens once, at the moment a decimal or a real result is forced into the grid. After that, the stored value is whatever it is, exactly.

Three fields and a bias

The layout comes from IEEE 754, first published in 1985 and revised in 2008 and again in 2019, according to the Wikipedia history of the standard. A binary float is a sign bit, then an exponent field, then the fraction. Binary64 uses 1 + 11 + 52 bits. Binary32 uses 1, 8 and 23. The half-precision binary16 format, increasingly common in graphics and machine learning hardware, uses 1, 5 and 10.

The exponent field is stored with an offset, called the bias, so that it can be read as an unsigned integer: 15 for binary16, 127 for binary32, 1023 for binary64. For an ordinary, normal number the value is the sign, times two raised to the stored exponent minus the bias, times a significand of 1 followed by the fraction bits. That leading 1 is never stored. Every normal number has it, so the format takes it as given and gains a bit of precision for free.

Take 0.1 in binary64 again. Its exponent field holds 1019, which is minus 4 once the bias of 1023 is removed. Its fraction is the repeating pattern 1001 1001 1001, cut off after 52 bits and rounded up at the end, which is why the hex form finishes in A rather than 9. In hex the whole pattern reads 3FB999999999999A.

Two exponent values are reserved. All zeros means zero or a subnormal number; all ones means infinity or NaN, short for not a number. The standard distinguishes quiet and signalling NaNs by the first fraction bit and recommends 1 for quiet, leaving the remaining fraction bits as a payload, as the Wikipedia article on IEEE 754 describes. JavaScript shows how loosely languages treat that payload. The ECMAScript specification says the 9,007,199,254,740,990 distinct NaN patterns of binary64 are all one value to a program, and that storing a NaN into a typed array may produce any NaN encoding the implementation chooses.

Spacing that doubles

Between two consecutive powers of two, a format has exactly as many values as its fraction has patterns, spread evenly. The gap between neighbours is called the unit in the last place, or ulp. Wikipedia gives the usual definition: if x lies between 2 to the e and 2 to the e plus one, its ulp is 2 to the power of e minus the precision plus one, with e held at the smallest normal exponent for very small numbers.

The consequence is a number line whose spacing doubles at every power of two. Near 1 in binary64 the gap above is 2 to the minus 52, the quantity JavaScript names Number.EPSILON, about 2.22 times 10 to the minus 16 according to MDN. Just below 1 the gap is half that. Near 0.1 it is 2 to the minus 56. Far out the gaps grow large enough to swallow whole integers: Wikipedia’s ulp article shows binary32 counting up by one until 16,777,216, after which adding one changes nothing, because 2 to the 24 plus one is not representable and rounds back.

Here is a worked example to reproduce. Type 0.1 in the binary64 view. The stored value exceeds one tenth by 0.0000000000000000055511151231257827021181583404541015625. The gap to either neighbour is 2 to the minus 56, so the error is exactly 0.4 ulp. The neighbours below and above print as 0.09999999999999999 and 0.10000000000000002, and a tenth sits between the stored value and the one below, closer to the stored value, which is why rounding chose it.

Subnormal numbers deal with the far end of that line. When the exponent field is zero the hidden 1 becomes a 0, and the values keep the same spacing as the smallest normal ones all the way down to zero. The article on subnormal numbers calls this gradual underflow and notes the guarantee it buys: two nearby floats always have a representable non-zero difference, so x minus y is zero only when x equals y. The same article records the price. Subnormals were the most controversial feature of the proposal that became the standard, and some processors handle them in slow software, which is why flush-to-zero modes exist.

Rounding, once, to the nearest even

Every operation in the standard behaves as if it computed the exact result and then rounded it once. The default rule for binary formats is round to nearest, with ties going to the neighbour whose last bit is even. The Wikipedia article on the standard gives the reason in one line: rounding ties to even removes the statistical bias that can creep in when similar figures are added. Three directed modes, toward zero, up and down, also exist; the same article says they were meant as an aid for checking error bounds, for instance in interval arithmetic.

Ties are rarer than they look but easy to build. The integer 9,007,199,254,740,993 sits exactly halfway between two binary64 values, and the even one wins, so it reads as 9,007,199,254,740,992. A rule that pushed ties away from zero would give the other neighbour, and would bring back exactly the bias that ties to even was chosen to remove.

Text has to cross the same boundary in both directions. Reading a decimal correctly means rounding the exact decimal value, which William Clinger’s 1990 paper showed how to do reliably. Printing means choosing digits that will read back to the same bits. The standard’s own requirement, as summarised on Wikipedia, is that 5 decimal digits always suffice for binary16, 9 for binary32 and 17 digits for binary64.

Shorter strings usually work too. Steele and White published the first algorithm for the shortest accurate output in 1990, and the ECMAScript specification now requires it: the fewest digits that read back, and among those the one closest to the true value. Python’s tutorial notes that its prompt once printed 0.10000000000000001, seventeen digits, and since version 3.1 prints just 0.1. Both are correct. Neither is the stored value.

The classic traps, and the calm rule

The famous 0.1 + 0.2 follows directly. Both operands are already rounded, their exact sum lands between two binary64 values, and rounding picks 0.30000000000000004, a different double from the one 0.3 rounds to. Wikipedia’s article on floating-point arithmetic calls 0.1 + 0.2 minus 0.3 the classical toy problem; it comes out near 5.551 times 10 to the minus 17 rather than zero.

Absorption is the next trap. When a large and a small number are added, the small one can fall below half an ulp of the large one and vanish. In binary64, 10 to the 16 plus 1 rounds straight back to 10 to the 16. Cancellation is the reverse: subtracting two nearly equal numbers is exact, yet it exposes the errors they already carried. Type 1.000000000000001 and subtract 1, and the answer is 1.1102230246251565e-15, about eleven percent away from the 10 to the minus 15 the decimals meant.

Because each step rounds, the order of operations matters, and addition is not associative. In binary64, adding 0.1 and 0.2 first and then 0.3 gives 0.6000000000000001; adding 0.2 and 0.3 first gives 0.6. Summing 0.1 ten times in a row gives 0.9999999999999999. Kahan’s compensated summation, published in 1965, carries the lost low-order part in a second variable and feeds it back, which keeps the error bound essentially independent of how many terms are added, as the Wikipedia article on the algorithm shows. On that list it returns 1.

None of this means floats are unreliable. David Goldberg’s 1991 tutorial in ACM Computing Surveys opens by noting that many people treat floating point as an esoteric subject, which is surprising for something used in almost every language and computer. The rules are strict enough to test. The checks behind this explorer show it: the exact rounding implemented here matched the platform’s own parsing, arithmetic and typed arrays on hundreds of thousands of cases, and it matched Math.fround and the newer Float16Array that MDN lists as available across current browsers since April 2025.

The tool has limits. It covers the three binary interchange formats and not the decimal ones, the 80-bit extended format or bfloat16. It shows what one rounding does, not how error grows through a long algorithm, and it cannot tell you whether a result is good enough for your purpose. That judgement needs the size of the errors you can tolerate.

What it can show is the calm rule under every surprise. A float is exact about what it stores and approximate about what you meant. Know which number you hold, and where the one rounding happened.

Sources

Every source consulted for this guide. Links checked October 17, 2026.

  1. IEEE 754 Wikipedia
  2. Double-precision floating-point format Wikipedia
  3. Single-precision floating-point format Wikipedia
  4. Half-precision floating-point format Wikipedia
  5. Subnormal number Wikipedia
  6. Unit in the last place Wikipedia
  7. Floating-point arithmetic Wikipedia
  8. Kahan summation algorithm Wikipedia
  9. What every computer scientist should know about floating-point arithmetic David Goldberg, ACM Computing Surveys 23(1), pages 5 to 48, 1991 (OpenAlex record)
  10. Pracniques: further remarks on reducing truncation errors W. Kahan, Communications of the ACM 8(1), page 40, 1965 (OpenAlex record)
  11. How to print floating-point numbers accurately Guy L. Steele and Jon L. White, ACM SIGPLAN PLDI 1990, pages 112 to 126 (OpenAlex record)
  12. How to read floating point numbers accurately William Clinger, ACM SIGPLAN PLDI 1990, pages 92 to 101 (OpenAlex record)
  13. 15. Floating-Point Arithmetic: Issues and Limitations Python documentation, Python Software Foundation
  14. ECMAScript 2027 Language Specification Ecma International, TC39
  15. Number.EPSILON MDN Web Docs, Mozilla
  16. Math.fround() MDN Web Docs, Mozilla
  17. Float16Array MDN Web Docs, Mozilla