ahmedgu ahmedgu - 3 months ago 24
C Question

Floating point representation (using bitwise operators)

This is a solution to a problem to print the representation of a floating point (i.e : x = (−1)^sign · (1.m 22 m 21 m 20 . . . m 0 ) · 2^(e −bias) ) and I haven't understood some things in it :
1) The use of the union, why ?
2) MANTISSA_MASK and EXPONENET_MASK, what are they for ?
3) the use of & in here :

uint32_t exponent = ( t.bits >> MANTISSA_WIDTH ) & EXPONENT_MASK;
uint32_t mantissa = ( t.bits & MANTISSA_MASK );


Here's the code :

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>

#define ABSOLUTE_WIDTH 31
#define MANTISSA_WIDTH 23
#define EXPONENT_WIDTH 8
#define EXPONENT_MASK 0xffu
#define MANTISSA_MASK 0x007fffffu
#define EXPONENT_BIAS 127

union float_bits {
float f;
uint32_t bits;
};

void print_float( FILE *output, float f ) {
union float_bits t; t.f = f;

uint32_t sign_bit = ( t.bits >> ABSOLUTE_WIDTH );
uint32_t exponent = ( t.bits >> MANTISSA_WIDTH ) & EXPONENT_MASK;
uint32_t mantissa = ( t.bits & MANTISSA_MASK );

if( sign_bit != 0 ) {
fprintf( output, "-" );
}

if( exponent > 2 * EXPONENT_BIAS ) {
fprintf( output, "Inf\n" ); /* Infinity */
return;
} else if( exponent == 0 ) {
fprintf( output, "0." ); /* Zero or Denormal */
exponent = ( mantissa != 0 ) ? exponent + 1 : exponent;
} else {
fprintf( output, "1." ); /* Usual */
}

for( int k = MANTISSA_WIDTH - 1; k >= 0; --k ) {
fprintf( output, "%d", ( mantissa >> k ) & 1 );
}

if( exponent != 0 || mantissa != 0 ) {
fprintf( output, " * 2^%d\n", (int) ( exponent - EXPONENT_BIAS ) );
}
}

int main() {
FILE *input = fopen( "floating.in", "r" ),
*output = fopen( "floating.out", "w" );

size_t N; float f;
fscanf( input, "%zu", &N );

for( size_t i = 0; i < N; ++i ) {
fscanf( input, "%f", &f );
print_float( output, f );
}

fclose( input );
fclose( output );
return 0;
}

Answer

1) The use of the union, why ?

The bit operators are available for integral types only. You cannot convert the floating point number to an integer for obvious reasons. But a union locates the memory of the components overlapping. So by writing into the floating point component and then reading the integral component returns a integral representation of the floating point number. To make that clear: This is not the integral value of the floating point number. Using it as an integral number in calculations will give unexpected results. But you can access the bits of the integral number as it would be the bits of the floating point number.

2) MANTISSA_MASK and EXPONENET_MASK, what are they for?

Floating point numbers are represented by a number of bits specifying the mantissa (the digit string) and by an exponent part representing the "location" of the digits. After "conversion" of the floating point number into an integral type, this two parts are mixed in the integral value. MANTISSA_MASK and EXPONENT_MASK (you have a typo in your Q) masks out that parts. MANTISSA_BITS moves the exponent to the right place.

3) the use of & in here:

It is the bit and operator that masks out the bits.

Let's have an – completely virtual – example:

From your code you have 23 bits of mantissa and 8 bits of exponent. One bit of the 32 bits is reserved for the sign. Let's have a number:

00000001000010011010011010101010

Having 1 sign bit, 8 exponent bits and 23 mantissa bits you can read it like this

0 00100010 00010011010011010101010
s exponent --------mantissa-------

To get the mantissa you use a mask that only has the mantissa bits set:

0 00000000 11111111111111111111111

When you bit-and it, only bits that are 1 in both operands are 1, every other bit is 0:

0 00100010 00010011010011010101010 A
0 00000000 11111111111111111111111 B
- -------- -----------------------
0 00000000 00010011010011010101010 A&B

The mantissa is isolated from the exponent (and now a real integer value representing the mantissa.

To get the exponent, you first shift right the whole word so that the exponent starts from bit 0 (right most):

0 00100010 00010011010011010101010 
00000000000000000000000 0 00100010 >> 23 (mantissa bist)

To isolate the exponent from the sign bit, you have to bit-and it again:

00000000000000000000000 0 00100010 A
00000000000000000000000 0 11111111 B
------------------------------------
00000000000000000000000 0 00100010 A&B

Et voíla.

Comments