2

我不明白以下 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 实际上只是投入了doubleint32_t?为什么要加法?为什么要这样写?
  • num2u64 中提到的这个“功能”是什么?(我相信 _MSC_VER 意味着它是微软 C 编译器的代码路径)。

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

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

每一个提示都值得赞赏。

4

4 回答 4

11

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

num2u64 是 MSVC 的错误/错误功能的解决方法,它始终通过 int64_t 将双精度转换为 uint64_t。对于 >= 2^63 的数字,这不会给出所需的结果。MS 认为这种可憎行为是一种“特征”。呃。

于 2013-01-24T10:27:36.310 回答
3

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 的 double 转换为其整数表示时,可能是为了防止溢出或签名失败,但我不能告诉你更多。

于 2013-01-23T21:43:12.997 回答
1

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

通过 a 进行转换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)。将double大于 2 63的值转换为无符号整数时,会发生“不好的事情”(可能是饱和),因此作者减去 2 64使其成为负数,然后将其转换为有符号负整数,然后转换结果到一个值大于 2 63的无符号整数。

在任何情况下,您都可以看出意图只是将 a 转换double为 a uint64_t,因为这就是它在非 Windows 平台上所做的一切。

于 2013-01-24T02:22:52.513 回答
0

这些功能通过魔术“工作”。

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

请注意所呈现的代码如何通过分配给 on 然后使用 o.u32.lo 的值来使用未指定的值。

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

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

于 2013-01-23T22:27:12.673 回答