⚙️ 第三章 计算机在计算小数时会出错的原因
📄 一句话概括

先分清场合——图形与信号可以带误差,钱和账号一位都不能错;要精确就换表示法:整数、Decimal、分数。

📝 本节和前面几节的关系

3.6 把误差看清楚了,这一节给出处理它的办法,也是本章的收口。

一、先分清:什么时候可以不管

可以带误差的场合:图形渲染、信号处理、科学计算、机器学习。 这些场合里,第 15 位的误差根本没人看得见,硬要消掉反而拖慢速度。 一位都不能错的场合:金额、账目、库存、序号、密码、坐标索引。 第一句话就该问自己:这个数字错了,会不会有人来找我? 会,就必须换表示法。

二、办法一:改用整数

最省事也最可靠的一招——把单位放大,改用整数。 金额以【分】为单位存:19.99 元存成 1999,加法减法全是整数运算,一位不差。 要显示的时候再除回来。除法只做一次,误差最多出现一次。 这也是各家支付系统在后台普遍的做法。

三、办法二、三:换一个更合适的表示法

Decimal:按十进制算,专为财务设计。0.1 加 100 次正好是 10。 分数(fractions):存 1/3 就真的存 1/3,不做除法就没有误差。 两者都比浮点慢、也比浮点占地方——这是【精确】的代价,付得值不值看场合。

四、办法四、五:改掉两个坏习惯

比较判等要带容差:不要写 a == b,写成 abs(a - b) < 1e-9。 能一次算完就别累加:求和尽量用公式或库函数,别一步一舍。 两条都极其便宜,改起来只是一行代码。 记住那句话就够了:要精确,就别用浮点;非用不可,就别判等、别累加。

五、动手 Demo:避开小数误差的几种办法

误差来自【能表示的位数有限】,所以办法都围绕【别拿浮点存精确值】。

from decimal import Decimal
s = 0.0
for _ in range(100):
    s += 0.1
d = sum(Decimal("0.1") for _ in range(100))
print("浮点   ", s, s == 10.0)
print("整数   ", 100, 100 == 100)
print("Decimal", d, d == Decimal("10"))

实跑输出:

--------------------------------------------------------------------
避开小数误差的几种办法
--------------------------------------------------------------------

**误差来自【能表示的位数有限】,所以办法都围绕【别拿浮点存精确值】。**

  办法           怎么做                       适合什么场景
  -----------------------------------------------------------
  改用整数       金额以【分】为单位存         钱、计数、库存
  用 Decimal     decimal 模块按十进制算       财务、报表
  用分数         fractions 模块保真           比例、精确推导
  比较设容差     abs(a-b) < 1e-9 判定相等     任何判等
  别做累加       能一次算完就不要循环加       统计求和

  实跑三行对照:

    浮点     0.1 加 100 次 = 9.99999999999998       等于 10? False
    整数     1   加 100 次 = 100                    等于 100? True
    Decimal  0.1 加 100 次 = 10.0                   等于 10? True

  **一条原则:要精确,就别用浮点。**
  浮点是为【大范围、可带误差】的量设计的——科学计算、图形、信号处理都合适;
  钱、账、序号这类【一位都不能错】的东西,从一开始就该换表示法。

浮点累加 100 次得 9.99999999999998;整数与 Decimal 都精确得到结果。 一条原则:要精确,就别用浮点——钱、账、序号从一开始就该换表示法。

30 秒实验

  1. 把一段涉及金额的代码改成【以分为单位】的整数运算,看还有没有误差。
  2. 找出你项目里所有直接比较浮点数的语句,给它们加上容差。

常见误区

  1. 一律改成 Decimal【以防万一】。可带误差的场合用它只会白白变慢。
  2. 用四舍五入显示来掩盖误差。误差还在,只是被藏起来了。
自测三问(点开对答案) 1. 什么样的场合必须避免浮点误差? —— 金额、账目、库存、序号、密钥——错了会有人找上门的那类数据。 2. 处理金额最常用的办法是什么? —— 以最小单位(如【分】)存整数,显示时再换算回去。 3. 比较两个浮点数该怎么写? —— 不要直接判等,改成判断差值是否小于一个容差(如 abs(a-b) < 1e-9)。

相关笔记