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

Gwe*_*oué 22 c floating-point double integer

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

我目前的实施如下:

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

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

一些样本输入:

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

Pas*_*uoq 16

是的,您的解决方案可以正常工作,因为它是为此而设计的,因为int64_t在定义中使用类似二进制IEEE 754双精度的平台的平台(C99 7.18.1.1:1)表示为二进制补码double.它与基本相同.

在这些条件下:

  • d < INT64_MAX是正确的,因为它相当于d < (double) INT64_MAX并且在转换为double时,数字INT64_MAX,等于0x7fffffffffffffff,向上舍入.因此,您希望d严格地小于结果,double以避免在执行时触发UB (int64_t)d.

  • 另一方面,INT64_MIN-0x8000000000000000是完全可表示的,这意味着double等于的a (double)INT64_MIN可以等于某个int64_t并且不应该被排除(并且这样的a double可以转换为int64_t不触发未定义的行为)

不言而喻,由于我们专门使用了关于整数和二进制浮点的2的补码假设,因此在不同的平台上进行这种推理并不能保证代码的正确性.采用具有二进制64位浮点和64位1的补码整数类型的平台T.在那个平台T_MIN-0x7fffffffffffffff.该double数字的转换向下舍入,导致-0x1.0p63.在该平台上,使用您编写的程序,使用-0x1.0p63for d使前三个条件成立,导致未定义的行为(T)d,因为从整数到浮点的转换中的溢出是未定义的行为.


如果您可以访问完整的IEEE 754功能,则可以使用更短的解决方案:

#include <fenv.h>
…
#pragma STDC FENV_ACCESS ON
feclearexcept(FE_INEXACT), f == i && !fetestexcept(FE_INEXACT)
Run Code Online (Sandbox Code Playgroud)

如果转换是不精确的(即,如果i不能完全表示为a double),此解决方案利用从整数到浮点的转换设置INEXACT标志.

INEXACT标志保持未设置状态,f并且等于(double)i且仅当fi表示相应类型中的相同数学值.

这种方法要求编译器警告代码访问FPU的状态,通常使用#pragma STDC FENV_ACCESS on但通常不支持,并且您必须使用编译标志.