有没有办法用基于十六进制模式的常量值初始化一个C double?

s6fujrry  于 2023-01-29  发布在  其他
关注(0)|答案(5)|浏览(94)

我需要将无效的十六进制模式放入C-99 double值中。
不幸的是,这不起作用(原因显而易见):

const double invalid_double = 0x7ff0000000000001;

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

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

而且union init似乎也没有多大帮助(因为u.d不被认为是编译时常量):

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

const double invalid_double =  u.d;

有什么办法吗?我知道无效的double是未定义的行为,我正在进入一个奇怪的领域。但是,我有一个特殊的使用这个值的情况。

ff29svar

ff29svar1#

使用编译器扩展,例如GCC具有__builtin_nan__builtin_nans函数,分别用于静默和发送NaN信号。

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

它不是C99,但如果编译器不支持它,那么最好是可预测地失败,而不是用一些黑客解决方案不可预测地失败。
您还可以定义自己的NaN宏,它在GCC上使用这个内置函数,但在其他编译器上恢复为标准的NAN宏。

cqoc49vn

cqoc49vn2#

不,这不可能。
哎呀,我在跟谁开玩笑呢!只要编写一个IEE 745数字的预处理器解析器,通过预处理该数字生成一个浮点常量表达式即可。下面的代码允许您执行以下操作:

const double invalid_double = HEX_TO_DOUBLE(0x7ff0000000000001);

注意,EXPONENT_TO_DOUBLE_IN宏缺少大约2000个大小写,所以不能处理所有的数字,但是很容易填充,或者也许有人会更好地知道如何将指数转换为2^(指数-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,无论如何你必须使用编译器扩展,请参见user 694733的回答。

rqqzpn5f

rqqzpn5f3#

哎呀,现在还做不到。即将到来的C23将允许它通过添加constexpr存储说明符。现在我建议使用一个结合宏的联合。

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

#define invalid_double (u.d)

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

印刷品:

nan
6rvt4ljy

6rvt4ljy4#

这可能会起作用,但它依赖于未定义的行为,因此不推荐使用:
在文件consts.c中:

#include <stdint.h>

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

在文件main.c中:

#include <stdio.h>

extern const float x;
extern const double y;

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

在我的机器上,至少,这个打印

34.500000 987.625000

如果我用托马斯·凯泽的位模式,得到。

wvt8vs2t

wvt8vs2t5#

也许有一个替代方法?用一个指向double的 * 指针 * 初始化?

const double *const invalid_double_ptr = &( union {
      double d;
      uint64_t i;
    }) {.i = 0x7ff0000000000001}.d;

注:union漏洞利用假定sizeof(double) == sizeof(uint64_t),两个成员具有相同的字节序,并且double使用预期的编码。

相关问题