C“double to num”转换代码:为什么这样写?

时间:2013-01-23 21:28:02

标签: c double type-conversion low-level

我不明白以下C转换函数是如何工作的(以及为什么它们以这种方式编写);我很确定原作者知道他在做什么:

typedef union TValue {
  uint64_t u64;
  double n;
  struct {
    uint32_t lo;    /* Lower 32 bits of number. */
    uint32_t hi;    /* Upper 32 bits of number. */
  } u32;
  [...]
} TValue;


static int32_t num2bit(double n)
{
  TValue o;
  o.n = n + 6755399441055744.0;  /* 2^52 + 2^51 */
  return (int32_t)o.u32.lo;
}

static uint64_t num2u64(double n)
{
#ifdef _MSC_VER
  if (n >= 9223372036854775808.0)  /* They think it's a feature. */
    return (uint64_t)(int64_t)(n - 18446744073709551616.0);
  else
#endif
  return (uint64_t)n;
}
  • num2bit实际上只是将double投射到int32_t?为什么要加?为什么这样写呢?
  • num2u64中提到的这个“功能”是什么? (我相信_MSC_VER意味着它是微软C编译器的代码路径)。

请注意,这些函数不是总是使用(取决于CPU架构),这适用于little-endian(我解决了一些预处理器宏以简化)。

指向在线可浏览镜像的链接(代码来自LuaJIT项目): 周围Header file(或whole project)。

每个提示都受到赞赏。

4 个答案:

答案 0 :(得分:10)

num2bit旨在实现Lua BitOp semantics特别是wrt。模数运算。由于LuaJIT仅适用于特定的CPU,平台和编译器,因此实现定义的行为得到了很好的控制。不要在其他任何地方使用此代码。

num2u64是MSVC的错误/错误的解决方法,它总是通过int64_t将double转换为uint64_t。对于数字> = 2 ^ 63,这没有给出期望的结果。 MS认为这种憎恶是一种“特征”。咄。

答案 1 :(得分:3)

num2bit:通过将第51位和第52位设置为1,这会强制指数为特定数字(否则会出现溢出) - 然后当您返回(int32_t)o.u32.lo时,您知道您正在获得因为指数是固定的,所以整数返回与double的'低32位'相同的值。所以,这是一个快速获得大多数双倍整数值的技巧。看起来这样做会截断小数点后的数字,如果它开始时为2 ^ 51或更大,则会产生意想不到的效果。

>>> math.frexp(1.0 + 6755399441055744.0)
(0.7500000000000001, 53)
>>> math.frexp(0.0 + 6755399441055744.0)
(0.75, 53)
>>> math.frexp(564563465 + 6755399441055744.0)
(0.7500000626791358, 53)
>>> math.frexp(-564563465 + 6755399441055744.0)
(0.7499999373208642, 53)
>>> math.frexp(1.5 + 6755399441055744.0)
(0.7500000000000002, 53)
>>> math.frexp(1.6 + 6755399441055744.0)
(0.7500000000000002, 53)
>>> math.frexp(1.4 + 6755399441055744.0)
(0.7500000000000001, 53)

编辑:设置第51位和第52位的原因是因为如果你只设置第52位,那么负数会导致指数改变:

>>> math.frexp(0 + 4503599627370496.0)
(0.5, 53)
>>> math.frexp(-543635634 + 4503599627370496.0)
(0.9999998792886404, 52)

num2u64:没有头绪。但第一个数字是2 ^ 63,第二个数字是2 ^ 64。当将大于2 ^ 63的双精度转换为整数表示时,可能是为了防止溢出或符号失败,但我不能告诉你更多。

答案 2 :(得分:1)

num2bit使用舍入到最接近的整数,手动将IEEE标准double的内存中表示转换为32位,定点,二进制补码的格式。

转换union是不安全的,因为它违反了严格类型别名规则。你不被允许写一个工会的一个成员,然后从另一个成员读。做一些像

这样的事情会更合适
static int32_t num2bit(double n)
{
  int32_t o;
  n += 6755399441055744.0;  /* 2^52 + 2^51 */
  memcpy( & o, & n, sizeof o ); /* OK with strict aliasing but must mind endianness. */
  return o;
}

这个功能可能是一个优化,但它的价值是可疑的。您需要对每个新微处理器进行重新测试,并确保它仅用于速度更快的硬件上。

另请注意,普通C浮点积分转换使用舍入到零或截断。这个函数可能根本不用于处理小数值。


num2u64是特定于Windows的解决方法(请注意#ifdef)。当将大于2 63 double值转换为无符号整数时,会发生“不好”(可能是饱和),因此作者减去2 64 来制作它是一个负数,然后将其转换为带符号的负整数,然后将结果转换为无符号整数,该整数的值大于2 63

在任何情况下,您都可以告诉意图只是将double转换为uint64_t,因为这是非Windows平台上的全部内容。

答案 3 :(得分:0)

这些功能通过魔法“起作用”。

这来自n1570.pdf的§6.2.6.1p7,它是C标准草案:当一个值存储在union类型的对象的成员中时,对象表示的字节与那个不对应成员,但确实对应其他成员采取未指定的值

注意所提供的代码如何使用未指定的值分配给o.n然后使用o.u32.lo的值。

这来自n1570.pdf的§6.3.1.3p3,这是C标准草案:否则,新类型已签名且值无法在其中表示;结果是实现定义的,或者引发实现定义的信号。

注意所呈现的代码如何调用实现定义的行为,因为它多次从无符号转换为带符号的32位整数。假设它是提出一个实现定义的计算异常信号。如果要返回默认信号处理程序,这也会导致未定义的行为。 /* They think it's a feature. */