Converting given mantissa, exponent, and sign to float?

2.3k Views Asked by At

I am given the mantissa, exponent, and sign and I have to convert it into the corresponding float. I am using 22 bits for mantissa, 9 bits for exponent, and 1 bit for the sign.

I conceptually know how to convert them into a float, first adjusting the exponent back to its place, then converting the resulting number back into a float, but I'm having trouble implementing this in C. I saw this thread, but I couldn't understand the code, and I'm not sure the answer is even right. Can anyone point me in the right direction? I need to code it in C

Edit: I've made some progress by first converting the mantissa into binary, then adjusting the decimal point of the binary, then converting the decimal-point binary back into the actual float. I based my conversion functions off these two GeekforGeek pages (one, two) But it seems like doing all these binary conversions is doing it the long and hard way. The link above apparently does it in very little steps by using the >> operators, but I don't understand exactly how that results in a float.

3

There are 3 best solutions below

0
Eric Postpischil On BEST ANSWER

Here is a program with comments explaining the decoding:

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


//  Define constants describing the floating-point encoding.
enum
{
    SignificandBits = 22,   //  Number of bits in signficand field.
    ExponentBits    =  9,   //  Number of bits in exponent field.

    ExponentMaximum = (1 << ExponentBits) - 1,
    ExponentBias    = (1 << ExponentBits-1) - 1,
};


/*  Given the contents of the sign, exponent, and significand fields that
    encode a floating-point number following IEEE-754 patterns for binary
    floating-point, return the encoded number.

    "double" is used for the return type as not all values represented by the
    sample format (9 exponent bits, 22 significand bits) will fit in a "float"
    when it is the commonly used IEEE-754 binary32 format.
*/
double DecodeCustomFloat(
    unsigned SignField, uint32_t ExponentField, uint32_t SignificandField)
{
    /*  We are given a significand field as an integer, but it is used as the
        value of a binary numeral consisting of “.” followed by the significand
        bits.  That value equals the integer divided by 2 to the power of the
        number of significand bits.  Define a constant with that value to be
        used for converting the significand field to represented value.
    */
    static const double SignificandRatio = (uint32_t) 1 << SignificandBits;

    /*  Decode the sign field:

            If the sign bit is 0, the sign is +, for which we use +1.
            If the sign bit is 1, the sign is -, for which we use -1.
    */
    double Sign = SignField ? -1. : +1.;

    //  Dispatch to handle the different categories of exponent field.
    switch (ExponentField)
    {
        /*  When the exponent field is all ones, the value represented is a
            NaN or infinity:

                If the significand field is zero, it is an infinity.
                Otherwise, it is a NaN.  In either case, the sign should be
                preserved.

            Note this is a simple demonstration implementation that does not
            preserve the bits in the significand field of a NaN -- we just
            return the generic NAN without attempting to set its significand
            bits.
        */
        case ExponentMaximum:
        {
            return Sign * (SignificandField ? NAN : INFINITY);
        }

        /*  When the exponent field is not all zeros or all ones, the value
            represented is a normal number:

                The exponent represented is ExponentField - ExponentBias, and
                the significand represented is the value given by the binary
                numeral “1.” followed by the significand bits.
        */
        default:
        {
            int    Exponent = ExponentField - ExponentBias;
            double Significand = 1 + SignificandField / SignificandRatio;
            return Sign * ldexp(Significand, Exponent);
        }

        /*  When the exponent field is zero, the value represented is subnormal:

                The exponent represented is 1 - ExponentBias, and the
                significand represented is the value given by the binary
                numeral “0.” followed by the significand bits.
        */
        case 0:
        {
            int    Exponent = 1 - ExponentBias;
            double Significand = 0 + SignificandField / SignificandRatio;
            return Sign * ldexp(Significand, Exponent);
        }
    }
}


/*  Test that a given set of fields decodes to the expected value and
    print the fields and the decoded value.
*/
static void Demonstrate(
    unsigned SignField, uint32_t SignificandField, uint32_t ExponentField,
    double Expected)
{
    double Observed
        = DecodeCustomFloat(SignField, SignificandField, ExponentField);

    if (! (Observed == Expected) && ! (isnan(Observed) && isnan(Expected)))
    {
        fprintf(stderr,
            "Error, expected (%u, %" PRIu32 ", %" PRIu32 ") to represent "
            "%g (hexadecimal %a) but got %g (hexadecimal %a).\n",
            SignField, SignificandField, ExponentField,
            Expected, Expected,
            Observed, Observed);
        exit(EXIT_FAILURE);
    }

    printf(
        "(%u, %" PRIu32 ", %" PRIu32 ") represents %g (hexadecimal %a).\n",
        SignField, SignificandField, ExponentField, Observed, Observed);
}


int main(void)
{
    Demonstrate(0, 0, 0, +0.);
    Demonstrate(1, 0, 0, -0.);
    Demonstrate(0, 255, 0, +1.);
    Demonstrate(1, 255, 0, -1.);
    Demonstrate(0, 511, 0, +INFINITY);
    Demonstrate(1, 511, 0, -INFINITY);
    Demonstrate(0, 511, 1, +NAN);
    Demonstrate(1, 511, 1, -NAN);
    Demonstrate(0, 0, 1, +0x1p-276);
    Demonstrate(1, 0, 1, -0x1p-276);
    Demonstrate(0, 255, 1, +1. + 0x1p-22);
    Demonstrate(1, 255, 1, -1. - 0x1p-22);
    Demonstrate(0, 1, 0, +0x1p-254);
    Demonstrate(1, 1, 0, -0x1p-254);
    Demonstrate(0, 510, 0x3fffff, +0x1p256 - 0x1p233);
    Demonstrate(1, 510, 0x3fffff, -0x1p256 + 0x1p233);
}

Some notes:

  • ldexp is a standard C library function. ldexp(x, e) returns x multiplied by 2 to the power of e.
  • uint32_t is an unsigned 32-bit integer type. It is defined in stdint.h.
  • "%" PRIu32 provides a printf conversion specification for formatting a uint32_t.
4
Program man On

Linked question is C++ not C. To convert between datatypes in C preserving bits, a tool to use is the union. Something like

union float_or_int {
  uint32_t i;
  float f;
}

float to_float(uint32_t mantissa, uint32_t exponent, uint32_t sign)
{
  union float_or_int result;
  result.i = (sign << 31) | (exponent << 22) | mantissa;
  return result.f;
}

Sorry for typos, it's been a while since I've coded in C

2
chqrlie On

Here is a simple program to illustrate how to break a float into its components and how to compose a float value from a (sign, exponent, mantissa) triplet:

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

void dumpbits(uint32_t bits, int n) {
    while (n--)
        printf("%d%c", (bits >> n) & 1, ".|"[!n]);
}

int main(int argc, char *argv[]) {
    unsigned sign = 0;
    unsigned exponent = 127;
    unsigned long mantissa = 0;
    union {
        float f32;
        uint32_t u32;
    } u;

    if (argc == 2) {
        u.f32 = strtof(argv[1], NULL);
        sign = u.u32 >> 31;
        exponent = (u.u32 >> 23) & 0xff;
        mantissa = (u.u32) & 0x7fffff;
        printf("%.8g -> sign:%u, exponent:%u, mantissa:0x%06lx\n",
               (double)u.f32, sign, exponent, mantissa);
        printf("+s+----exponent---+------------------mantissa-------------------+\n");
        printf("|");
        dumpbits(sign, 1);
        dumpbits(exponent, 8);
        dumpbits(mantissa, 23);
        printf("\n");
        printf("+-+---------------+---------------------------------------------+\n");
    } else {
        if (argc > 1) sign = strtol(argv[1], NULL, 0);
        if (argc > 2) exponent = strtol(argv[2], NULL, 0);
        if (argc > 3) mantissa = strtol(argv[3], NULL, 0);
        u.u32 = (sign << 31) | (exponent << 23) | mantissa;
        printf("sign:%u, exponent:%u, mantissa:0x%06lx -> %.8g\n",
               sign, exponent, mantissa, (double)u.f32);
    }
    return 0;
}

Note that contrary to your assignment, the size of the mantissa is 23 bits and the exponent has 8 bits, which correspond to the IEEE 754 Standard for 32-bit aka single-precision float. See the Wikipedia article on Single-precision floating-point format.