Displaying the Raw Fields of a Floating-Point Number

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.” Also, recasting a double as an integer violates strict aliasing, which may or may not be enforced by your compiler.)

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.

Dingbat

2 Responses to “Displaying the Raw Fields of a Floating-Point Number”

  1. anon1 Says:

    Hi, I don’t understand, why in function print_raw_double_binary you write:
    1. unsigned long long *double_as_int = (unsigned long long *)&d;
    and why later in this function you use:
    2. if ((*double_as_int >> (63-i)) & 1)
    what does it mean? Can we use different method to achieve the same results?

    Great article, btw! Thank you.

  2. Rick Regan Says:

    anon1,

    I am “aliasing” the double to treat it as a 64-bit integer so I can use bitwise operations on it. For the line you cited, which is in a loop, the effect is to iterate through all the bits from right to left.

    You can use a union of a double and an unsigned long long instead, which is probably cleaner than aliasing.

Leave a Comment

(To reduce spam, cookies must be enabled)


css.php