你有没有遇到过这种情况:明明是个再简单不过的加法,Python 算出来却是 0.30000000000000004;或者两个浮点数看起来一模一样,== 判断却返回 False。我第一次被这个问题坑到,是在做数据清洗时对一个金额列做累计求和,算到最后一位多了几分钱,怎么对都对不上。后来一圈排查下来,问题的根源根本不在业务逻辑,而在浮点数精度。
这篇文章我把浮点数精度这件事从头到尾讲透:先说清楚计算机里浮点数到底怎么存的,再说哪些场景最容易踩雷,最后给出一套完整的、分场景的解决方案。无论你是刚学 Python 的新手,还是常年写数据分析、量化交易策略、后端接口的开发者,只要代码里出现过 float,这篇文章都值得花十分钟读完。
1. 浮点数精度问题的本质:计算机里到底发生了什么
1.1 0.1 这个数,在二进制里根本不存在
很多人一听“浮点数精度”就觉得是 Python 的 bug,其实这口锅 Python 不背。问题的根源在“进制转换”这件事上。
人类日常用的是十进制,但计算机底层只有 0 和 1。十进制小数转换成二进制小数,靠的是“乘 2 取整”的规则:把小数部分不断乘以 2,取整数位作为二进制位,直到小数部分为 0。比如 0.5 转二进制,乘以 2 得到 1.0,取整数位 1,结果是 0.1,干净利落。但 0.1 转二进制呢?0.1 × 2 = 0.2,取 0;0.2 × 2 = 0.4,取 0;0.4 × 2 = 0.8,取 0;0.8 × 2 = 1.6,取 1……这个计算会一直循环下去,永远达不到小数部分为 0。换句话说,0.1 在二进制里是一个无限循环小数,就像 1/3 在十进制里是 0.3333... 一样。
但计算机的存储空间是有限的。Python 里的 float 默认采用 IEEE 754 双精度 64 位格式:1 位符号位、11 位指数位、52 位尾数位。52 位尾数只能存下有限个二进制位,存不下的部分就只能舍入。所以你看到的 0.1,其实是一个“非常接近 0.1 的二进制近似值”,而不是精确的 0.1。
可以自己动手验证一下:
python复制>>> format(0.1, '.20f')
'0.10000000000000000555'
看到没有,第 17 位小数开始就“露馅”了。这就是浮点数精度的本质:绝大部分十进制小数,在二进制里根本没法精确表示,只能存一个近似值。
1.2 误差不是单次运算的问题,而是会累积的
理解了 0.1 本身是近似值之后,0.1 + 0.2 != 0.3 就很好解释了:两个近似值相加,结果也是一个近似值,而这个近似值碰巧不等于 0.3 的近似值。
python复制>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
单次运算的误差可能只有 5e-17 这种量级,看起来无伤大雅。但误差可怕的地方在于它会累积。你在一个循环里做一万次加法,每次误差都往同一个方向偏,最后的结果可能就差出好几分钱;你在科学计算里做矩阵迭代,误差经过指数级放大,整个结果可能直接发散。
还有一个非常经典的坑,是“大数吃小数”:
python复制>>> 1e16 + 1
10000000000000000.0
>>> 1e16 + 1 == 1e16
True
1e16 在二进制下需要一个很大的整数部分,留给小数部分的尾数位就不够了,+1 这种级别的变化根本“挤”不进去。这就好比一个只能装 16 位数字的行李箱,你已经装了 16 位整数,再想塞进一个“1”,就得把后面一位挤掉。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 哪些场景最容易让精度问题“爆雷”
2.1 三类高危特征,中一条就要警惕
不是所有浮点数运算都会出问题,但下面三个特征只要中了一个,你就得提高警惕。
第一是相等比较。只要代码里出现 == 来比较两个浮点数,基本就是在埋雷。0.1 + 0.2 == 0.3 只是一道入门题,真正可怕的是在业务代码里判断“金额是否等于退款金额”“传感器读数是否等于阈值”,这类判断在某个临界点上突然返回 False,排查起来极其痛苦。
第二是累加聚合。大量小数连续相加,误差一步步累积。比如一段循环里不断 total += item_price,跑了几千次之后,total 跟数据库里的精确值对不上是很常见的事。Python 的 sum() 对浮点数的处理也谈不上精确,只是做得比手写循环稍微好一点而已。
第三是连续乘除运算。每一次乘除法都会产生新的舍入误差,而且乘除法会把误差成倍放大。尤其是折扣、税率、汇率这类涉及小数乘法的业务,误差会顺着计算链路层层传递。
