当前位置: 首页 > news >正文

【Python】整数除法不正确,少1的问题,以及有关浮点数转换的精度问题

1. 问题

今天在做leetcode 不同路径 的时候发现了个问题

对于m=53 n=4

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        rlt = 1
        for i in range(0, m-1):
            rlt *= (m + n - 2 - i)
        for i in range(0, m-1):
            rlt /= (i + 1)

        return int(rlt)

为什么这个结果是 26234

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        up = 1
        down = 1
        for i in range(0, m-1):
            up *= (m + n - 2 - i)
        for i in range(0, m-1):
            down *= (i + 1)

        return int(up/down)

而这个结果是 26235

据GPT描述,两个代码逻辑上是相同的,计算的是组合数 C m + n − 2 m − 1 C_{m+n-2}^{m-1} Cm+n2m1

C m + n − 2 m − 1 = ( m + n − 2 ) ! ( m − 1 ) ! ( n − 1 ) ! C_{m+n-2}^{m-1} = \frac{(m+n-2)!}{(m-1)!(n-1)!} Cm+n2m1=(m1)!(n1)!(m+n2)!

然而,两个代码的计算方式有所不同:

  1. 第一个代码 直接使用浮点除法 rlt /= (i + 1),在 Python 中,浮点数的计算可能会引入精度误差,最终 int(rlt) 向下取整,导致结果偏小。

  2. 第二个代码 分别计算 updown,然后用 int(up/down),由于 Python 的整数运算是精确的,这种方式避免了浮点误差,得到了正确的组合数 26235。

第一个代码因为浮点误差,导致 int(rlt) 取整后比正确值少 1,所以结果是 26234,而第二个代码由于整数计算的精确性,得到了正确答案 26235

2. 思考

即使除法的数学结果是整数,如果中间计算涉及浮点数,可能会因为浮点精度误差导致最终取整时丢失 1。我们来深入分析原因:

① Python 的 / 生成浮点数

Python 里的 / 即使除得的是整数,结果仍然是 float。例如:

print(10 / 2)  # 5.0
print(type(10 / 2))  # <class 'float'>

在代码中,这意味着:

rlt = int(rlt / (i + 1))

即使 rlt / (i + 1) 的数学结果是整数,Python 仍然会先计算 浮点数,然后 int() 强制取整,可能引入误差。


② 浮点数存储精度问题

Python 的浮点数是基于 IEEE 754 标准的 双精度浮点数(double precision)。但 某些整数在浮点数表示中无法精确存储,例如:

x = 10000000000000003
y = 10000000000000002

print(float(x))  # 10000000000000004.0  (存储时发生变化)
print(float(y))  # 10000000000000002.0  (存储正确)

rlt 变大时,比如 C_{m+n-2}^{m-1} 计算过程中,rlt 可能会进入 浮点存储的误差范围,导致浮点数计算不精确。


int() 取整丢失 1

浮点数不能精确表示某些大整数时,取 int() 可能会导致向下取整。假设 rlt / (i + 1) 的数学结果是 26235,但由于浮点精度问题,Python 存储的可能是:

26234.999999999998

此时 int(26234.999999999998) 向下取整,结果变成 26234,比正确答案少了 1。


④ 解决方案

避免浮点数参与计算,直接使用 整数除法 //,确保所有计算都在 整数空间 进行:

rlt //= (i + 1)  # 这样不会有浮点精度问题

这样,每次除法都保证是 整数运算,不会因浮点精度误差导致 int() 取整出错。

3. 进一步理解

为什么

x = 10000000000000003
y = 10000000000000002

print(float(x))  # 10000000000000004.0  (存储时发生变化)
print(float(y))  # 10000000000000002.0  (存储正确)

这个现象是由于 IEEE 754 双精度浮点数(double precision floating point)有限精度 导致的。让我们来深入分析为什么 10000000000000003 在转换成 float 时变成了 10000000000000004.0,而 10000000000000002 仍然能正确存储。

① 浮点数的存储方式

Python 的 float 类型遵循 IEEE 754 双精度浮点数标准,使用 64 位 进行存储:

  • 1 位 表示符号位(正/负)。
  • 11 位 表示指数(决定浮点数的范围)。
  • 52 位 表示 尾数(精确存储的部分)。

由于 52 位尾数最多只能精确存储 52 位二进制数,所以如果一个整数的二进制表示超过 52 位,就必须 进行四舍五入,这可能导致数值发生变化。


1000000000000000310000000000000002 的二进制表示

我们先看看这两个数的 二进制表示(十进制 → 二进制):

10000000000000002
10000000000000002 = 10001110000110111100100110101100111011001000000000000000000010 (二进制)
  • 这个数的尾数(低 52 位)可以被精确存储,所以转换成 float 时不会损失精度。
10000000000000003
10000000000000003 = 10001110000110111100100110101100111011001000000000000000000011 (二进制)
  • 这个数的尾数超过了 52 位的存储限制,所以 IEEE 754 必须进行四舍五入
  • 由于 IEEE 754 采用 “最接近偶数舍入”(round to nearest, ties to even),它会 向上舍入10000000000000004,因此 float(10000000000000003) 变成了 10000000000000004.0

③ 为什么 10000000000000003 变成 10000000000000004.0

当一个整数大到超出 52 位精度范围 时:

  1. 无法精确存储,需要进行近似表示。
  2. IEEE 754 采用“四舍五入到最近的偶数”规则
    • 1000000000000000310000000000000004 更接近 10000000000000004,所以它会被 向上舍入
    • 如果它的二进制表示是 ...00011(奇数结尾),IEEE 754 会向上舍入到 ...00100(偶数)。

所以:

print(float(10000000000000003))  # 10000000000000004.0
print(float(10000000000000002))  # 10000000000000002.0

这个现象就是 浮点数的精度限制 造成的,它在转换时 自动进行了四舍五入,使得 10000000000000003 变成了 10000000000000004.0

总结

  • Python float 遵循 IEEE 754 双精度浮点数(64 位),其中只有 52 位 用于存储尾数,超过部分会 四舍五入
  • 10000000000000002 可以精确存储,因为它的二进制表示没有超出 52 位精度范围。
  • 10000000000000003 由于超出 52 位精度范围,被四舍五入成 10000000000000004.0

写在最后

本文采用了 ChatGPT 辅助进行内容的书写和完善

相关文章:

  • 二:RV1126的VDEC视频解码模块的工作流程
  • AI革命编程学习:Python语法速通与高阶突破全实战(第二部分:AI辅助调试与高阶编程)
  • Flink-DataStreamAPI-生成水印
  • C++使用BFS求解最短路径
  • NS3学习——运行自定义拥塞控制算法步骤
  • 动态规划中固定倒数第二个数与倒数第一个数的区别与应用场景分析 —— 从最长等差数列问题到统计等差数列个数的填表策略对比
  • 关于前后端整合和打包成exe文件的个人的总结和思考
  • 数据集构建与训练前准备
  • VIC模型有哪些优势?适用哪些范围?基于QGIS的VIC模型建模;未来气候变化模型预测;基于R语言VIC参数率定和优化
  • 【Academy】Web 缓存欺骗 ------ Web cache deception
  • 如何实现wordpress搜索自字义字段内容
  • 分享最佳ChatGPT替代11个方案(2025)
  • 计算机组成原理(第六章 总线)
  • 关于在electron(Nodejs)中使用 Napi 的简单记录
  • 内容中台的核心架构是什么?
  • 【在校课堂笔记】Python 第 3 节课 总结
  • FlinkSQL源码笔记
  • ~(取反)在算法竞赛中的常见用法和注意事项
  • 线程管理操作
  • 【CSS3】金丹篇
  • 四川省外卖骑手接单将不再强制要求上传健康证
  • 90后青年学者李海增逝世9个月后文章登上顶刊,同仁缅怀其贡献
  • 金融监管总局将研究出台专门的城市更新项目贷款管理办法:引导大力支持城中村的改造等
  • 前4个月全国新建商品房销售面积降幅收窄,房地产库存和新开工有所改善
  • 博物馆书单|走进博物馆,去体验一场与文明的对话
  • 浙江一教师被指殴打并威胁小学生,教育局通报涉事人被行拘