我有一个双,一个int64_t。我想知道它们是否具有完全相同的值,如果将一种类型转换为另一种类型,则不会丢失任何信息。
我目前的实施如下:
int int64EqualsDouble(int64_t i, double d) {
return (d >= INT64_MIN)
&& (d < INT64_MAX)
&& (round(d) == d)
&& (i == (int64_t)d);
}
我的问题是:这个实现是否正确?如果没有,那么什么是正确的答案?为了正确,它必须不留任何误报,也不得假阴性。
一些示例输入:
答案 0 :(得分:16)
是的,您的解决方案可以正常运行,因为它的设计是这样做的,因为int64_t
在定义中用两个补码表示(C99 7.18.1.1:1),在使用类似二进制的东西的平台上{75}的{75}双精度double
类型。它与this one基本相同。
在这些条件下:
d < INT64_MAX
是正确的,因为它等同于d < (double) INT64_MAX
,并且在转换为加倍时,数字INT64_MAX
等于0x7fffffffffffffff,向上舍入。因此,您希望d
严格小于生成的double
,以避免在执行(int64_t)d
时触发UB。
另一方面,INT64_MIN
,即-0x8000000000000000,是完全可表示的,这意味着等于double
的{{1}}可以等于某些{{1}并且不应该被排除(并且这样的(double)INT64_MIN
可以转换为int64_t
而不会触发未定义的行为)
不言而喻,由于我们专门使用了关于整数和二进制浮点的2的补码假设,因此在不同平台上的这种推理无法保证代码的正确性。采用具有二进制64位浮点和64位1的补码整数类型double
的平台。在该平台上,int64_t
为T
。转换为该数字的T_MIN
会向下舍入,从而产生-0x7fffffffffffffff
。在该平台上,使用您编写的程序,使用double
-0x1.0p63
使前三个条件成立,导致-0x1.0p63
中的未定义行为,因为overflow in the conversion from integer to floating-point is undefined behavior。< / p>
如果您可以访问完整的IEEE 754功能,则可以使用更短的解决方案:
d
如果转换不准确(即,如果(T)d
不能完全表示为#include <fenv.h>
…
#pragma STDC FENV_ACCESS ON
feclearexcept(FE_INEXACT), f == i && !fetestexcept(FE_INEXACT)
),则此解决方案利用从整数到浮点的转换设置INEXACT标志。
当且仅当i
和double
表示各自类型中的相同数学值时,INEXACT标志仍未设置且f
等于(double)i
。
这种方法要求编译器警告代码访问FPU的状态,通常使用f
,但通常不支持,并且您必须使用编译标志。
答案 1 :(得分:3)
OP的代码具有可以避免的依赖性。
要成功进行比较,d
必须是整数,round(d) == d
负责处理。即使d
,作为NaN也会失败。
d
必须在[INT64_MIN
... INT64_MAX
]范围内以数学方式,如果if
条件正确确保,然后最后的i == (int64_t)d
完成了测试。
所以问题归结为将INT64
限制与double
d
进行比较。
我们假设FLT_RADIX == 2
,但不一定是IEEE 754 binary64。
d >= INT64_MIN
不是问题,因为-INT64_MIN
是2的幂并且恰好转换为具有相同值的double
,因此>=
是准确的。
代码想做数学d <= INT64_MAX
,但这可能不起作用,所以问题。 INT64_MAX
是“2 - 1的幂”,可能不能完全转换 - 这取决于double
的精度是否超过63位 - 使得比较不清楚。解决方案是将比较减半。 d/2
没有精确丢失,INT64_MAX/2 + 1
完全转换为double
2次幂
d/2 < (INT64_MAX/2 + 1)
[编辑]
// or simply
d < ((double)(INT64_MAX/2 + 1))*2
因此,如果代码不想依赖精度低于double
的{{1}}。 (可能适用于uint64_t
)更便携的解决方案
long double
注意:没有舍入模式问题。
[编辑]更深的限制说明
以数学方式确保int int64EqualsDouble(int64_t i, double d) {
return (d >= INT64_MIN)
&& (d < ((double)(INT64_MAX/2 + 1))*2) // (d/2 < (INT64_MAX/2 + 1))
&& (round(d) == d)
&& (i == (int64_t)d);
}
可以重新声明为INT64_MIN <= d <= INT64_MAX
,因为我们正在处理整数。由于代码中INT64_MIN <= d < (INT64_MAX + 1)
的原始应用肯定是0,因此可以选择(double) (INT64_MAX + 1)
。这可以扩展到((double)(INT64_MAX/2 + 1))*2
更高2的幂到double
的稀有机器。比较限制为精确 2次幂,转换为((double)(INT64_MAX/FLT_RADIX + 1))*FLT_RADIX
不会导致精度损失,double
是精确的,无论浮点精度如何。注意:(lo_limit >= d) && (d < hi_limit)
的罕见浮点仍然存在问题。
答案 2 :(得分:3)
除了Pascal Cuoq的精心解答之外,考虑到你在评论中提供的额外背景,我还会为负零添加一个测试。你应该保留负零,除非你有充分的理由不这样做。您需要进行特定测试,以避免将其转换为(int64_t)0
。根据您当前的提案,负零将通过您的测试,存储为int64_t
并以正零读回。
我不确定测试它们的最有效方法是什么,也许是这样:
int int64EqualsDouble(int64_t i, double d) {
return (d >= INT64_MIN)
&& (d < INT64_MAX)
&& (round(d) == d)
&& (i == (int64_t)d
&& (!signbit(d) || d != 0.0);
}