Why log is Non-Monotonic in PHP and Lua Due to Hidden Implementation Tricks
I discovered that the log function in PHP and Lua breaks mathematical monotonicity in rare cases. This happens because these languages switch between different calculation methods for specific bases like 10 and 2, creating a discontinuity. While other languages like Python and Rust handle this consistently, PHP and Lua's optimization choices lead to unexpected results that confuse developers relying on standard floating-point behavior.
I like it when people value correctness.
- ReactiveJelly
> Everyone already knows floating-point operations are imprecise and it wouldn’t be fun to blog about.
Wellll... Yes and no. And I want to nitpick "FP ops are imprecise" because it's important sometimes.
It does come up in Lua - Lua uses 64-bit double floats for everything, _even array indexing_, because they have 53 bits of mantissa and they're guaranteed to represent all 32-bit integers with 100% precision.
I just opened Lua and got `2 ^ 32 == 4294967296.0` and `2 ^ 32 + 1 == 4294967297.0`. You can store 4 billion things in a Lua table and access them with double float indexes.
The usual "0.1 + 0.2 != 0.3" is _not_ imprecision. You could dedicate 1,000 bits to a float and still find some example of a number that's trivial to represent in decimal but repeats forever in binary.
While I'm on it, fixed-point and floating-point aren't magically different. Floats work better on very big values. Fixed-points are more predictable but run out of range easily when squaring numbers. This comes in 3D math when finding the length of vectors or normalizing vectors.
The PlayStation 1 didn't have jiggly vertices and warpy textures because of fixed-point. It had jiggly vertices because its GPU didn't have subpixel-precise rendering, and it only had 16 bits of precision. It had warpy textures because it had affine texture mapping. The N64's GPU had more bits of precision and it had perspective-correct texture mapping. There's no 16-bit floating-point format that would have saved the PS1 fro […]
- lioeters
Recently I was reading an article on the EML operator (exp-min-log) that uses exponentiation and logarithm to build elementary math functions including arithmetic operations. There was a table of results from testing across languages.
Language Result of 2 x 3 Error
---
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
It was speculated that this miniscule margin of error, 1 ULP (unit in the last place), is likely due to the difference in how log() is implemented by the language. Supposedly Python, PHP, and Rust use LLVM's libm (C math library) but maybe Go and Node.js internally compile to CPU instructions directly? There was no evidence presented, so I was skeptical of this explanation.
- cwt137
I find it interesting the blog author tied PHP and Lua together. PHP uses a JIT from Lua. Is this related to the log issue?