C ";“双精度到数值”;转换代码:为什么这样写?

C ";“双精度到数值”;转换代码:为什么这样写?,c,double,type-conversion,low-level,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; stati

我不明白下面的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中暗指的“特性”是什么?(我相信这是微软C编译器的代码路径)
请注意,这些函数并不总是被使用(取决于CPU体系结构),这是针对little endian的(为了简化,我解析了一些预处理器宏)

指向联机可浏览镜像的链接(代码来自项目): 周围的(或)


每一个提示都值得欣赏。

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的双精度数转换为整数表示形式时,这可能是为了防止溢出或签名失败,但我不能告诉您更多信息。

这些函数神奇地“工作”

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

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

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


请注意,当代码多次从无符号整数转换为有符号32位整数时,它是如何调用实现定义的行为的。假设它将引发一个实现定义的计算异常信号。如果默认信号处理程序返回,这也会导致未定义的行为<代码>/*他们认为这是一项功能*/

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

通过联合进行转换是不安全的,因为它违反了严格的类型别名规则。你不允许先给工会的一个成员写信,然后再从另一个成员那里读。这样做更合适

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


在任何情况下,您都可以看出其目的只是将一个
double
转换为
uint64\u t
,因为这就是它在非Windows平台上所做的一切。

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


num2u64是MSVC的一个bug/misfeature的解决方案,它总是通过int64\t将double转换为uint64\t。对于大于等于2^63的数字,这不会给出所需的结果。MS认为这是一个“特征”。Duh.

“我相当肯定原作者知道他在做什么”永远不要假设。不要这么不信任。。。有些人赢得了被相信的权利,直到被证明是错误的;)(这是一个JIT编译器代码库,一个单人项目,目前是我所知道的任何动态语言中速度最快的,所以…)浮点数位模式的特定知识。阅读更多类似于这样的大脑褶皱代码。查看脚注95:“如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将重新解释为6.2.6中所述的新类型中的对象表示(有时称为“类型双关”的过程)。”。这可能是陷阱表示。“使用陷阱表示是未定义的行为。
uint32\u t
不能有任何陷阱表示(7.20.1.1 p2:“typedef名称uintN\u t指定一个宽度为N且没有填充位的无符号整数类型。因此,uint24\u t表示这种宽度正好为24位的无符号整数类型。”)很好。我将修改我的帖子。这个答案毫无帮助。正如我在问题中所说的,我试图找出代码是如何工作的,而不是最新的C标准是否对其做出任何断言