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
- 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:
| field | bits | position | meaning |
|---|---|---|---|
| sign | 1 | bit 31 | 0 = positive, 1 = negative |
| exponent | 8 | bits 30 to 23 | stored value minus 127 = power of two |
| mantissa (fraction) | 23 | bits 22 to 0 | the 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:
C0C80000 = 1 10000001 10010000000000000000000
s exponent mantissa- Sign bit 1: the number is negative.
- Exponent
10000001= 129. Minus the bias: 129 - 127 = 2. - Mantissa
1001followed by zeros = 0x480000. As a fraction of 2^23 that is 4718592 / 8388608 = 0.5625. With the implicit 1 in front: 1.5625. - 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.
| Property | float (binary32) | double (binary64) |
|---|---|---|
| sign / exponent / mantissa | 1 / 8 / 23 | 1 / 11 / 52 |
| bias | 127 | 1023 |
| largest finite value | 3.4028234663852886e38 | 1.7976931348623157e308 |
| smallest normal value | 2^-126 ≈ 1.1754944e-38 | 2^-1022 ≈ 2.2250739e-308 |
| smallest subnormal value | 2^-149 ≈ 1.4e-45 | 2^-1074 ≈ 4.9e-324 |
| machine epsilon | 2^-23 ≈ 1.1920929e-07 | 2^-52 ≈ 2.220446e-16 |
| exact integers up to | 2^24 = 16777216 | 2^53 = 9007199254740992 |
The Wikipedia article on IEEE 754 lists the other interchange formats, including the 16-bit half and the 128-bit quad.

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.0is0x00000000and-0.0is0x80000000. They compare equal with==, but1.0f / -0.0fgives 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,
0x7F800000and0xFF800000. - 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
NANmacro is the quiet0x7FC00000.
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:
$ 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.100000001490116119384765625The 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.

Comparing float values in 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;
}$ 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.0Ten 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.
#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;
}$ 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 nanThe 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.
