Displaying the Raw Fields of a Floating-Point Number
Copyright © 2008-2010 Exploring Binary
http://www.exploringbinary.com/displaying-the-raw-fields-of-a-floating-point-number/
A double-precision floating-point number is represented internally as 64 bits, divided into three fields: a sign field, an exponent field, and a fraction field. You don’t need to know this to use floating-point numbers, but knowing it can help you understand them. This article shows you how to access those fields in C code, and how to print them — in binary or hex.
Treating a Floating-Point Variable as Raw Bits
To access the raw storage of a double-precision floating-point variable, you need to treat the double not as a number, but as a generic sequence of bits. One way to do this is to cast the double as an array of characters. However, this is not ideal: it exposes endianness, and it does not allow for easy access to the IEEE defined fields — fields which span byte boundaries.
A better solution is to recast the double as an integer — a 64 bit integer. This gives endian-independent, bit-level access to the IEEE representation. That is the solution I use in the C code below.
(Note: My code works on architectures — like Intel — that use the same endianness for both floating point and integer values. My code will not work with mixed-endian encoding, like that used by ARM “soft float.”)
The Code
I wrote three functions:
- print_raw_double_binary(). This function casts the double as an integer and then shifts off its bits one at a time, printing each bit as it goes. A space is added between fields to delineate them.
- parse_double(). This function isolates the three fields of the double with shifts and bit masks and turns them into integers.
- print_raw_double_hex(). This function calls parse_double() to isolate the fields and then calls printf() with the ‘%X’ format specifier to print them in hex (spaces are printed between fields).
I declared and defined these functions in files I named rawdouble.h and rawdouble.c, respectively. I also wrote a test program, rawTest.c, to test the functions on various inputs.
rawdouble.h
/***********************************************************/
/* rawdouble.h: Functions to access and print the raw */
/* contents of an IEEE double floating-point */
/* variable */
/* */
/* Rick Regan, http://www.exploringbinary.com */
/* */
/***********************************************************/
void print_raw_double_binary(double d);
void parse_double(double d,
unsigned char *sign_field,
unsigned short *exponent_field,
unsigned long long *fraction_field);
void print_raw_double_hex(double d);
rawdouble.c
/***********************************************************/
/* rawdouble.c: Functions to access and print the raw */
/* contents of an IEEE double floating-point */
/* variable */
/* */
/* Rick Regan, http://www.exploringbinary.com */
/* */
/***********************************************************/
#include "stdio.h"
#include "rawdouble.h"
void print_raw_double_binary(double d)
{
unsigned long long *double_as_int = (unsigned long long *)&d;
int i;
for (i=0; i<=63; i++)
{
if (i==1)
printf(" "); // Space after sign field
if (i==12)
printf(" "); // Space after exponent field
if ((*double_as_int >> (63-i)) & 1)
printf("1");
else
printf("0");
}
printf("\n");
}
void parse_double(double d,
unsigned char *sign_field,
unsigned short *exponent_field,
unsigned long long *fraction_field)
{
unsigned long long *double_as_int = (unsigned long long *)&d;
*sign_field = (unsigned char)(*double_as_int >> 63);
*exponent_field = (unsigned short)(*double_as_int >> 52 & 0x7FF);
*fraction_field = *double_as_int & 0x000FFFFFFFFFFFFFULL;
}
void print_raw_double_hex(double d)
{
unsigned char sign_field;
unsigned short exponent_field;
unsigned long long fraction_field;
parse_double(d,&sign_field,&exponent_field,&fraction_field);
printf("%X %X %llX\n",sign_field,exponent_field,fraction_field);
}
rawTest.c
/***********************************************************/
/* rawTest.c: Program to test raw double functions */
/* */
/* Rick Regan, http://www.exploringbinary.com */
/* */
/***********************************************************/
#include "stdio.h"
#include "assert.h"
#include "rawdouble.h"
int main(int argc, char *argv[])
{
double d;
int i;
/* Make sure unsigned long long is 8 bytes */
assert (sizeof(unsigned long long) == sizeof(double));
d = 0.5;
printf("0.5:\n");
print_raw_double_binary(d);
print_raw_double_hex(d);
printf("\n");
d = 0.1;
printf("0.1:\n");
print_raw_double_binary(d);
print_raw_double_hex(d);
printf("\n");
/* d = NaN */
printf("NaN:\n");
d = 0;
d/=d;
print_raw_double_binary(d);
print_raw_double_hex(d);
printf("\n");
/* d = +infinity */
printf("+infinity:\n");
d = 1e300;
d*=d;
print_raw_double_binary(d);
print_raw_double_hex(d);
printf("\n");
/* d = 2^-1074 */
printf("2^-1074:\n");
d = 1;
for (i=1; i<=1074; i++)
d/=2;
print_raw_double_binary(d);
print_raw_double_hex(d);
return (0);
}
Notes
- The code works for all double values, including negative numbers, not-a-number (NaN), and infinity.
- You could use a union of a double and an integer instead of type casting the double to an integer, but this would require an extra step: copying the double to the union.
- The statement assert (sizeof(unsigned long long) == sizeof(double)) in rawTest.c is there to ensure that long long integers are 64 bits on your platform. You can remove it once the assertion checks out.
Compiling and Running
I compiled and ran this code on both Windows and Linux:
- On Windows, I built a project in Visual C++ and compiled and ran it in there.
- On Linux, I compiled with “gcc rawTest.c rawdouble.c -o rawTest” and then ran with “./rawTest”.
Output
0.5: 0 01111111110 0000000000000000000000000000000000000000000000000000 0 3FE 0 0.1: 0 01111111011 1001100110011001100110011001100110011001100110011010 0 3FB 999999999999A NaN: 1 11111111111 1000000000000000000000000000000000000000000000000000 1 7FF 8000000000000 +infinity: 0 11111111111 0000000000000000000000000000000000000000000000000000 0 7FF 0 2^-1074: 0 00000000000 0000000000000000000000000000000000000000000000000001 0 0 1
Modifying to Print Floats
The code can be modified to print floats; here’s an outline of what to change:
- Use ‘float’ instead of ‘double’.
- Use ‘unsigned long’ instead of ‘unsigned long long’.
- Use the ‘%lX’ printf() format specifier instead of ‘%llX’
- Use bit masks and shift amounts appropriate for the float representation.
- In print_raw_double_binary(), or what would become print_raw_float_binary(), print a space after bit 9 instead of after bit 12.
- Rename the routines and variables to use ‘float’ instead of ‘double’.
Addendum: Printing in Binary Scientific Notation
Another way to display a floating-point number is in binary scientific notation; I wrote a C function called print_double_binsci() to do this.



Leave a Comment
(To reduce spam, cookies must be enabled)