0

这段代码取自 Sussman and Wisdom 的Structure and Interpretation of Classical Mechanics,其目的是导出(接近)主机支持的最小正浮点数。 https://github.com/hnarayanan/sicm/blob/e37f011db68f8efc51ae309cd61bf497b90970da/scmutils/src/kernel/numeric.scm

在 DrRacket 中运行它会在我的机器上生成 2.220446049250313e-016。

我的问题,是什么导致它甚至返回一个值?这段代码是尾递归的,在某些时候计算机不能再除以 2 是有意义的。为什么它不抛出?

(define *machine-epsilon*
  (let loop ((e 1.0))
     (if (= 1.0 (+ e 1.0))
         (* 2 e)
         (loop (/ e 2)))))
*machine-epsilon*
4

4 回答 4

4

这段代码是尾递归的,在某些时候计算机不能再除以 2 是有意义的。为什么它不抛出?

不,想法不同:在某些时候计算机仍然可以除以 2,但结果 ( e) 变得与 0 无法区分 [更新:仅在浮点加法的上下文中- 评论中提到的非常好的点] ( e + 1.0 = 1.0,这正是if正在检查的子句)。我们肯定知道e“从机器的角度来看”前一个仍然大于零(否则我们不会到达当前执行点),所以我们简单地 return e*2

于 2020-01-21T08:29:47.550 回答
1

当您摆脱令人困惑的 let 表示法时,它会变得更加清晰。

(define (calculate-epsilon (epsilon 1.0))
  (if (= 1.0 (+ 1.0 epsilon))
      (* epsilon 2)
      (calculate-epsilon (/ epsilon 2))))

(define *machine-epsilon* (calculate-epsilon))

代码实际上是做什么的。所以现在我们看看命名的 let 表达式有什么好处。它在本地定义函数并运行它。只是函数的名称 asloop非常不精确和令人困惑,而 epsilon to 的命名e是一个非常不愉快的选择。命名是可读代码最重要的事情。

因此,这个 SICP 示例应该是错误命名选择的示例。(好吧,也许他们是有意训练学生的)。

命名的 let 定义并调用/运行一个函数/过程。避免它会导致更好的代码 - 因为更清晰。

在普通的 lisp 中,这样的结构会更清晰地表达:

(defparameter *machine-epsilon*
  (labels ((calculate-epsilon (&optional (epsilon 1.0))
              (if (= 1.0 (+ 1.0 epsilon))
                  (* epsilon 2)
                  (calculate-epsilon (/ epsilon 2)))))
    (calculate-epsilon)))

在 CLISP 实现中,这给出了:1.1920929E-7

于 2020-01-22T07:25:52.897 回答
1

这种形式的 let-binding是递归的语法糖。

在您掌握语言并尽可能多地使用内核语言编写之前,您可以避免使用过多的语法,以专注于本质问题。例如,在完整的 SICP 文本中,从不指定此语法糖进行迭代。

迭代的 r6rs 定义在这里

于 2020-01-22T12:06:46.067 回答
1

这段代码的目的不是找到机器可以支持的最小浮点数:它是找到最小的浮点数,epsilon这样(= (+ 1.0 epsilon) 1.0)是错误的。这个数字很有用,因为它是您从添加数字中得到的错误的上限。特别是您所知道的是,例如,(+ x y)在 [(x+y)*(1 - epsilon), (x+y)* 范围内(1 + epsilon)],其中第二个表达式+&c 表示对数字的理想运算。

特别(/ *machine-epsilon* 2)是一个完美的数字,(/ *machine-epsilon* 10000)例如,并且对于许多合理的值(* (/ *machine-epsilon* x) x)将非常接近。这只是事实。*machine-epsilon*x(= (+ (/ *machine-epsilon* 2) 1.0) 1.0)

我对浮点标准不够熟悉,但您可能想到的数字是 Common Lisp 所称least-positive-double-float的(或其变体)。在球拍中,您可以通过以下方式得出一些近似值

(define *least-positive-mumble-float*
  ;; I don't know what float types Racket has if it even has more than one.
  (let loop ([t 1.0])
    (if (= (/ t 2) 0.0)
        t
        (loop (/ t 2)))))

我不确定这是否允许引发异常:它在实践中并没有得到一个看起来合理的答案。

于 2020-01-22T17:51:17.157 回答