Reading the trailing fraction part in a nanl

Viewed 62

I am upgrading a working multi-platform math library (compliant with c99 standard) and have to code the long double extension to a couple functions (alike sinl for sin).

In particular, I have to deal with some functions that can fail in multiple ways and, in the library currently working, I leveraged the nan(const char *tagp) function like this:

double nan_with_err(uint8_t err)
{   char buffer[4];
    snprintf(buffer, 4, "%d", err);
    return nan(buffer);
}   /*  end of nan_with_err  */

inline uint8_t geterr_from_nan(double d)
{   return *(uint64_t*)&d & 0xff;
}   /*  end of geterr_from_nan  */

The two functions rely on the fact that double and uint64_t have the same number of bytes and, regardless of the endianness, the less significant byte contains the trailing fraction part (no need to use more than 8 bits: actually the reasons to get a nan are less than a dozen). In fact, the code can rely also on IEEE 754 which, I am told, guarantees that integers and floating-points numbers have the same endianness on the same machine.

A straightforward extension is substituting nan with nanl and writing a new function like:

long double nanl_with_err(uint8_t err)
{   char buffer[4];
    snprintf(buffer, 4, "%d", err);
    return nanl(buffer);
}   /*  end of nanl_with_err  */

Troubles arise when I am trying to retrieve the error code embedded into the long double, as the endianness and the number of bytes in a long double can vary on different machines, and there is no integer guaranteed to have the same amount of bytes than the long double on a given machine.

This is a working function to retrieve the error code:

uint8_t geterr_from_nanl(long double d)
{   uint16_t maybe_head = *(uint16_t*)&d,
             maybe_tail = *((uint16_t*)&d + sizeof(long double)/2);

    return maybe_head == 0x7fff
         ? maybe_tail & 0xFF : maybe_head & 0xFF;
}   /*  end of geterr_from_nanl  */

The function relies on the fact that the exponent field of long double is a 16-bit field (or larger?) filled with 1, and on the assumption that integer and floating-point values have the same byte ordering.

I tested the code on a little-endian machine and on a mock-up big-endian one, and it works, but I am concerned with portability and wondering if there was a more standardized way to extract the error code from a nanl.

For testing, the following code is working, which incidentally also shows that the trailing fraction on nanl is not written onto a nan (of course I could shift it 2 bytes upward in the nanl, but it seems like asking for even more troubles). This is the reason why I didn't wrote something like

inline uint8_t geterr_from_nanl(long double d) { return geterr_from_nan((double)d); }

Working code (compiled with -std=c99):

#include <stdio.h>   // for *printf
#include <stdint.h>  // for uint*_t
#include <math.h>    // for nan*

double nan_with_err(uint8_t err)
{   char buffer[4];
    snprintf(buffer, 4, "%d", err);
    return nan(buffer);
}   /*  end of nan_with_err  */

uint8_t geterr_from_nan(double d)
{   return *(uint64_t*)&d & 0xff;
}   /*  end of geterr_from_nan  */

long double nanl_with_err(uint8_t err)
{   char buffer[4];
    snprintf(buffer, 4, "%d", err);
    return nanl(buffer);
}   /*  end of nanl_with_err  */

uint8_t geterr_from_nanl(long double d)
{   uint16_t maybe_head = *(uint16_t*)&d,
             maybe_tail = *((uint16_t*)&d + sizeof(long double)/2);

    return maybe_head == 0x7fff
        ? maybe_tail & 0xFF : maybe_head & 0xFF;
}   /*  end of geterr_from_nanl  */

int main(void)
{   uint8_t err = 0xdb;
    double d = nan_with_err(err);
    printf("err = 0x%02X, nan  = %f, err from nan  = 0x%02X\n",
           err, d, geterr_from_nan(d));

    long double ld = nanl_with_err(err);
    printf("err = 0x%02X, nanl = %Lf, err from nanl = 0x%02X\n"
           "err from (double)nanl = 0x%02X\n",
           err, ld, geterr_from_nanl(ld), geterr_from_nan((double)ld));

    return 0;
}   /*  end of main  */

The code output is:

err = 0xDB, nan  = nan, err from nan  = 0xDB
err = 0xDB, nanl = nan, err from nanl = 0xDB
err from (double)nanl = 0x00
0 Answers
Related