【问题标题】:Testing a floating point error agnostic = in Common Lisp在 Common Lisp 中测试浮点错误不可知 =
【发布时间】:2020-12-29 20:51:10
【问题描述】:

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

;;; 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))

现在,当我通过将浮点数与 添加 的随机分数进行比较来测试函数时,响应是(至少就我对函数的测试而言)是积极的,例如:

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

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


(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

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

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

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

更新 f = 1.0 的情况也与其他情况一样,当我使用双浮点 1 时,即 1d0 或这样做:

(setf *read-default-float-format* 'double-float)

【问题讨论】:

  • (1) 你说的是随机“分数”,但这有点令人困惑 (2) 关于执行时间“总是”会在第一个 NIL 早期终止循环,而对于结果 T 所有测试都必须通过
  • 1) 对于随机分数,我只是指 (random flerag-devi) 2) 我知道这一点,我只是好奇为什么提前终止会发生在 f = 1.0(在 `*read-default-float-format* = single 下)而不是其他实数。

标签: common-lisp


【解决方案1】:

欢迎来到浮点运算:一个可怕的陷阱等待着即使是警惕和粗心的人也会立即被咕噜声吃掉。请参阅this,您可以获得here 的 PDF 副本(此副本可能合法,但像 Oracle 之类的人也有免费提供的版本),而且本文并非免费提供给所有人,这有点愚蠢。

这里发生的情况是,假设单个浮点数,在某些时候(random 0.0005) 会产生一个数字r(例如4.9999706E-4),它与0.0005 足够接近,即(- 1.0 r)0.9995 .但是(- 1.0 0.9995) 0.0005 更大:特别是对于单花车(也许也适用于双打,但绝对是单打)存在单花车r 这样

(and (< r 0.0005)
     (> (- 1.0 (- 1.0 r)) 0.0005)))

大概可以系统地列举出这样的单个浮点数,但是我太懒了。

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

像下面这样的定义(这是从以前的错误修改而来的)可能适合。

(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))))

但是,如果您以摄氏度为单位测量温度,那么您不希望事情在接近零时变得特别挑剔(除非您非常关心结冰,在这种情况下您可能会这样做)。

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


作为不相关的说明:您的 loop 语法不合法。 with 需要在 for 或其他迭代构造之前。

【讨论】:

  • 感谢您的评论!我可以在一个简单的循环中用一些整数找出其中的一些 horrible rs。除非我完全弄错了,否则执行测试任务的最快和最安全的方法是将浮点数作为字符串转换为某个小数点,例如 (string= (format nil "~D" 0.465188032387971243567) (format nil "~D" 0.46567714) :end1 5 :end2 5) 以检查该点之后的第三位数字。
  • (close-enough-p 0.0 0.000000000000000001) ;NIL 显然因为(&lt;= 0.0 1e-20 0.0) 对于任何非标准化浮点数都是错误的(对于 f1 乘以零)。
  • @Student:我的close-enough-p 是针对量级很重要的情况:如果a >> b,a 不够“接近”。我试图想出比打印它们更糟糕的比较浮点数的方法,但我不能:如果你想测试浮点实现,那么你想解码浮点数并查看位。如果你想知道 x = y +/- delta y,然后编写代码来检查它,并接受会存在奇数边界情况的事实,这很好:浮点数对精确数字没有用处。
  • 实际上我是在比较像素,对于这些像素,在长时间的计算中微小的分数移位可能会导致图形发生相当大的变化。由于像素没有物理物质,我认为在我的情况下与 scaled delta 进行比较并不是一个好主意(因为根据其他标准,单个像素不应该失去重要性)。无论如何感谢您的讨论!
  • @Student 那么我认为你的相等性测试很好,但你不能指望它总是在非常接近区间边缘给出数学上正确的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-25
  • 1970-01-01
  • 2011-09-05
  • 1970-01-01
  • 2015-04-04
相关资源
最近更新 更多