【问题标题】:When is it useful to compare floating-point values for equality?什么时候比较浮点值是否相等有用?
【发布时间】:2018-09-12 00:01:36
【问题描述】:

我似乎看到人们一直在这里询问有关比较浮点数的问题。规范的答案总是:只要看看这些数字是否在彼此之间的某个小数字之内……

所以问题是:为什么你需要知道两个浮点数是否相等?

在我所有的编码生涯中,我从来不需要这样做(尽管我承认即使 也不是无所不知的)。从我的角度来看,如果您尝试使用浮点数并且出于某种原因想知道数字是否相等,您可能应该使用整数类型(或支持它的语言中的十进制类型)。我只是错过了什么吗?

【问题讨论】:

  • 如果您要投反对票,请解释为什么我的问题值得投反对票
  • 不同的项目需求需要不同的数据类型。仅仅因为从未使用过它,并不意味着从未有过使用它的用例。
  • @Ripi2:StackOverflow 是一个收集和保存信息的网站。你能举一些例子,而不是仅仅说有人会发现吗?后者似乎没有帮助。
  • 我提名重新提出问题。 StackOverflow 是一个提供一般编程知识的地方,而不仅仅是对错误或特定技术细节的回答。探索特定浮点运算的用途是有用的知识,尤其是对于有争议的运算。
  • 如何排序或搜索列表中的项目?

标签: algorithm types floating-point integer


【解决方案1】:

比较浮点数是否相等的几个原因是:

  • 测试软件。给定的软件应该符合精确的规范,精确的结果可能是已知的或可计算的,因此测试程序会将主题软件的结果与预期结果进行比较。
  • 执行精确算术。精心设计的软件可以执行精确的浮点运算。在最简单的情况下,这可能只是整数运算。 (在提供 IEEE-754 64 位双精度浮点但仅 32 位整数运算的平台上,浮点运算可用于执行 53 位整数运算。)在执行精确算术时比较相等是与用整数运算比较相等性相同。
  • 搜索排序或结构化数据。浮点值可用作搜索的键,在这种情况下,需要进行相等性测试以确定已找到所查找的项目。 (如果 NaN 可能存在,则会出现问题,因为它们在任何顺序测试中都报告错误。)
  • 避免极点和不连续性。函数在某些点上可能有特殊的行为,其中最明显的就是除法。软件可能需要针对这些点进行测试并将执行转移到其他方法。

请注意,当使用浮点算术逼近实数算术时,只有最后一个相等性测试。 (此示例列表不完整,因此我不希望这是唯一的此类用途。)前三个是特殊情况。通常在使用浮点算术时,一种是逼近实数算术并使用大部分连续函数。连续函数对于浮点运算来说是“可以的”,因为它们以“正常”的方式传输错误。例如,如果到目前为止您的计算产生了一些接近理想数学结果 aa',而您有一个接近于理想数学结果的 b'一个理想的数学结果b,那么计算的总和a'+b'将近似于a+ b.

另一方面,不连续的函数会破坏这种行为。例如,如果我们尝试将一个数字四舍五入到最接近的整数,当 a 为 3.49 时会发生什么?我们的近似值 a' 可能是 3.48 或 3.51。计算舍入时,近似值可能会产生 3 或 4,将非常小的误差变成非常大的误差。在浮点运算中使用不连续函数时,必须小心。例如,考虑计算二次公式 (−b±sqrt(b2-4ac)) /(2a)。如果b2−4ac的计算过程中出现轻微错误,结果可能为负,然后sqrt会返回南。所以软件不能简单地使用浮点运算,就好像它很容易逼近实数运算一样。程序员必须了解浮点运算并警惕其中的陷阱,而这些问题及其解决方案可能因特定的软件和应用程序而异。

相等性测试是一个不连续的功能。它是一个函数 f(a, b),除了沿 a=b 线以外的所有地方都为 0。由于它是一个不连续的函数,它可以将小错误变成大错误——如果用理想数学计算,它可以报告为不相等的相等数,如果用理想数学计算,它可以报告为相等的不等数。

通过这个视图,我们可以看到相等性测试是一般函数类的成员。它并不比平方根或除法更特殊——它在大多数地方是连续的,但在某些地方是不连续的,因此必须小心使用它。这种护理是针对每个应用程序定制的。

我将介绍一个测试相等性非常有用的地方。我们实现了一些指定为忠实四舍五入的数学库例程。例程的最佳质量是正确舍入。考虑一个函数,其精确数学结果(对于特定输入 x)是 y。在某些情况下,y 可以精确地以浮点格式表示,在这种情况下,一个好的例程将返回 y。通常,y 不能完全表示。在这种情况下,它介于可以用浮点格式表示的两个数字之间,一些数字 y0y1子>。如果例程正确舍入,则返回 y0y1 中更接近 的那个是的。 (如果是平局,它会返回一个偶数低位的数字。另外,我只讨论从四舍五入到最近的平局模式。)

如果例程被忠实地四舍五入,则允许返回 y0y1

现在,这是我们想要解决的问题:我们有一些版本的单精度例程,比如 sin0,我们知道它是忠实四舍五入的。我们有一个新版本,sin1,我们想测试它是否忠实地四舍五入。我们有多精度软件,可以非常精确地评估数学 sin 函数,因此我们可以使用它来检查 sin1 的结果是否如实四舍五入。但是,多精度软件速度很慢,我们要测试所有 40 亿个输入。 sin0sin1 都很快,但sin1 允许有与sin0 不同的输出,因为sin1 只需要忠实地四舍五入,而不是与sin0 相同。

但是,sin1 结果中大部分sin0 相同。 (这部分是数学库例程设计方式的结果,在使用一些最终算术运算来提供最终结果之前,使用一些额外的精度来获得非常接近的结果。这往往会得到正确舍入的结果大多数,但有时会滑到下一个最接近的值。)所以我们可以做的是:

  • 对于每个输入,计算 sin0sin1
  • 比较结果是否相等。
  • 如果结果相同,我们就完成了。如果不是,请使用扩展精度软件测试sin1 结果是否如实四舍五入。

同样,这是使用浮点运算的一种特殊情况。但它是平等测试非常有用的一种。最终的测试程序在几分钟而不是几个小时内运行。

【讨论】:

    【解决方案2】:

    我唯一需要的是检查 GPU 是否符合 IEEE 754 标准。 不是。

    无论如何,我没有使用与编程语言进行比较。我只是在 CPU 和 GPU 上运行程序,产生一些二进制输出(没有文字),并将输出与简单的差异进行比较。

    【讨论】:

      【解决方案3】:

      有很多可能的原因。

      因为我更了解 Squeak/Pharo Smalltalk,所以这里有一些简单的例子(它依赖于严格的 IEEE 754 模型):

      Float>>isFinite
          "simple, byte-order independent test for rejecting Not-a-Number and (Negative)Infinity"
      
          ^(self - self) = 0.0
      
      Float>>isInfinite
          "Return true if the receiver is positive or negative infinity."
      
          ^ self = Infinity or: [self = NegativeInfinity]
      
      Float>>predecessor
          | ulp |
          self isFinite ifFalse: [
              (self isNaN or: [self negative]) ifTrue: [^self].
              ^Float fmax].
          ulp := self ulp.
          ^self - (0.5 * ulp) = self
              ifTrue: [self - ulp]
              ifFalse: [self - (0.5 * ulp)]
      

      如果你打开一些 libm 实现并检查,我相信你会发现更多参与 ==... 不幸的是,我不知道如何通过 github Web 界面搜索 ==,但我手动找到了这个julia libm 中的示例(fdlibm 的变体) https://github.com/JuliaLang/openlibm/blob/master/src/s_remquo.c

      remquo(double x, double y, int *quo)
      {
      ...
      fixup:
          INSERT_WORDS(x,hx,lx);
          y = fabs(y);
          if (y < 0x1p-1021) {
              if (x+x>y || (x+x==y && (q & 1))) {
              q++;
              x-=y;
              }
          } else if (x>0.5*y || (x==0.5*y && (q & 1))) {
              q++;
              x-=y;
          }
          GET_HIGH_WORD(hx,x);
          SET_HIGH_WORD(x,hx^sx);
          q &= 0x7fffffff;
          *quo = (sxy ? -q : q);
          return x;
      

      这里,余数函数回答介于 -y/2 和 y/2 之间的结果 x。如果恰好是 y/2,则有 2 个选择(平局)... fixup 中的 == 测试是为了测试 exact 平局的情况(已解决以便始终有偶商)。

      还有一些==zero 测试,例如在 __ieee754_logf(测试普通案例 log(1))或 __ieee754_rem_pio2(模 pi/2 用于三角函数)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多