比较双打产生一个少一个和等于另一个

Comparing doubles yields one less AND equal to the other

所以我有以下代码:

 double which_min(const int i, const int j, const int nx,
                  const double step, const double local_cost,
                  double *D)
 {
      double tuple[3];

      // DIAG, LEFT, UP
      tuple[0] = (D[d2s(i-1, j-1, nx)] == NOT_VISITED) ? DBL_MAX : D[d2s(i-1, j-1, nx)] + step * local_cost;
      tuple[1] = (D[d2s(i, j-1, nx)] == NOT_VISITED) ? DBL_MAX : D[d2s(i, j-1, nx)] + local_cost;
      tuple[2] = (D[d2s(i-1, j, nx)] == NOT_VISITED) ? DBL_MAX : D[d2s(i-1, j, nx)] + local_cost;
      /*
      if (i == 83 && j == 124) printf("less? %d\n", tuple[1] < tuple[0]);
      if (i == 83 && j == 124) printf("equal? %d\n", tuple[1] == tuple[0]);
      if (i == 83 && j == 124) printf("greater? %d\n", tuple[1] > tuple[0]);
      */
      int min = (tuple[0] <= tuple[1]) ? 0 : 1;
      min = (tuple[min] <= tuple[2]) ? min : 2;

      if (i == 83 && j == 124) printf("min = %d\n", min + 1);

      return ((double) min + 1.0);
 }

对于我正在处理的问题案例,当 step = 1i = 83j = 124 时,我得到

min = 2. 

但是,如果我取消注释其他 if 语句,我会得到 min 的正确值,但是...

less? 1
equal? 1
greater? 0
min = 1

我知道比较双精度数可能很挑剔,我读到 this answer 提到从 80 位寄存器移动到 64 位内存,所以我猜 printf 语句可能导致类似的东西。然而,

  1. 感觉 tuple[1] 既小于又等于 tuple[0],这怎么可能?*
  2. 我可以更改它以始终提供相同的结果吗?

顺便说一句,这只发生在 32 位系统中,64 位版本不会给我带来任何问题。

编辑:我猜第一个 printf 会导致精度损失,因此 == 比较之后的计算结果为 1,但是,主要问题仍然是我是否可以使结果一致。

EDIT2:确实,将 if 语句的顺序更改为

 if (i == 83 && j == 124)
 {
      printf("equal? %d\n", tuple[1] == tuple[0]);
      printf("less? %d\n", tuple[1] < tuple[0]);
      printf("greater? %d\n", tuple[1] > tuple[0]);
 }

结果

equal? 0
less? 0
greater? 0
min = 1

我用 printf(%a) 得到的值是

tuple[0] = 0x1.2594a8056d275p+5
tuple[1] = 0x1.2594a8056d275p+5
tuple[2] = 0x1.fffffffffffffp+1023

确实,存储在 tuple 中的结果的读取精度高于预期,导致一些比较产生了相互矛盾的结果。

向编译器添加 -ffloat-store 标志可以解决这种情况,但如评论中所述,它可能并不理想,而且显然不可移植。

tuple 声明为 volatile 似乎可以解决问题,但必须将其声明为

volatile double *tuple = (double *)malloc(3 * sizeof(double));

然后

free((double *)tuple);

编辑:显然上面没有必要, 使用 volatile double tuple[3]; 就足够了。 为了仅在 x32 编译中使用它, 我在 C++ 中使用了以下 typedef

#include <type_traits> // conditional
typedef std::conditional<sizeof(void*) == 4, volatile double, double>::type tuple_t;
tuple_t tuple[3];