You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双精度浮点数幂运算舍入误差问题及Common Lisp解决方案咨询

LeetCode 50题:Pow(x, n) Common Lisp实现的舍入误差问题

问题背景

我正在为LeetCode第50题“Pow(x, n)”(实现pow(x, n),计算x的n次幂xⁿ)开发Common Lisp解决方案,当前的直接实现代码如下:

(defun pow (x n)
  (declare (ftype (function (double-float fixnum) double-float) pow))
  (if (zerop n)
      1d0
      (let ((r 1d0))
        (dotimes (i (abs n)) (setf r (* r x)))
        (if (plusp n)
            r
            (/ 1d0 r)))))

(pow 2.1d0 3)
=> 9.261000000000001d0

为何结果小数点后第15位会出现1?如何消除该舍入误差?运行环境为Linux下的SBCL 2.3.4。


误差原因

这个问题的核心在于双精度浮点数(double-float)的二进制存储特性:

  • 十进制的2.1无法被二进制浮点数精确表示,它在二进制中是无限循环小数,存储时会被截断为最接近的可表示近似值。
  • 连续三次乘以这个不精确的近似值时,每次乘法的微小误差会累积,最终导致结果末尾出现额外的1。

解决方法

方法1:使用精确十进制运算(适合高精度场景)

Common Lisp支持十进制浮点数类型(decimal-float),可以避免二进制浮点数的精度问题。修改代码使用十进制类型:

(defun pow-decimal (x n)
  (declare (ftype (function (decimal-float fixnum) decimal-float) pow-decimal))
  (if (zerop n)
      1.0d0M  ; 十进制浮点数字面量
      (let ((r 1.0d0M))
        (dotimes (i (abs n)) (setf r (* r x)))
        (if (plusp n)
            r
            (/ 1.0d0M r)))))

(pow-decimal 2.1d0M 3)
=> 9.261d0M

注意:十进制浮点数运算速度通常比二进制双精度慢,仅适合对精度要求极高的场景。

方法2:格式化输出时截断或四舍五入

如果只需要在展示时消除误差,可以对结果进行格式化,保留合理的小数位数:

; 保留12位小数输出
(format t "~,12F~%" (pow 2.1d0 3))
=> 9.261000000000

; 四舍五入到指定精度
(round (* (pow 2.1d0 3) 1d12)) 1d12
=> 9.261d0

方法3:用快速幂减少误差累积(同时优化效率)

当前线性遍历的实现不仅效率低(时间复杂度O(n)),还会增加误差累积次数。使用快速幂算法(时间复杂度O(log n))可以减少乘法次数,从而降低误差累积:

(defun fast-pow (x n)
  (declare (ftype (function (double-float fixnum) double-float) fast-pow))
  (cond
    ((zerop n) 1d0)
    ((minusp n) (/ 1d0 (fast-pow x (- n))))
    ((oddp n) (* x (fast-pow x (- n 1))))
    (t (let ((half (fast-pow x (/ n 2))))
         (* half half)))))

(fast-pow 2.1d0 3)
=> 9.261000000000001d0

注:快速幂虽减少了乘法次数,但由于2.1本身的二进制表示不精确,结果仍会有微小误差,结合格式化输出即可解决展示问题。


内容的提问来源于stack exchange,提问作者Jérôme Radix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 10:07:29