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



我不明白以下 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 架构),这是针对小端序的(我解析了一些预处理器宏以简化)。

链接到在线可浏览镜像(代码来自LuaJIT项目):周围的头文件(或整个项目)。

每一个提示都值得赞赏。

num2bit旨在实现Lua BitOp语义,特别是wt.模块化算法。实现定义的行为得到了很好的控制,因为LuaJIT无论如何都只适用于特定的CPU,平台和编译器。不要在其他任何地方使用此代码。

num2u64 是 MSVC 错误/错误功能的解决方法,它总是通过 int64_t 将双倍转换为 uint64_t。这不会给出数字>= 2^63 的预期结果。MS认为这种可憎的东西是一个"特征"。咄。

num2bit:通过将第 51 位和第 52 位设置为 1,这会强制指数为特定数字(否则会出现溢出) - 然后当你返回 (int32_t)o.u32.lo 时,你知道你得到的是一个整数,其值与双精度值的"低 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 的双精度转换为其整数表示形式时溢出或符号失败,但我不能告诉你更多。

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)。当将大于 263double值转换为无符号整数时,会发生"不好的事情"(可能是饱和度),因此作者减去 264 使其成为负数,然后将其转换为有符号负整数,然后将结果转换为值大于 263 的无符号整数。

无论如何,您可以说其意图只是 将double转换为uint64_t ,因为这就是它在非 Windows 平台上所做的一切。

这些函数通过魔法"工作"。

这来自 n1570.pdf 的 §6.2.6.1p7,这是 C 标准草案:当值存储在联合类型的对象的成员中时,对象表示形式中不对应于该成员但对应于其他成员的字节采用未指定的值

请注意,呈现的代码如何使用未指定的值,方法是分配给 o.n,然后使用 o.u32.lo 的值。

这来自 n1570.pdf 的 §6.3.1.3p3,这是 C 标准草案: 否则,新类型是有符号的,并且无法在其中表示值;结果要么是实现定义的,要么是实现定义的信号。

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

最新更新