Search code examples
cieee-754long-double

Why do i get nan after swapping bits in long double value?


I had to write a program for my class that swaps 2 sets of bits in binary representation of some long double value. I know that in IEEE-754 standard nan appears when whole exponent is filled with ones. But in my case it is not fully filled, so i think that there's no reason for nan. I have no idea why this happens. I debugged my code many times, and it seems to work correctly. I think that conversation to char array and back to decimal are correct, because when i remove ld_switch_chunks function from main, program prints same number as is was in the beginning.

#include <stdio.h>

typedef union b_long_double
{
    char bin[16];
    long double num;
}b_long_double;



// char array to long double conversation
void transform_to_ld(const char * in)
{
    b_long_double tmp;
    tmp.num=0;
    for(int i=0; i<16; ++i)
        tmp.bin[i] = 0;
    for(int i=0; i<16; ++i)
    {
        char total = 0;
        for(int j=0; j<8;++j)
        {
            char current = 0;
            if(in[(i*8)+j] == 1)
            {
                current += 1;
                current <<= j;
                total |= current;
            }
        }
        tmp.bin[i] = total;
    }
    printf("%Lf\n", tmp.num);
}


//splits long double value to array of chars 
void split_ld_to_bin(const char* in, char* out)
{
    for(int i=0; i<16; ++i)
    {
        char current = in[i];
        for(int j=0; j<8;++j)
        {
            int index = i*8+j;
            out[index] = current & 1;
            current >>= 1;
        }
    }

}


//swaps 2 chunks of bits
void ld_switch_chunks(char * in)
{
    int first_bit=77; //lead bit of first chunk
    int second_bit=63; //lead bit of second chunk
    int length=6; // length of chunk

    if(first_bit < 0 || first_bit > 79)
    {
        printf("Invalid input\n");
        return;
    }

    if(second_bit < 0 || second_bit > 79 || first_bit == second_bit)
    {
        printf("Invalid input\n");
        return;
    }

    if(length <= 0 || length > 40 || (second_bit-length) < 0 || (first_bit-length) < 0
       || ( second_bit>first_bit && (second_bit-length) <= first_bit) || (first_bit>second_bit && (first_bit-length) <= second_bit))
    {
        printf("Invalid input\n");
        return;
    }


    char tmp = 0;
    if(first_bit > second_bit)
        for(int i=0; i<length; ++i)
        {
            tmp = in[second_bit-i];
            in[second_bit-i] = in[first_bit-i];
            in[first_bit-i] = tmp;
        }
    else
        for(int i=0; i<length; ++i)
        {
            tmp = in[first_bit-i];
            in[first_bit-i] = in[second_bit-i];
            in[second_bit-i] = tmp;
        }
}


void print_ld(char* ld_array)
{
    for(int i=79; i>=0; --i)
        printf("%d", ld_array[i]);
    printf("\n");
}


int main()
{
    b_long_double input;
    input.num = 15.375; // input number
    char binary[128] = {};
    split_ld_to_bin(input.bin, binary);
    print_ld(binary);


    ld_switch_chunks(binary);
    print_ld(binary);


    transform_to_ld(binary);
}

Output is:

01000000000000101111011000000000000000000000000000000000000000000000000000000000
01111101000000100000001000000000000000000000000000000000000000000000000000000000
nan

, where first line is binary of initial data, second line is binary with swapped sets and third line must be new long double value, resulting from bits swap. I would like to know the reason why that happens.


Solution

  • Digging into this Wikipedia page about x86 80-bit extended precision format (which I assume is the one the OP is messing with), we can find a table about the "interpretation of the fields of an x86 Extended Precision value".

    It says, about bit 63 beeing zero (as in OP's example):

    Unnormal. Only generated on the 8087 and 80287. The 80387 and later treat this as an invalid operand.
    ...
    In contrast to the single and double-precision formats, this format does not utilize an implicit/hidden bit. Rather, bit 63 contains the integer part of the significand and bits 62-0 hold the fractional part. Bit 63 will be 1 on all normalized numbers.

    This should explain the NaN.