Python 数值溢出风险分析

1次阅读

python整数不会溢出但Float会静默失准:int为任意精度,float基于ieee 754双精度,超253后无法精确表示整数,导致1016+1==10**16为true、0.1+0.2!=0.3等错误。

Python 数值溢出风险分析

Python 整数不会溢出,但 float 会 silently 失准

Python 的 int 类型是任意精度的,加到内存耗尽前都不会“溢出”,但这是假安全感——真正踩坑的是 float。它底层用 IEEE 754 双精度表示,超过 2**53 后就无法精确表示每个整数,后续运算开始丢位。

常见错误现象:10**16 + 1 == 10**16 返回 True;用 float 做金融计算时出现 0.1 + 0.2 != 0.3;科学计算中迭代误差滚雪球。

  • 使用场景:读取 CSV 数值列(默认转 float)、调用 C 扩展返回 double、与 numpy 交互(np.float64 同构)
  • 关键参数差异:sys.float_info.dig 是十进制有效位数(通常 15),sys.float_info.max 是最大可表示值(约 1.8e308),但“最大”不等于“精确”
  • 性能影响:换成 decimal.Decimalfractions.Fraction 会慢 10–100 倍,别在热循环里用

NumPy 的 int64 / float64 溢出行为完全不同

NumPy 数组用固定宽度类型,int64 加法超限会 wraparound(如 2**63-1 + 1 变成 -2**63),float64 超限则变 infnan,且不报错。

常见错误现象:np.Array([2**63-1], dtype=np.int64) + 1 得到负数;np.log(0) 返回 -inf,后续 np.sqrt(-inf)nan,再参与求和就污染整个结果。

立即学习Python免费学习笔记(深入)”;

  • 使用场景:处理图像像素(uint8)、时间戳(int64 微秒)、机器学习特征缩放
  • 检测方法:启用 np.seterr(all='raise') 捕获 FloatingPointError;对整数数组用 np.iinfo(np.int64).max 做前置校验
  • 兼容性注意:pandas 默认用 float64 存整数列(含 NaN),读大数 CSV 时可能意外降精度,显式指定 dtype={'col': 'Int64'}(大写 I)启用 Nullable Integer

如何判断当前数值是否已失真

不能只看是否报错——大多数溢出或精度丢失是静默发生的。得主动验证。

  • 对 float:用 math.isfinite(x) 排除 inf/nan;用 x == x + 0 检查是否还能加 0(inf 不满足);更严格用 abs(x - round(x)) > 1e-10 看是否本该是整数却漂移了
  • 对整数运算链:用 decimal.Decimal 重跑关键路径做交叉比对,例如 Decimal('1.1') + Decimal('2.2') 对比 1.1 + 2.2
  • 路径依赖:如果输入来自 Struct.unpack('d', ...)ctypes.c_double,直接视为不可信 float,优先转 Decimal 再算

真实项目里最该盯住的三个点

不是所有数值都值得较真,但以下三处一旦出错,debug 成本极高。

  • 数据库 roundtrip:postgresqlNUMERICmysqlDECIMAL 读到 Python 变成 float,再写回去就丢精度。用 SQLAlchemy 时设 type_=Numeric(precision=12, scale=2) 并配 decimal_return_scale=2
  • json 序列化:json.dumps({'x': 1e20}) 输出字符串 "1e+20",但反序列化后是 float,再算 int() 就可能截断。需要自定义 json.JSONEncoder 把大数转字符串,或用 decimal 解析
  • 时间戳计算:time.time() * 1000 是常见写法,但超过 2**53 毫秒(约 285 年后)就会丢毫秒精度。用 time.time_ns()(Python 3.7+)替代

复杂点在于,很多溢出不发生在你写的那行代码,而是在你依赖的库内部——比如某个 requests 响应头里带的时间戳被自动转成了 float。得习惯在关键数据流入口打日志,输出 repr(x) 而不是 str(x)

text=ZqhQzanResources