有没有办法使用基于十六进制模式的 const 值来初始化 C double ?

2024-01-02

我需要将无效的十六进制模式放入 C-99 中double value.

不幸的是,这不起作用(出于明显的原因):

const double invalid_double = 0x7ff0000000000001;

这也不是(因为 int const 的 deref 显然不知道是 const)

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

And unioninit 似乎也没有多大帮助(因为u.d不被视为编译时常量):

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


const double invalid_double =  u.d;

有办法吗?我知道无效double是未定义的行为,我正在走进奇怪的领域。但是,我对这个值有一个特殊的用例。


不,这是不可能的。

哦,我在开玩笑吧!只需编写一个 IEE745 数字的预处理器解析器,即可通过预处理该数字来生成浮点常量表达式。以下代码允许您执行以下操作:

const double invalid_double = HEX_TO_DOUBLE(0x7ff0000000000001);

请注意,EXPONENT_TO_DOUBLE_IN宏大约缺少2000个案例,所以并不是所有的数字都被处理,但是很容易填满。或者也许有人会更好地了解如何将指数转换为 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;
}

当编译时gcc -Wall -Wextra -pedantic输出代码test()我用于单元测试的函数。

   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

其他案例NAN("1")等等也可以用 if-case 来处理,比如#define FRACTION_TO_NAN(x) x == 0 ? __builtin_nan("0") : x == 1 ? __builtin_nan("1") ...但是,我不认为这是可以编译的。

无论如何,不​​可能将十六进制转换为双精度static。而对于NAN对于特殊分数,无论如何您都必须使用编译器扩展,请参阅 user694733 的回答。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

有没有办法使用基于十六进制模式的 const 值来初始化 C double ? 的相关文章

随机推荐