PHP与Lua的log函数为何非单调?
Log is non-monotonic in PHP and Lua
数学上,若底数a大于b且都大于1,log_a(x)应小于log_b(x)。但在PHP和Lua中,特定情况下这一规律会失效。这并非普通的浮点数精度问题,而是源于实现细节:当底数为10或2时,这两个语言会直接调用log10或log2函数,而非通用的换底公式。这种混合策略在边界处导致了不连续性,使得计算结果出现反直觉的翻转。相比之下,Python、Rust和C#则表现正常。文章指出,这虽非严格意义上的bug,却是浮点运算中因实现决策不当而引发的典型问题,揭示了看似简单的数学函数背后隐藏的复杂性。
IEEE-754标准本身极其稳健,但这里或那里的草率实现决策却会污染它,导致人们将几乎所有浮点错误都归咎于固有的不精确性。
HN 评论区
16- ReactiveJelly
> 大家都知道浮点运算不精确,写博客讲这个也没啥意思。
好吧……既对也不对。我想挑挑“浮点运算不精确”这句话的刺,因为这有时候挺重要的。
这在 Lua 里确实是个问题——Lua 对所有东西都用 64 位双精度浮点数,_连数组索引也不例外_,因为它们有 53 位尾数,能保证 100% 精确地表示所有 32 位整数。
我刚才打开 Lua 试了一下,`2 ^ 32 == 4294967296.0` 和 `2 ^ 32 + 1 == 4294967297.0`。你可以在 Lua 表里存 40 亿个东西,用双精度浮点数做索引来访问它们。
通常说的“0.1 + 0.2 != 0.3”_并不是_精度问题。就算你给浮点数分配 1000 位,照样能找到一些在十进制里很简单、但在二进制里无限循环的数。
既然说到这儿,定点数和浮点数也没啥本质区别。浮点数处理极大值时表现更好。定点数更可预测,但平方运算时很容易超出范围。这在 3D 数学里很常见,比如计算向量长度或归一化向量时。
PlayStation 1 之所以出现顶点抖动和纹理扭曲,并不是因为用了定点数。顶点抖动是因为它的 GPU 没有亚像素级渲染能力,而且只有 16 位精度。纹理扭曲是因为它用的是仿射纹理映射。N64 的 GPU 精度更高,还用了透视校正纹理映射。根本不存在什么 16 位浮点格式能拯救 PS1 的 […]
- lioeters
最近我读到一篇关于 EML 算子(exp-min-log)的文章,它利用指数和对数来构建包括算术运算在内的基础数学函数。文章里有一张跨语言测试结果的表格。
语言 2 x 3 的结果 误差
---
Node.js v25.3.0 6.000000000000000 0
Python 3.9.6 6.000000000000001 8.88e-16
PHP 8.5.1 6.000000000000001 8.88e-16
Go 1.26.2 6.000000000000000 0
Rust 6.000000000000001 8.88e-16
有人推测,这微小的误差(1 ULP,即最后一位的单位)可能是因为各语言对 log() 的实现方式不同。据说 Python、PHP 和 Rust 用的是 LLVM 的 libm(C 数学库),但也许 Go 和 Node.js 内部是直接编译成 CPU 指令的?文章没提供证据,所以我对这个解释持怀疑态度。
- cwt137
我觉得博主把 PHP 和 Lua 扯到一起挺有意思。PHP 用的是来自 Lua 的 JIT。这和 log 的问题有关吗?