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

KvR*_*KvR 2 c++ 64-bit

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

 value = (floor(((value - min) + (step / 2)) / step) * step) + min;
Run Code Online (Sandbox Code Playgroud)

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

我已经使用了一段时间,并且效果很好,直到最近发现它可以产生意想不到的效果。以下程序演示了我的观点:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

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

g++ -Wall --std=c++11 -o test test.cpp
Run Code Online (Sandbox Code Playgroud)

结果:

result1 = 0 (0x0)
result2 = 0 (0x0)
result3 = 0 (0x0)
result4 = 0 (0x0)
result5 = 2305913515935801344 (0x2000402020202000)
result6 = 2305913515935801433 (0x2000402020202059)
Run Code Online (Sandbox Code Playgroud)

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

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

提前致谢!

Bat*_*eba 5

底数转换为浮点数double,这会导致精度损失。

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