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