C(++) 中双精度的排序/比较不稳定

Sorting/comparison of doubles in C(++) not stable?

本文关键字:排序 比较 不稳定 双精度      更新时间:2023-10-16

我在双打方面遇到了一个非常奇怪的问题。我有一个按降序排序的浮点数(双精度)列表。然而,在我的程序后面,我发现它们不再完全排序了。例如:

0.65801139819
0.6545651031    <-- a
0.65456513001   <-- b
0.64422968678

中间的两个数字被翻转。有人可能会认为这个问题出在数字的表示上,它们只是打印错误。但是我使用与排序相同的运算符将每个数字与前一个数字进行比较 - 没有转换为基数 10 或类似的数字:

double last_pt = 0;
for (int i = 0; i < npoints; i++) {
  if (last_pt && last_pt < track[i]->Pt()) {
    cout << "ERROR: new value " << track[i]->Pt()
         << " is higher than previous value " << last_pt << endl;
  }
  last_pt = track[i]->Pt();
}

在排序过程中,这些值将按以下方式进行比较

bool moreThan(const Track& a, const Track& b) {
  return a.Pt() > b.Pt();
}

我确保它们始终是双倍的,而不是转换为浮动。 Pt()返回双精度值。列表中没有 NaN,排序后我不碰列表。

为什么会这样,这些数字有什么问题,以及(如何)对数字进行排序以使它们保持排序?

您确定在某个时候没有将double转换为float吗?让我们看一下这两个数字的二进制表示:

0 01111111110 0100111100100011001010000011110111010101101100010101
0 01111111110 0100111100100011001010010010010011111101011010001001

double中,我们有1位符号,11位指数和53位尾数,而在float中有1位符号,8位指数和23位尾数。请注意,两个数字中的尾数在前 23 位是相同的。

根据舍入方法的不同,会有不同的行为。如果只是修剪了位>23,则这两个数字作为float是相同的:

0 011111110 01001111001000110010100 (trim: 00011110111010101101100010101)
0 011111110 01001111001000110010100 (trim: 10010010011111101011010001001)

您正在比较函数的返回值。 浮点返回值在浮点寄存器中返回,浮点寄存器具有更高的精度比双倍。 当比较两个这样的值时(例如 a.Pt() > b.Pt()),编译器会调用其中一个函数,存储返回类型为 double 的未命名临时中的值(因此将结果到 double ),然后调用另一个函数,并比较其结果(仍在浮点寄存器中,未舍入为 double ) 与存储的值。 这意味着您最终可以得到a.Pt() > b.Pt()b.Pt() > a.Pt()a.Pt() > a.Pt()的情况。 这会导致sort感到困惑。(正式地说,如果我们在这里谈论std::sort,这会导致未定义的行为,我听说过它确实会导致核心的情况转储。

另一方面,您说Pt()"只返回一个双精度字段"。如果Pt()不计算什么;如果只是:

double Pt() const { return someDouble; }

,那么这应该不是问题(前提是someDouble有类型 double )。 扩展的精度可以表示所有可能的双精度正是值。