MIT 6.5151课程close-enuf?特殊定义的设计原因及适用场景问询
close-enuf?函数的设计逻辑与优势分析 函数定义与背景
课程代码库中定义的close-enuf?函数用于浮点数近似相等判断,代码如下:
(define (close-enuf? h1 h2 tolerance) (n:<=(n:magnitude(n:-h1 h2)) (n:* .5 (n:max tolerance flo:ulp-of-one) (n:+ (n:magnitude h1) (n:magnitude h2) 2.))))
该函数被封装进(assert-close z1 tolerance z2)宏中用于测试,典型测试用例覆盖工程计算场景(如气体定律计算、单位转换):
(assert-close .02404848592336704 1e-4 (gas-law-volume (psi-to-nsm 14.7) ((compose celsius-to-kelvin fahrenheit-to-celsius) 68) 1)) ... (assert-close 1467.5286508533222 1e-4 ((unit:expt (unit:invert inch-to-meter) 3) (gas-law-volume (psi-to-nsm 14.7) ((compose celsius-to-kelvin fahrenheit-to-celsius) 68) 1)))
其中前缀n:表示MIT/GNU Scheme的原语过程副本,书中解释:
由于我们预期在MIT/GNU Scheme系统中会改变许多运算符的含义,因此我们创建了一组特殊的运算符,命名了我们以后可能需要的原语过程。我们给这些副本命名时加上前缀
n:。在MIT/GNU Scheme中,原始的原语过程始终以其原始名称在系统全局环境中可用,因此我们也可以从那里获取它们。
当tolerance非极小值时,该函数等价于:|h1 - h2| <= ((|h1| + |h2| + 2) / 2) * tolerance
属于相对容差比较,但存在特性:当|h1| + |h2|极大时,容易返回#t(例如(close-enuf? 1e4 (+ 1 1e4) 1e-4)返回#t)。
设计原因与优势
1. 平衡相对容差与小数值场景的合理性
普通相对容差比较在数值接近0时会出现问题:如果|h1| + |h2|趋近于0,相对容差的阈值会变得极小,导致对接近0的数值要求过于严格。而该函数通过加入+2.项,保证了当|h1| + |h2|极小时,阈值下限为tolerance(当tolerance大于flo:ulp-of-one时),避免了小数值场景下的误判。
2. 适配工程计算的误差需求
课程中的测试场景多为物理/工程计算,这类场景的核心需求是相对误差可控:
- 数值跨度极大(从0.02级到1000级),相对容差能保证不同量级的数值都使用合理的误差标准;
- 大数场景下,微小的绝对误差对应的相对误差往往在工程允许范围内(比如1e4和1e4+1的相对误差为1e-4,刚好匹配测试用例中的tolerance),此时返回
#t完全符合工程计算的合理性。
3. 规避浮点数精度陷阱
函数中使用(n:max tolerance flo:ulp-of-one),将比较阈值与机器最小精度单位(flo:ulp-of-one)绑定。当用户设置的tolerance小于机器能表示的最小精度时,自动使用机器精度作为基准,避免要求浮点数做超出其表示能力的精确比较——这是对浮点数固有精度限制的适配,防止无意义的严苛判断。
内容的提问来源于stack exchange,提问作者An5Drama

