大数计算实战:Python decimal、gmpy2 与 NumPy 高精度运算
一、为什么 float 不够用
1 | |
二进制浮点无法精确表示许多十进制小数。float 的有效精度约为 15~17 位,适合工程测量和机器学习中的近似计算,却不适合金额结算、密码学整数和高精度常数。
要先区分三类问题:
- 定点金额:使用整数最可靠,例如以最小单位存储;
- 十进制定点/科学计算:使用
decimal; - 超大整数和高精度数学函数:使用
gmpy2或mpmath; - 向量化近似数值计算:使用 NumPy,但不能因此获得任意精度。
二、四大库对比
decimal 是标准库,支持上下文精度和银行家舍入,适合财务规则。gmpy2 基于 GMP、MPFR 和 MPC,整数和高精度浮点速度很快。mpmath 纯 Python 接口友好,适合任意精度数学函数。NumPy 的 float64 仍是双精度浮点,dtype=object 虽可放 Python 大整数,却失去向量化优势。
安装 gmpy2:
1 | |
Linux 若缺少系统库,可先安装编译依赖;生产环境应固定 Python、编译器和 gmpy2 版本。
三、decimal:金额与定点计算
1 | |
不要从 float 构造 Decimal:
1 | |
getcontext().prec 控制有效数字,不是小数位数;固定小数位应使用 quantize()。在并发代码中可使用 localcontext(),避免全局上下文被其他模块改变。
四、gmpy2 与大整数
1 | |
RSA 素数生成示例:
1 | |
这是演示代码,不是完整 RSA 密钥生成器。真实密码学应使用经过审计的库,如 cryptography,不能自行实现协议、填充和密钥格式。
五、PI 与链上金额
gmpy2 可以计算高精度 PI:
1 | |
这里的 precision 使用二进制位。1000 位十进制约等于 3322 位二进制,设置 3400 可留出余量。
链上金额则优先整数化。若代币最小单位为 10^-18:
1 | |
乘法和除法时要明确舍入方向,不能直接把高精度 Decimal 转成 float。
六、复利计算与性能边界
1 | |
如果需求是金融结算,应按每期规则舍入,而不是只在最终结果舍入。两者可能产生不同结果。性能测试应固定输入、预热运行并统计多次,而不是凭一次执行时间判断“快 100 倍”。
七、踩坑记录
NumPy 的 float64 不能保存 256 位密码学整数。JSON 标准也没有统一的大整数语义,JavaScript 端可能将大整数转为不准确的 Number。接口传输时可以使用字符串,或拆成高低位并明确协议。
gmpy2 的 mpz、mpfr 不是所有 JSON 编码器都认识,序列化前显式转换为字符串。不要用 eval 反序列化大数。对用户输入设置位数上限,避免攻击者提交十亿位整数消耗 CPU 和内存。
八、小结
金额优先使用最小单位整数;需要十进制规则时使用 Decimal;高精度数学和超大整数优先 gmpy2;NumPy 只适合浮点近似和批量向量化。精度、舍入、序列化和性能必须作为同一个系统问题设计。