IEEE 754 float32 values in [0, 1] – live WebGPU

Every glowing dot is a real single-precision float generated with the exact hardware formula:

value = (1 + mantissa) × 2exponent
exponent runs from –126 to 0  |  mantissa is a fraction in [0, 1)

Linear scale = true number line → almost all values sit extremely close to zero (the bright bar).
Log scale = position ∝ log₁₀(value) → shows that floats exist at every order of magnitude from ~10⁻³⁸ up to 1.

WebGPU: instance buffer holds every float + phase. Vertex shader maps value → screen X (linear/log) + bobbing. Fragment shader makes soft circles. Additive blending shows density.

Current: Log scale
WebGPU ready · 0 / 0 points
0
1
10⁻³⁰
10⁻²⁰
10⁻¹⁰
10⁻⁵
0.01
0.1

Scientific Notation & Computing

In scientific notation a number is written as a significand (also called the mantissa) multiplied by a power of ten (or two in binary).

Example: 5.3266 × 10³
→ The part 5.3266 is the significand (mantissa).
→ The part 10³ is the exponent that scales the value.

Value scaling: The significand carries the precision (the significant digits), while the exponent controls the magnitude (how large or small the number is). Together they let computers represent both very large and very tiny numbers with a fixed number of bits.

In IEEE 754 binary floating-point the significand is stored in normalized form (usually with a leading 1 that is implicit). That is exactly why the formula you see in the visualization is written as (1 + mantissa) × 2exponent.

Color Meaning

Cyan / blue points (left side) = very small floating-point numbers (close to 0).

Pink / magenta points (right side) = floating-point numbers that are relatively close to 1.0 (for example 0.5, 0.75, 0.9, 0.99, etc.).

In Linear scale you see very few pink points because almost all possible float32 values are packed tightly near zero.
In Log scale the pink points become more visible because the scale spreads out the larger magnitudes.

Why This Design Was Intentional (History)

The dense cluster of values near zero that you see on the Linear scale is not an accident — it was a deliberate design choice made by the IEEE 754 committee (led by William Kahan) in the late 1970s / early 1980s.

Approach Spacing of numbers Dynamic range Result
Fixed-point Uniform Very limited Not practical for scientific computing
Floating-point (IEEE 754) Denser near zero Extremely wide What we use today

The designers prioritized relative accuracy and a huge dynamic range over uniform spacing.

Exact Number of Representable Values

Yes — there is a fixed, deterministic number of values that IEEE 754 float32 can represent.

Category Count Notes
Total possible bit patterns 4,294,967,296 (2³²) Every possible 32-bit combination
Distinct finite real numbers 4,278,190,079 Counting +0 and –0 as the same value
Finite bit patterns (incl. ±0) 4,278,190,080 Treating +0 and –0 as distinct
+Infinity and –Infinity 2
NaN payloads 16,777,214 Many different NaN bit patterns

Positive finite numbers (including +0): 2,139,095,040
Negative finite numbers (including –0): 2,139,095,040

Of the positive numbers:
• 1 zero
• 8,388,607 denormalized (subnormal) numbers
• 2,130,706,432 normal numbers (254 exponents × 2²³ significands)

In the closed interval [0, 1] there are exactly 1,056,964,610 distinct non-negative float32 values (this is what the visualization is sampling from).

NaN Payloads – The Hidden 16 Million Patterns

When the 8 exponent bits of a float32 are all set to 1 and the 23-bit fraction is not zero, the value is a NaN (Not a Number). This leaves 23 bits that can hold arbitrary data — the NaN payload.

There are \(2^{23} - 1 = 8{,}388{,}607\) possible payloads for positive NaNs and the same number for negative NaNs, giving a total of 16,777,214 different NaN bit patterns.

Original Intent (William Kahan)

One of the lead architects of IEEE 754, William Kahan, deliberately left these bits available so that software (or hardware) could record why or where a NaN was created. The idea was “retrospective diagnosis”: a long calculation could finish, and then you could inspect the payload to see what went wrong.

The Most Successful Use: NaN-Boxing

The most brilliant and widely deployed use of NaN payloads is NaN-boxing (also called NaN-tagging).

In dynamically typed languages, every value normally needs both its data and a type tag. NaN-boxing solves this elegantly:

Because modern CPUs only use the lower 48 bits of a 64-bit pointer, the 51–52 available payload bits in a double are more than enough to store a pointer plus type information. The result: every value in the language fits in a single 64-bit machine register.

Major systems that use NaN-boxing:

This technique is one of the reasons modern JavaScript engines can be surprisingly fast despite being dynamically typed.

Other Real-World Uses

While the original diagnostic vision of Kahan was only partially realized in mainstream software, the creative reuse of NaN space for NaN-boxing turned out to be far more impactful than anyone expected in the 1980s.

Where IEEE 754 Appears in Real Systems

1. CPU & GPU Hardware
Modern processors contain dedicated Floating-Point Units (FPUs). The silicon is designed to implement the exact IEEE 754 rules (bit layout, rounding, NaN, Infinity, denormals). Instruction sets such as x86 SSE/AVX, ARM NEON, and RISC-V all have special floating-point instructions that follow the standard.

2. Compilers
Compilers must respect IEEE 754 when generating code. They cannot freely rearrange floating-point operations the way they can with integers. Flags like -ffast-math deliberately break strict compliance for speed.

3. Software
Almost all numerical software depends on it: games, graphics, machine learning, scientific simulations, finance, physics engines, etc.

Why This Knowledge Matters + Concrete Examples

Here are real situations where understanding the non-uniform density and other IEEE 754 behaviors prevents bugs or performance problems:

Example 1 – Random numbers in [0, 1)

// Bad: biased let r = Math.random(); // Better for uniform float distribution let r = (Math.random() * 0x1000000) / 0x1000000;

Because of the density near zero, a naïve 32-bit integer divided by 2³² produces a biased distribution.

Example 2 – Comparing floating-point numbers

// Dangerous if (a === b) { ... } // Better if (Math.abs(a - b) < 1e-6) { ... } // or use a relative epsilon

Two calculations that should be equal often differ by a few units in the last place (ULPs).

Example 3 – Denormal performance trap

// Can become extremely slow on many CPUs let x = 1e-40; for (let i = 0; i < 1000000; i++) { x = x * 0.5; // eventually hits denormals → big slowdown }

Denormalized numbers (the extra values very close to zero) are handled in hardware much more slowly on most CPUs.

Example 4 – Catastrophic cancellation

let a = 1.0000001; let b = 1.0000000; let diff = a - b; // result has almost no precision left

Subtracting two nearly equal numbers destroys significant digits. This is why many numerical algorithms are carefully rewritten to avoid it.

Understanding the density near zero, the limited number of significand bits, and special values (NaN, Infinity, denormals) helps you write more robust and faster numerical code.

References & Further Reading

  1. Wikipedia – NaN (Good overview of quiet vs signaling NaNs and payloads)
  2. The Secret Life of NaN by Annie Cherkaev (Excellent deep dive into NaN-boxing)
  3. Stack Overflow – What uses do floating point NaN payloads have?
  4. NaN Boxing explained (practical C tutorial)
  5. nanbox – A clean C implementation of NaN-boxing
  6. IEEE 754-2019 standard (official document) – Section on NaN payload operations