Home > database >  Is there a way to init a C double with a const value based on a hex pattern?
Is there a way to init a C double with a const value based on a hex pattern?

Time:01-27

I have a need to put an invalid hex pattern into a C-99 double value.

Unfortunately, this does not work (for obvious reasons):

const double invalid_double = 0x7ff0000000000001;

Neither does this (because the deref of the int const isn't know to be const apparently)

const uint64_t invalid_int = 0x7ff0000000000001;
const double invalid_double =  *(((double*)&invalid_int);

And union init does not seem to help much either (because u.d is not considered compile time const):

union {
  double d;
  uint64_t i;
} const u = { .i = 0x7ff0000000000001 };


const double invalid_double =  u.d;

Is there a way? I know invalid double are undefined behaviour and that I am walking into strange territory here. But, I have a special use case for this value.

CodePudding user response:

No it is not possible.

Och, who am I kidding! Just write a preprocessor parser of a IEE745 number that would generate a floating point constant expression by preprocessing that number. The following code allows you to do:

const double invalid_double = HEX_TO_DOUBLE(0x7ff0000000000001);

Note that the EXPONENT_TO_DOUBLE_IN macro is lacking about 2000 cases, so not all numbers are handled, but it's easy to fill up. Or maybe someone will have a better idea how to convert exponent to 2^(exponent-1023).

#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <float.h>
#include <math.h>
#include <inttypes.h>

// Apply function f on argument x and number
#define FOREACH_52(f, x) \
    f(x, 52) f(x, 51) f(x, 50) f(x, 49) f(x, 48) f(x, 47) f(x, 46) f(x, 45) \
    f(x, 44) f(x, 43) f(x, 42) f(x, 41) f(x, 40) f(x, 39) f(x, 38) f(x, 37) \
    f(x, 36) f(x, 35) f(x, 34) f(x, 33) f(x, 32) f(x, 31) f(x, 30) f(x, 29) \
    f(x, 28) f(x, 27) f(x, 26) f(x, 25) f(x, 24) f(x, 23) f(x, 22) f(x, 21) \
    f(x, 20) f(x, 19) f(x, 18) f(x, 17) f(x, 16) f(x, 15) f(x, 14) f(x, 13) \
    f(x, 12) f(x, 11) f(x, 10) f(x, 9) f(x, 8) f(x, 7) f(x, 6) f(x, 5) \
    f(x, 4) f(x, 3) f(x, 2) f(x, 1)

#define EXPONENT_TO_DOUBLE_CASE(x, y)  \
    x == (unsigned long long) y ? 0x1p ##y : \
    x == (unsigned long long)-y ? 0x1p-##y :
#define EXPONENT_TO_DOUBLE_IN(x)  ( \
    EXPONENT_TO_DOUBLE_CASE(x, 1022) \
    EXPONENT_TO_DOUBLE_CASE(x, 1021) \
    EXPONENT_TO_DOUBLE_CASE(x, 127) \
    EXPONENT_TO_DOUBLE_CASE(x, 2) \
    EXPONENT_TO_DOUBLE_CASE(x, 1) \
    EXPONENT_TO_DOUBLE_CASE(x, 0) \
    /* TODO: add more cases*/ \
    NAN )
// Convert EXPONENT number to 2^(x - 1023) number
#define EXPONENT_TO_DOUBLE(x)  EXPONENT_TO_DOUBLE_IN(x - 1023)

#define FRACTION_TO_DOUBLE_CASE(x, n) \
    ( (x & (1ull << (52 - n))) ? 0x1p-##n : 0 )  
// Convert FRACTION to 0.FRACTION_BITS(2) number.
#define FRACTION_TO_DOUBLE(x)  ( \
    FOREACH_52(FRACTION_TO_DOUBLE_CASE, x) \
    0 )

// Convert sign, exponent and fraction into a hex number.
#define HEX_TO_DOUBLE_IN(SIGN, EXP, FRAC) \
    EXP == 0x7FF ? \
        FRAC == 0 ? \
            SIGN * INFINITY : \
            NAN : \
    SIGN * EXPONENT_TO_DOUBLE(EXP) * ( \
        EXP == 0 ? \
            FRAC == 0 ? \
                0 : \
                (0.0   FRACTION_TO_DOUBLE(FRAC)) : \
        (1.0   FRACTION_TO_DOUBLE(FRAC)) \
    )

// The basic conversion utilities.
#define HEX_TO_DOUBLE_SIGN(x)      (((x) & 0x8000000000000000ull) ? -1.0 : 1.0)
#define HEX_TO_DOUBLE_EXPONENT(x)  (((x) & 0x7FF0000000000000ull) >> 52ull)
#define HEX_TO_DOUBLE_FRACTION(x)  (((x) & 0x000fffffffffffffull))
#define HEX_TO_DOUBLE(x)  \
    HEX_TO_DOUBLE_IN( \
        HEX_TO_DOUBLE_SIGN(x), \
        HEX_TO_DOUBLE_EXPONENT(x), \
        HEX_TO_DOUBLE_FRACTION(x) \
    )

/* ----------------------------------------------------------------------- */

union conv { uint64_t i; double d; };
static int err = 0;
void test(const char *str, union conv u, double d) {
    int equal = (isnan(d) && isnan(u.d)) || u.d == d;
    fprintf(stderr,
        "%s if %s is %#"PRIx64" = %g,%a =? %g,%a\n",
        equal ? "   OK" : "ERROR",
        str, u.i, u.d, u.d, d, d
    );
    err  = !equal;
}
// Test the conversion.
#define TEST(x) do { \
        static const double mydouble = HEX_TO_DOUBLE(x); \
        test(#x, (union conv){x}, mydouble); \
    } while(0)

int main() {
    assert(HEX_TO_DOUBLE_EXPONENT(0x3FF0000000000000) == 0x3FF);
    assert(FRACTION_TO_DOUBLE(0) == 0);
    assert(FRACTION_TO_DOUBLE(1) == 0x1p-52);
    assert(FRACTION_TO_DOUBLE(0x2) == 0x1p-51);
    assert(FRACTION_TO_DOUBLE(0x3) == 0x1p-51   0x1p-52);
    TEST(0x3FF0000000000000);
    TEST(0x3FF0000000000001);
    TEST(0x3FF0000000000101);
    TEST(0x3FFabcdef1234567);
    TEST(0xFFF0000000000000);
    TEST(0x7FF0000000000000);
    TEST(0x7FF0000000000001);

    // Finally, the test.
    static const double invalid_double = HEX_TO_DOUBLE(0x7ff0000000000001);
    assert(isnan(invalid_double));

    return err;
}

When compiled with gcc -Wall -Wextra -pedantic outputs the code from test() function that I used for unit testing.

   OK if 0x3FF0000000000000 is 0x3ff0000000000000 = 1,0x1p 0 =? 1,0x1p 0
   OK if 0x3FF0000000000001 is 0x3ff0000000000001 = 1,0x1.0000000000001p 0 =? 1,0x1.0000000000001p 0
   OK if 0x3FF0000000000101 is 0x3ff0000000000101 = 1,0x1.0000000000101p 0 =? 1,0x1.0000000000101p 0
   OK if 0x3FFabcdef1234567 is 0x3ffabcdef1234567 = 1.67111,0x1.abcdef1234567p 0 =? 1.67111,0x1.abcdef1234567p 0
   OK if 0xFFF0000000000000 is 0xfff0000000000000 = -inf,-inf =? -inf,-inf
   OK if 0x7FF0000000000000 is 0x7ff0000000000000 = inf,inf =? inf,inf
   OK if 0x7FF0000000000001 is 0x7ff0000000000001 = nan,nan =? nan,nan

Additional cases of NAN("1") etc. can be also handled with an if-case, like #define FRACTION_TO_NAN(x) x == 0 ? __builtin_nan("0") : x == 1 ? __builtin_nan("1") ... however, I do not think that will be compilable.

Anyway, it's not possible to convert hex to double in static. And for NAN with special fraction, you have to use compiler extensions anyway, see answer by user694733 .

CodePudding user response:

Use compiler extensions, for example GCC has __builtin_nan and __builtin_nans functions for quiet and signaling NaN respectively.

const double invalid_double = __builtin_nans("1");  // 0x7ff0000000000001

It's not C99, but better to fail predictably if compiler doesn't support it, than fail unpredictably with some hacky solution.

You could also define your own NaN macro, which uses this builtin function on GCC, but reverts to standard NAN macro for other compilers.

CodePudding user response:

AFAIK, it cannot be done now. Upcoming C23 will allow it by adding constexpr storage specifier. For now I suggest using a union combined with a macro.

union {
  double d;
  uint64_t i;
} const u = { .i = 0x7ff0000000000001 };

#define invalid_double (u.d)

int main() {
    printf("%g", invalid_double);
}

prints:

nan

CodePudding user response:

This will probably work, but it relies on undefined behavior and so cannot be recommended:

In file consts.c:

#include <stdint.h>

const uint32_t x = 0x420a0000UL;
const uint64_t y = 0x408edd0000000000ULL;

In file main.c:

#include <stdio.h>

extern const float x;
extern const double y;

int main()
{
    printf("%f %f\n", x, y);
}

On my machine, at least, this prints

34.500000 987.625000

as intended. And if I use Thomas Kejser's bit pattern 0x7ff0000000000001 I get nan.

  • Related