浮点数很奇怪
浮点运算经常让程序员感到意外,因为它只是对实数的近似,在减去两个几乎相等的数等常见操作中会丢失精度,文中通过 `exp(x) - 1` 相关示例和“抵消”数值噪声的技巧对此作了说明。评论者讨论了数值分析中的灾难性消去、稳定改写(例如使用 `expm1` 或替代代数形式),以及像 Kahan 求和或 Herbie 这类帮助重写公式以提高精度的工具。一个重要主题是浮点数何时不适合使用——尤其是金钱和记账场景——以及教育、库和替代数值类型(十进制、定点、大整数)对于安全进行数值计算有多么关键。
浮点数行为与灾难性消去
- 许多评论强调,浮点数与其说是“奇怪”,不如说是对实数的有限精度近似;在减去两个非常接近的值时,显著数字不可避免地会丢失。
- 文中的“魔法”示例也被讨论:函数
g(x) = (exp(x)-1)/log(exp(x))在很大程度上抵消了分子和分母之间共享的舍入噪声,而f(x) = (exp(x)-1)/x则没有。 - 这种抵消只在一定范围内有效;对于非常小的
x,它会失效(例如当log(exp(x))舍入为 0 时会出现除以零)。 - 有几条评论指出,这关乎算法稳定性,而不只是“浮点数不好”。
数值技术与工具
- 推荐:在接近零时对
exp(x)-1使用expm1(x);文中重点提到了 Python 的math.expm1,包括其受 Kahan 启发的实现。 - 一般经验法则:
- 避免减去几乎相等的数。
- 避免把数量级相差很大的数相加。
- 对求和使用 Kahan 求和或分组求和,或者先加较小数量级的项。
- 当数值接近 1 时,计算相对于 1 的偏移(例如
expm1)。
- Herbie 被提到是一种可建议更稳定改写形式的工具。
- 还提到了一些库(例如
decimal.js),以及许多语言暴露了 IEEE-754 和 C 的libm函数,有时还会针对已知的不准确性做自己的修复。
浮点数 vs 金钱与商业计算
- 强烈观点:记账绝不要使用浮点数;应使用整数或十进制类型,以避免在发票、定价和客户可见总额上出现舍入意外。
- 反对观点:对于许多金融模型(利率、债券、期权、风险模拟),浮点数才是合适的工具;输入的不确定性远大于分到分的舍入误差,而整数/定点方法会变得复杂且脆弱。
- 折中方案:
- 在存储、结算和面向客户的算术中使用精确的整数/十进制格式。
- 在分析/预测层中使用浮点数,此时精确到分没那么关键。
- 文中讨论了十进制浮点数(IEEE 十进制、IBM 硬件)和定点“整数分”作为替代方案,它们各有取舍。
教育与数值学科
- 不少人认为,误解源于糟糕的教育:学生先学实数,再突然接触浮点数,却没有被教过表示方式、舍入或稳定性。
- 数值分析被强调为一个完整学科;算法稳定性和误差分析与数值格式的选择同样重要。