浮点数的相对比较


10

我有一个f(x, y)返回双浮点数的数值函数,该数字实现了一些公式,并且我想检查参数组合的所有解析形式对解析表达式的正确性x以及y我是否感兴趣。比较计算所得的和分析浮点数?

假设两个数字是ab。到目前为止,我一直在确保绝对(abs(a-b) < eps)和相对(abs(a-b)/max(abs(a), abs(b)) < eps)误差均小于eps。这样,即使数字在1e-20左右,它也会捕获数字不准确的地方。

但是,今天我发现了一个问题,其数值a和解析值为b

In [47]: a                                                                     
Out[47]: 5.9781943146790832e-322

In [48]: b                                                                     
Out[48]: 6.0276008792632078e-322

In [50]: abs(a-b)                                                              
Out[50]: 4.9406564584124654e-324

In [52]: abs(a-b) / max(a, b)                                                  
Out[52]: 0.0081967213114754103

因此,绝对误差[50](显然)很小,但是相对误差[52]很大。所以我认为我的程序中有错误。通过调试,我意识到这些数字是不正常的。因此,我编写了以下例程来进行适当的相对比较:

real(dp) elemental function rel_error(a, b) result(r)
real(dp), intent(in) :: a, b
real(dp) :: m, d
d = abs(a-b)
m = max(abs(a), abs(b))
if (d < tiny(1._dp)) then
    r = 0
else
    r = d / m
end if
end function

其中tiny(1._dp)在我的计算机上返回2.22507385850720138E-308。现在一切正常,相对误差为零,一切正常。特别是,上述相对误差[52]是错误的,这仅是由于非正规数的准确性不足引起的。我对rel_error函数的实现正确吗?我是否应该检查一下这个值abs(a-b)是否小于small(= normal),然后返回0?还是我应该检查其他组合,例如 max(abs(a), abs(b))

我只想知道什么是“正确”的方法。

Answers:


11

您可以使用isnormal()math.h(C99或更高版本,POSIX.1或更高版本)直接检查异常情况。在Fortran中,如果模块ieee_arithmetic可用,则可以使用ieee_is_normal()。要更精确地模糊模糊相等,您必须考虑非正规数的浮点表示形式,并确定要使结果足够好意味着什么。

更重要的是,要相信任何一个结果都是正确的,您必须确保在中间步骤中不会丢失太多数字。具有异常的计算通常是不可靠的,应通过在内部重新调整算法来避免这种情况。为确保内部缩放成功,我建议使用激活浮点异常feenableexcept() C99或ieee_arithmeticFortran中的模块。

尽管您可以让应用程序捕获浮点异常引发的信号,但我尝试过的所有内核都会重置硬件标志,因此fetestexcept()不会返回有用的结果。当与一起运行时-fp_trap,PETSc程序(默认情况下)将在出现浮点错误时打印堆栈跟踪,但不会识别出有问题的行。如果在调试器中运行,则调试器将保留硬件标志并中断令人讨厌的表达式。您可以通过fetestexcept从调试器进行调用来检查确切原因,其中结果是以下标志的按位“或”(值可能因计算机而异,请参见fenv.h;这些值适用于带有glibc的x86-64)。

  • FE_INVALID = 0x1
  • FE_DIVBYZERO = 0x4
  • FE_OVERFLOW = 0x8
  • FE_UNDERFLOW = 0x10
  • FE_INEXACT = 0x20

感谢您的出色回答。我在渐近状态下比较的解析表达式是exp(log_gamma(m+0.5_dp) - (m+0.5_dp)*log(t)) / 2m = 234,t = 2000。随着我的增加,它很快就为零m。我要确保我的数字例程将“正确的”数字(至少也返回零)返回至少12个有效数字。因此,如果计算返回的是非正规数,则它只是零,因此应该没有问题。因此,仅比较例程需要对此具有鲁棒性。
昂德里杰·塞蒂克

5

Donald Knuth在“计算机编程艺术”的第2卷“ Seminumerical Algorithms”中提出了一种浮点比较算法的建议。它由Th在C中实现。Belding(请参阅fcmp软件包),在GSL中可用。


2
这是我的Fortran实现:gist.github.com/3776847,请注意,无论如何我都需要显式处理非正规数。否则,我认为这几乎等同于相对误差,唯一的区别是abs(a-b)/max(a, b) < eps,我们没有这样做,而是做abs(a-b)/2**exponent(max(a, b)) < eps了尾数掉落max(a, b),所以在我看来,差别可以忽略不计。
昂德里杰·塞蒂克

5

最佳舍入的非规范化数可能确实具有较高的相对误差。(将其冲洗为零,同时仍将其称为相对错误会产生误导。)

但是接近零,计算相对误差是没有意义的。

因此,即使在达到非正规数之前,您也可能应该切换到绝对精度(即在这种情况下要保证的绝对精度)。

ÿX|ÿ-X|一个bs一个CC+[RË一个CC最大值|X||ÿ|

这样,您的代码用户就可以准确地知道他们真正具有多少精度。


您确定计算接近零的相对误差是没有意义的吗?我认为只有在准确性下降(无论出于何种原因)的情况下,它才是没有意义的。例如,如果由于某些数字问题(例如,减去两个大数字)而导致x <1e-150的准确性下降,那么您是正确的。但是在我的情况下,这些数字似乎一直精确到零,除非它达到非正常数字。因此,在我的情况下,absacc = 1e-320左右,我可以abs(a-b) < tiny(1._dp)像上面一样检查。
昂德里杰·塞蒂克

@OndřejČertík:在这种情况下,请将1e-150替换为1e-300或您可以验证的范围。在非常接近零的任何情况下,您都会产生绝对错误,并且您的错误声明应反映这一点,而不是将相对错误声明为零。
阿诺德·诺伊迈耶

我知道了。我可以确认,所有数字的作品都高于tiny(1._dp)=2.22507385850720138E-308(我在之前的评论中犯了一个错误,是2e-308,而不是1e-320)。所以这是我的绝对错误。然后,我需要比较相对误差。我明白你的意思,我认为你是对的。谢谢!
昂德里杰·塞蒂克

1
|ÿ-X|-一个bs一个CC最大值|X||ÿ|
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.