How to Format Turn Floating Point into Exponent Using Constants in C

772 Views Asked by At

So I'm given

const int mask = 0x7F800000
const int shift = 23
const int bias = 127

int exp (float number)  {
   unsigned int u = *(unsigned int*)&number;
   int field = 0;

Honestly, I'm just confused how to format properly to get what I want. Essentially, client inputs a float and then u is the unsigned int that has the same bit pattern as the float.

I know I need to first mask everything except the exponent variables in a number. So I'd assume it's

field = (u & mask)

That should presumably mask the bit pattern except for the part we want - the exponent.

Then step two would be to shift it by 23, so its not surrounded by all the zeros...this is where I get confused.

field = (u & mask) >> shift ???

See, I'm kind of lost. Maybe that's right. We are shifting by 23 to the right regardless.

Then what's last is to take the bias into account. You want to return the actual exponent, not the bits. And the formula to do that is exponent bits = (exp bit pattern) - bias...So then I try this

field = ((u & mask) >> shift) - bias;
return(field);

And I just can't get the right answer. Firstly, please go easy on my formatting. I'm new to C and this is a beginner project. I know it's not good as is. But, can someone assist me in understanding the formatting to make these steps work? Or help with what I'm missing? I'm just looking for assistance on logic.

2

There are 2 best solutions below

2
On

Three methods:

  1. Safest
int exp (float number)  {
   unsigned int u;
   memcpy(&u, &numberm sizeof(u));
  1. Safe in C
int exp (float number)  {
   union 
   {
       unsigned int u;
       float f;
   }fu = {.f = number};

then you can use fu.u

  1. Unsafe (pointer puning)
int exp (float number)  {
   unsigned int *u = (void *)&number;

Having unsigned value u

you can for example:

#define SIGN (1U << 31)
#define EXP  (0xff << 23)

unsigned sign = !!(u & SIGN)
unsigned exp = (u & EXP) >> 23;
unsigned mant = u & ~(SIGN | EXP);
5
On

Beware of endianness issues - for an IEEE-754-like layout, we can have the following:

     A        A+1      A+2      A+3        Big-endian addressing
    +--------+--------+--------+--------+
    |seeeeeee|efffffff|ffffffff|ffffffff|
    +--------+--------+--------+--------+
     A+3      A+2      A+1      A          Little-endian addressing

So on a little-endian system like x86, your value will be formatted as

+--------+--------+-------+--------+
|ffffffff|ffffffff|effffff|seeeeeee|
+--------+--------+-------+--------+

where the first byte will be the lowest 8 bits of the significand, followed by the middle 8 bits of the significand, followed by the lowest exponent bit, followed by the high 7 bits of the significand, followed by the sign bit, followed by the high 7 bits of the exponent.

Also, there is no guarantee that the bit pattern of a float value will fit in an unsigned int1. It's better to treat the float value as an array of unsigned char:

union f32 {
  float f;
  unsigned char c[sizeof f];
};

You can assign your value to the f member:

union f32 myval = {.f = 1.234};

and then examine it via the c member:

printf( "sign bit is %d\n", (f32.c[3] & 0x80) >> 7 );

  1. An unsigned int is only required to store values in at least the range [0..65535], meaning it can be as little as 16 bits wide; on almost any system built after 2000 it will be 32 bits wide, but it's not something you can count on being universally true.