如何使clojure.test在指定范围内断言两个不同浮点数相等?
解决Clojure.test中浮点数精度导致的测试失败问题
我太懂你遇到的这个糟心问题了——明明逻辑上等价的浮点数,就因为一点点二进制存储的精度偏差,被clojure.test的=断言判为不相等。不过别担心,我们可以给clojure.test实现类似NUnit Is.EqualTo().Within()的近似相等判断,下面是几种实用的方案:
方法一:自定义近似相等断言函数
这是最灵活也最易复用的方式,我们可以写一个通用函数来判断两个数值是否在指定误差范围内相等:
(defn approx-equal? "判断两个数值是否在给定容忍度内近似相等" [expected actual tolerance] (<= (Math/abs (- expected actual)) tolerance)) ;; 测试用例示例 (deftest test-float-approximation ;; 你的场景里1.0和0.999等价,所以把容忍度设为0.001刚好合适 (is (approx-equal? 1.0 0.9999999999999998 0.001)) (is (approx-equal? 0.5 0.4999999999999999 0.001)))
这个函数的逻辑很直观:计算两个数差值的绝对值,只要这个值小于等于你设定的容忍度,就认为它们是相等的。你可以根据业务场景随时调整tolerance参数,适配不同的精度需求。
方法二:直接在断言中写精度判断逻辑
如果只是少数几个测试用例需要处理,也可以不用自定义函数,直接在is断言里写逻辑:
(deftest test-float-direct-check (is (<= (Math/abs (- 1.0 0.9999999999999998)) 0.001)) (is (<= (Math/abs (- 0.5 0.4999999999999999)) 0.001)))
这种方式比较直接,但如果有大量测试用例需要处理,还是自定义函数更简洁易维护。
方法三:用are宏批量测试
如果你有一组浮点数测试用例,可以用are宏批量验证,让代码更整洁:
(deftest test-batch-floats (are [expected actual] (approx-equal? expected actual 0.001) 1.0 0.9999999999999998 0.5 0.4999999999999999 3.0 2.9995 0.1 0.09999999999999998))
补充:为什么会出现这个问题?
本质原因是大部分十进制小数无法被二进制浮点数精确表示,比如0.1在二进制里是无限循环的,存储时会有微小的精度损失。直接用=比较浮点数就会因为这些偏差导致测试失败,必须通过误差范围来做“近似相等”的判断。
内容的提问来源于stack exchange,提问作者Gabriel
相关产品推荐
相关产品推荐

