如何测试无损双/整数转换?

时间:2015-11-15 11:26:14

标签: c floating-point integer double

我有一个双,一个int64_t。我想知道它们是否具有完全相同的值,如果将一种类型转换为另一种类型,则不会丢失任何信息。

我目前的实施如下:

int int64EqualsDouble(int64_t i, double d) {
    return (d >= INT64_MIN)
        && (d < INT64_MAX)
        && (round(d) == d)
        && (i == (int64_t)d);
}

我的问题是:这个实现是否正确?如果没有,那么什么是正确的答案?为了正确,它必须不留任何误报,也不得假阴性。

一些示例输入:

  • int64EqualsDouble(0,0.0)应返回1
  • int64EqualsDouble(1,1.0)应返回1
  • int64EqualsDouble(0x3FFFFFFFFFFFFFFF,(double)0x3FFFFFFFFFFFFFFF)应该返回0,因为2 ^ 62 - 1可以用int64_t精确表示,但不能用double表示。
  • int64EqualsDouble(0x4000000000000000,(double)0x4000000000000000)应返回1,因为2 ^ 62可以在int64_t和double中精确表示。
  • int64EqualsDouble(INT64_MAX,(double)INT64_MAX)应该返回0,因为INT64_MAX不能完全表示为double
  • int64EqualsDouble(...,1.0e100)应返回0,因为1.0e100无法准确表示为int64_t。

3 个答案:

答案 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_tT。转换为该数字的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标志。

当且仅当idouble表示各自类型中的相同数学值时,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);
}