C & Networking · tool

IEEE 754 converter: how a float and a double store their bits

A clickable IEEE 754 converter for 32-bit float and 64-bit double, followed by the layout itself: the bias, a hand-decoded -6.25, subnormals, NaN, the real value behind 0.1 and C code that prints the bits.

LineC & Networking
Read9 min · 9 stops
DatePublished Nov 05, 2026
Tested withgcc 14.4.0 on Debian 13.7 (glibc 2.41, Docker image gcc:14), -std=c11 -O2; Python 3.12.3 (decimal, struct) on Ubuntu 24.04.4 LTS
Glass facade with a grid of windows, some lit like bits (AI-generated image)
AI-generated image
Guide + tool9 stations on the C line

How to use the IEEE 754 converter

Type a decimal value and the IEEE 754 converter shows the 32 bits of the float that stores it, split into sign, exponent and mantissa. Click any bit to flip it and watch the decimal and hex values change. The toggle switches to the 64-bit double. Besides working as a float to binary converter, it reads in the other direction too: paste a hex pattern such as 3DCCCCCD and the number comes back.

IEEE 754 converter

signexponentmantissaclick a bit to flip it
Stored value (exact)
Sign
Exponent
Mantissa
Class
Rounding error

IEEE 754 single precision: the float layout

IEEE 754 floating point stores a number as (-1)^sign × 1.mantissa × 2^(exponent - bias). The standard calls the 32-bit format binary32, better known as IEEE 754 single precision; C calls it float on every mainstream platform. Most significant bit first:

fieldbitspositionmeaning
sign1bit 310 = positive, 1 = negative
exponent8bits 30 to 23stored value minus 127 = power of two
mantissa (fraction)23bits 22 to 0the bits after the implicit leading 1

The exponent field is unsigned. Subtracting the bias of 127 turns its range 1 to 254 into powers from -126 to +127; the values 0 and 255 are reserved for special cases. The leading 1 of the significand is not stored, which buys one extra bit. A normal float therefore has 24 significant bits, and every integer up to 2^24 = 16777216 fits exactly.

Decoding -6.25 by hand

Take the bit pattern 0xC0C80000, which is what the IEEE 754 converter shows for its default value, -6.25:

output
C0C80000 = 1 10000001 10010000000000000000000
           s exponent mantissa
  1. Sign bit 1: the number is negative.
  2. Exponent 10000001 = 129. Minus the bias: 129 - 127 = 2.
  3. Mantissa 1001 followed by zeros = 0x480000. As a fraction of 2^23 that is 4718592 / 8388608 = 0.5625. With the implicit 1 in front: 1.5625.
  4. Value: -1.5625 × 2^2 = -6.25.

The double layout and how float differs from double

Compared with IEEE 754 single precision, a double (binary64) uses the same scheme with wider fields: 1 sign bit, 11 exponent bits with a bias of 1023, and 52 mantissa bits. The same -6.25 becomes 0xC019000000000000: the exponent field holds 2 + 1023 = 1025, and the mantissa again starts with 1001.

Propertyfloat (binary32)double (binary64)
sign / exponent / mantissa1 / 8 / 231 / 11 / 52
bias1271023
largest finite value3.4028234663852886e381.7976931348623157e308
smallest normal value2^-126 ≈ 1.1754944e-382^-1022 ≈ 2.2250739e-308
smallest subnormal value2^-149 ≈ 1.4e-452^-1074 ≈ 4.9e-324
machine epsilon2^-23 ≈ 1.1920929e-072^-52 ≈ 2.220446e-16
exact integers up to2^24 = 167772162^53 = 9007199254740992

The Wikipedia article on IEEE 754 lists the other interchange formats, including the 16-bit half and the 128-bit quad.

Antique abacus on a glass desk in front of city lights (AI-generated image)
AI-generated image

Zero, subnormals, infinity and NaN

The two reserved exponent values carry everything that is not an ordinary number.

  • Exponent all zeros, mantissa zero: zero. Because the sign bit is still there, there are two of them: +0.0 is 0x00000000 and -0.0 is 0x80000000. They compare equal with ==, but 1.0f / -0.0f gives negative infinity.
  • Exponent all zeros, mantissa non-zero: a subnormal, also called a denormal number. The implicit bit becomes 0 and the power is fixed at -126, so precision fades out gradually towards zero instead of dropping off a cliff at 2^-126. The smallest one, 0x00000001, is 2^-149.
  • Exponent all ones, mantissa zero: plus or minus infinity, 0x7F800000 and 0xFF800000.
  • Exponent all ones, mantissa non-zero: NaN. The top mantissa bit separates a quiet NaN (bit set, propagates silently through arithmetic) from a signaling NaN (bit clear, meant to raise the invalid-operation exception when used). glibc's NAN macro is the quiet 0x7FC00000.

Why 0.1 is not exact

Why 0.1 is not exact comes down to IEEE 754 floating point counting in powers of two. The fraction 1/10 has an infinite binary expansion, 0.000110011001100…, with 1100 repeating forever. A float keeps 24 significant bits of it and rounds the rest, so the stored pattern ends in …1101 instead of …1100: the round-up makes the stored value slightly larger than 0.1. Python's decimal module prints the exact value of a binary float without any rounding of its own:

shell
$ python3 -c "from decimal import Decimal; import struct; print(Decimal(0.1)); print(Decimal(struct.unpack('f', struct.pack('f', 0.1))[0]))"
0.1000000000000000055511151231257827021181583404541015625
0.100000001490116119384765625

The first line is the double nearest to 0.1, the second the float 0x3DCCCCCD, the same pattern the IEEE 754 converter shows for 0.1. The float is off by about 1.49e-9, the double by about 5.55e-18. The same rounding hits 0.2, 0.3 and every decimal fraction whose denominator is not a power of two, while 0.5, 0.25 or 6.25 convert without any error. That is why 0.1 is not exact in either format, only closer in the double.

Rounding to nearest even and machine epsilon

When a result falls between two representable values, the default rounding mode picks the nearer one. On an exact tie it picks the one whose last mantissa bit is 0, the "even" one. The integers above 2^24 show this directly in float: 16777217 lies exactly halfway between 16777216 and 16777218 and rounds down to 16777216, while 16777219 lies halfway between 16777218 and 16777220 and rounds up to 16777220. Ties go both ways, so errors do not drift in one direction.

Machine epsilon is the spacing of representable numbers just above 1.0: 2^-23 for float, 2^-52 for double. Above 2.0 the spacing doubles, above 4.0 it doubles again. A fixed tolerance such as 1e-6 is far too coarse near zero and smaller than one step for floats above 16, which is why the comparison below scales epsilon by the magnitude.

Precision micrometer measuring a small metal cylinder (AI-generated image)
AI-generated image

Comparing float values in C

compare.c
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>

static bool nearly_equal(float a, float b, float eps)
{
    if (a == b) return true;                      /* also covers +0 == -0 */
    return fabsf(a - b) <= eps * fmaxf(fabsf(a), fabsf(b));
}

int main(void)
{
    float sum = 0.0f;
    for (int i = 0; i < 10; i++)
        sum += 0.1f;

    printf("sum  = %.9g (%a)\n", sum, sum);
    printf("sum == 1.0f          : %d\n", sum == 1.0f);
    printf("nearly_equal, 1 eps  : %d\n", nearly_equal(sum, 1.0f, FLT_EPSILON));
    printf("nearly_equal, 4 eps  : %d\n", nearly_equal(sum, 1.0f, 4 * FLT_EPSILON));
    printf("0.1 + 0.2 == 0.3     : %d (double)\n", 0.1 + 0.2 == 0.3);
    printf("0.1f + 0.2f == 0.3f  : %d (float)\n", 0.1f + 0.2f == 0.3f);
    printf("FLT_EPSILON = %a = %.9g\n", FLT_EPSILON, FLT_EPSILON);
    printf("DBL_EPSILON = %a = %.17g\n", DBL_EPSILON, DBL_EPSILON);
    printf("16777217.0f = %.1f\n", 16777217.0f);
    return 0;
}
shell
$ gcc -std=c11 -Wall -Wextra -O2 compare.c -o compare -lm && ./compare
sum  = 1.00000012 (0x1.000002p+0)
sum == 1.0f          : 0
nearly_equal, 1 eps  : 1
nearly_equal, 4 eps  : 1
0.1 + 0.2 == 0.3     : 0 (double)
0.1f + 0.2f == 0.3f  : 1 (float)
FLT_EPSILON = 0x1p-23 = 1.1920929e-07
DBL_EPSILON = 0x1p-52 = 2.2204460492503131e-16
16777217.0f = 16777216.0

Ten additions of 0.1f end exactly one step above 1.0, 0x1.000002p+0, and the relative test accepts that.

How many epsilons to allow depends on how many operations produced the value. One rounding step means one epsilon; a long loop needs more. Near zero a relative test fails by design, since fmaxf of two tiny numbers shrinks the tolerance to almost nothing, so results that are expected to be zero need an absolute floor as well.

Printing the bits of a float in C

A float to binary converter in C takes a dozen lines, and only one of them is delicate. Casting (uint32_t *)&f breaks the strict aliasing rule and is undefined behavior. Copying with memcpy is defined, and gcc compiles it to a single register move at -O2.

floatbits.c
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

static void show(const char *label, float f)
{
    uint32_t u;
    memcpy(&u, &f, sizeof u);          /* no pointer casts, no UB */

    unsigned sign = u >> 31;
    unsigned exp  = (u >> 23) & 0xFF;
    unsigned man  = u & 0x7FFFFF;

    printf("%-12s 0x%08" PRIX32 "  ", label, u);
    for (int i = 31; i >= 0; i--) {
        putchar((u >> i) & 1 ? '1' : '0');
        if (i == 31 || i == 23) putchar(' ');
    }
    printf("  s=%u e=%3u m=0x%06X  %a\n", sign, exp, man, f);
}

int main(void)
{
    show("-6.25f", -6.25f);
    show("0.1f", 0.1f);
    show("1.0f", 1.0f);
    show("-0.0f", -0.0f);
    show("FLT_TRUE_MIN", 0x1p-149f);
    show("INFINITY", INFINITY);
    show("NAN", NAN);
    return 0;
}
shell
$ gcc -std=c11 -Wall -Wextra -O2 floatbits.c -o floatbits && ./floatbits
-6.25f       0xC0C80000  1 10000001 10010000000000000000000  s=1 e=129 m=0x480000  -0x1.9p+2
0.1f         0x3DCCCCCD  0 01111011 10011001100110011001101  s=0 e=123 m=0x4CCCCD  0x1.99999ap-4
1.0f         0x3F800000  0 01111111 00000000000000000000000  s=0 e=127 m=0x000000  0x1p+0
-0.0f        0x80000000  1 00000000 00000000000000000000000  s=1 e=  0 m=0x000000  -0x0p+0
FLT_TRUE_MIN 0x00000001  0 00000000 00000000000000000000001  s=0 e=  0 m=0x000001  0x1p-149
INFINITY     0x7F800000  0 11111111 00000000000000000000000  s=0 e=255 m=0x000000  inf
NAN          0x7FC00000  0 11111111 10000000000000000000000  s=0 e=255 m=0x400000  nan

The last column is printf("%a"), the C99 hex float format. It prints the significand in hex and the power of two in decimal, so 0x1.99999ap-4 is the mantissa 0x4CCCCD shifted left by one bit with the implicit 1 in front, and -0x1.9p+2 is -1.5625 × 2^2. Unlike %f or %.9g, %a never rounds: a value printed this way and read back with strtof gives the same bits. The literal 0x1p-149f in the source uses the same notation.

Sending float and double values over a network

The bits of a float in memory follow the byte order of the CPU, so the same 0x3DCCCCCD leaves an x86 machine as CD CC CC 3D. The byte order converter shows that layout for any value, and the socket programming guide covers htonl() for the integer case. For floats there is no standard network-order function. The portable way is to copy the bits into a uint32_t as above and send that integer in network byte order.

Beej's example pack2.c takes another route: its pack754() rebuilds the bit pattern arithmetically from sign, shift count and fraction. Compiled with gcc 14 and fed -6.25f, 0.1f and 3.14159f, it produced the same patterns as memcpy (0xC0C80000, 0x3DCCCCCD, 0x40490FD0). For -0.0f it returned 0x00000000 because the function treats zero as a special case before it looks at the sign, so the negative zero arrives as a positive one. Infinity is worse: the normalizing loop while(fnorm >= 2.0) divides infinity by two forever, and a test run had to be stopped by timeout after five seconds. The receiver cannot tell a sender stuck in that loop from a dead connection.

Last stops · FAQ

Questions people ask

How is a decimal number converted to IEEE 754 by hand?

Write the absolute value in binary, shift the point until one 1 is left of it, and count the shifts: that count plus the bias (127 for float, 1023 for double) is the exponent field. The bits after the point fill the mantissa, padded with zeros or rounded to fit. For -6.25 that gives 1 10000001 1001000..., which is 0xC0C80000.

What is the difference between float and double in IEEE 754?

A float (binary32) has 1 sign bit, 8 exponent bits and 23 mantissa bits, with a bias of 127. A double (binary64) has 1, 11 and 52 bits, with a bias of 1023. The double therefore carries about 15 to 17 significant decimal digits against roughly 6 to 9 for float, and its range reaches about 1.8e308 instead of 3.4e38.

Why does 0.1 + 0.2 not equal 0.3 in C?

None of the three decimals has a finite binary expansion, so each is rounded to the nearest double before the addition. The sum lands on 0.30000000000000004, one step above the double nearest to 0.3, and == compares bits exactly. In float precision the rounding errors happen to cancel and 0.1f + 0.2f == 0.3f is true, which is luck rather than a rule.

What is machine epsilon for float and double?

It is the distance from 1.0 to the next value the format can hold, published in <float.h> as FLT_EPSILON (0x1p-23) and DBL_EPSILON (0x1p-52). It is often mistaken for the smallest positive number, which is far smaller: FLT_TRUE_MIN, 2^-149. Since the spacing doubles with each power of two, epsilon only makes sense multiplied by the size of the values being compared.

What is a subnormal (denormal) number?

A value with an all-zero exponent field and a non-zero mantissa, filling the range below 2^-126 with fewer significant bits. Compiler flags can make them disappear. Built with gcc -O2, a program computing 0x1p-126f * 0.5f printed the subnormal 0x1p-127; built with gcc -O2 -ffast-math, the same source printed 0x0p+0, because that flag allows gcc to flush subnormal results to zero.

Does the IEEE 754 converter work offline?

Yes. The converter is plain JavaScript that runs in the browser tab and sends nothing to a server. Once the page has loaded, it works without a network connection. The figures shown match what memcpy into a uint32_t or uint64_t prints in C on any machine that uses IEEE 754 binary formats.

Next stop

Where this line continues