在 Common Lisp 中测试一个不可知的浮点错误=

Stu*_*ent 7 common-lisp

为了对涉及大量浮点运算的系统进行一些测试,我定义了浮点运算误差的偏差范围,因此如果两个浮点数之间的差异在偏差范围内,则它们被认为在数学上是相等的:

;;; Floating Error Agnostic =
;;; F2 is = to F1 within the deviation range +-DEV

(defparameter *flerag-devi* .0005
  "Allowed deviation range, within which two floats 
should be considered as mathematically equal.")

(defun flerag= (f1 f2 &optional (devi *flerag-devi*))
  ""
  (<= (abs (- f1 f2)) devi))
Run Code Online (Sandbox Code Playgroud)

现在,当我通过将浮点数与添加到其中的随机分数进行比较来测试函数时,响应是(至少对于我对函数的测试)是正的,例如:

(loop repeat 100000000
      with f = 1.0
      always (flerag= f (+ f (random *flerag-devi*)))) ;T

Run Code Online (Sandbox Code Playgroud)

当我从原始浮点数中减去一个随机分数时也是如此,例如:


(loop repeat 100000000
      with f = 19.0
      always (flerag= f (- f (random *flerag-devi*)))) ;T

(loop repeat 100000000
      with f = 3
      always (flerag= f (- f (random *flerag-devi*)))) ;T

Run Code Online (Sandbox Code Playgroud)

但是,当我将原始浮点数设置为 1.0(或 1)时:

(loop repeat 100000000
      with f = 1.0
      always (flerag= f (- f (random *flerag-devi*)))) ;NIL

Run Code Online (Sandbox Code Playgroud)

它总是返回 NIL,这在我看来更奇怪,因为它在评估后立即返回 NIL(在其他情况下,计算 100000000 次需要几秒钟)。到目前为止,这种情况只发生在f = 1.0没有其他数字上,无论对它们的分数进行加法还是减法。任何人都可以在他/她的 Lisp 上重现这种行为吗?任何解释和帮助将不胜感激。

更新f = 1.0当我使用双浮点数 1,即 1d0 或通过执行以下操作时,与其他 案例一样:

(setf *read-default-float-format* 'double-float)
Run Code Online (Sandbox Code Playgroud)

小智 3

欢迎来到浮点算术:在这个世界里,可怕的陷阱等待着即使是谨慎的人,粗心的人也会立即被怪物吃掉。请参阅此内容,您可以在此处获取 PDF 副本(该副本的合法性可能值得怀疑,但像 Oracle 这样的公司也有免费版本),而且这篇论文并非免费提供给所有人,这有点愚蠢。

这里发生的事情是,假设单个浮点数,在某个时刻会产生一个足够接近的数字(random 0.0005)r例如)。但大于:特别是对于单浮点数(也许对于双打,但绝对对于单打),存在单浮点数,使得4.9999706E-40.0005(- 1.0 r)0.9995(- 1.0 0.9995)0.0005r

(and (< r 0.0005)
     (> (- 1.0 (- 1.0 r)) 0.0005)))
Run Code Online (Sandbox Code Playgroud)

也许可以系统地枚举这样的单个浮点数,但我太懒了。

“足够接近”的定义还取决于您所测量的语义是什么。如果您正在比较物体的直径,您可能不希望出现 x 与 y “足够接近”并且 x >> y:1.5E-18在 之内0.0005的 情况1.5E-8,但如果您正在测量质子的半径,您会相差十个数量级,这是一个不小的误差。

像下面这样的定义(这是对之前有问题的定义的修改)可能适合于此。

(defun close-enough-p (f1 f2 &optional (epsilon 0.0005))
  (declare (type real f1 f2 epsilon))
  (let ((delta (* (abs f1) epsilon)))
    (<= (- f1 delta)
        f2
        (+ f1 delta))))
Run Code Online (Sandbox Code Playgroud)

但如果您测量的是摄氏度,那么您不希望事情变得特别接近零(除非您非常关心冰的形成,在这种情况下也许您会这样做)。

然而我不是浮点专家:我只知道这都是一场噩梦。


作为一个不相关的注释:你的loop语法不合法。 with需要在for或其他迭代构造之前。