64位整数方程中的意外结果

时间:2015-08-03 21:34:02

标签: c++ 64-bit

我想问一个关于以下等式的问题:

 value = (floor(((value - min) + (step / 2)) / step) * step) + min;

我使用它将输入值限制为步长,给定无符号整数类型的最小值。

我已经使用它一段时间并且运作良好,直到我最近发现它可以给出意想不到的结果。以下程序证明了我的观点:

// test.cpp
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cinttypes>
#include <cmath>

int main(void)
{
  uint64_t value = 2305913515935801433;
  uint64_t min = value;
  uint64_t step = 1;

  uint64_t result1 = (value - min) + (step / 2);
  uint64_t result2 = ((value - min) + (step / 2)) / step;
  uint64_t result3 = floor(((value - min) + (step / 2)) / step);
  uint64_t result4 = (floor(((value - min) + (step / 2)) / step) * step);
  uint64_t result5 = (floor(((value - min) + (step / 2)) / step) * step) + min;

  uint64_t result6_ = (floor(((value - min) + (step / 2)) / step) * step);
  uint64_t result6 = result6_ + min;

  printf("result1 = %" PRIu64 " (0x%" PRIX64 ")\n", result1, result1);
  printf("result2 = %" PRIu64 " (0x%" PRIX64 ")\n", result2, result2);
  printf("result3 = %" PRIu64 " (0x%" PRIX64 ")\n", result3, result3);
  printf("result4 = %" PRIu64 " (0x%" PRIX64 ")\n", result4, result4);
  printf("result5 = %" PRIu64 " (0x%" PRIX64 ")\n", result5, result5);
  printf("result6 = %" PRIu64 " (0x%" PRIX64 ")\n", result6, result6);

  return EXIT_SUCCESS;
}

使用以下命令在64位Linux下编译:

g++ -Wall --std=c++11 -o test test.cpp

结果:

result1 = 0 (0x0)
result2 = 0 (0x0)
result3 = 0 (0x0)
result4 = 0 (0x0)
result5 = 2305913515935801344 (0x2000402020202000)
result6 = 2305913515935801433 (0x2000402020202059)

如您所见,完整等式(result5)的结果是错误的,并且以某种方式清除了最后一个字节。最后两部分结果(result6)是正确的。

我无法解释第五个结果失败的原因。我在这里缺少什么?

提前致谢!

2 个答案:

答案 0 :(得分:4)

floor正在转换为浮点double,导致精度损失。

c ++中的典型浮点双精度对于小于2的整数增加到第53次幂是准确的。 64位无符号整数可以大于该值。

答案 1 :(得分:1)

当您在中间结果上调用floor时,它会以double的形式返回。当您在同一个等式中对min进行加法时,数学运算将作为double数学运算,当您将2305913515935801433转换为double时,会导致数据丢失。

但是,当您将floor的结果分配给临时值时,它会恢复为uint64_t,从而允许使用积分数学计算最终结果并且不会丢失精度。

然而,所有这一切都表示,因为你在整数数学中完成整个方程式,所以完全不需要调用floor。该语言已经指定整数除法截断,因此根本不需要使用floor。当您删除floor调用时,原始等式将全部以64位整数运算完成,从而在单个表达式中得到正确的答案。