Live data from Hacker News

Log is non-monotonic in PHP and Lua

purplesyringa.moe

1–10 of 58 posts

Re: Log is non-monotonic in PHP and Lua

#3
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.

Re: Log is non-monotonic in PHP and Lua

#5
post #2

> you can compute any logarithm from two base-e ones. They need not be base e. Any base will do as long as it is the same for both the numerator and the denominator

Yeah, I simplified it a little. Though I must say I'm surprised pretty much every library I looked at uses the natural logarithm specifically, and not log2, which would seemingly be easier to compute with floats. Does anyone here know why, by any chance?

Re: Log is non-monotonic in PHP and Lua

#6
post #4

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?

> PHP uses a JIT from Lua.

Wow, TIL! I was certain this is a mistake, but apparently PHP uses DynAsm (https://wiki.php.net/rfc/jit), which was developed for LuaJIT (https://luajit.org/dynasm.html). Cool stuff!

To answer your question, probably not. I dated the PHP change that added this "optimization" back to 2014 (https://github.com/php/php-src/commit/b547e1358d3846fad4cd0c...), while DynAsm only started being used around 2019 (https://wiki.php.net/rfc/jit). I think this is just convergent evolution.

(I'm putting "optimization" in quotes because it changes semantics.)

Re: Log is non-monotonic in PHP and Lua

#7
post #2

> you can compute any logarithm from two base-e ones. They need not be base e. Any base will do as long as it is the same for both the numerator and the denominator

Yeah, I simplified it a little. Though I must say I'm surprised pretty much every library I looked at uses the natural logarithm specifically, and not log2, which would seemingly be easier to compute with floats. Does anyone here know why, by any chance?

Not sure why they wouldn't use log2 internally, but the mathematical reason is that e^x is the function whose derivative is itself, so it and the natural logarithm are fundamental to lots of other theories/operations like Euler's formula.

Re: Log is non-monotonic in PHP and Lua

#8
post #3

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.…

Python, PHP, and Rust do indeed use libm. Not LLVM's libm specifically, though, but rather just about anything they can find in runtime, and those libraries can have suboptimal accuracy.

Node uses V8, which notably implements a ton of math manually, because it needs the results to be deterministic across devices. It seemingly uses LLVM's libm, which IIRC promises 0.5 ulp for most operations. (https://github.com/v8/v8/blob/f24c62fbc342d616032734b714116e...)

Go avoids dynamic linking, so they also have their own implementation. (https://github.com/golang/go/blob/543ead71a8e7acc2bd6f326327...) They only promise 1 ulp, but I guess in this specific case it works out better than approximations used by the default libm on their system by pure chance?

Re: Log is non-monotonic in PHP and Lua

#9
post #3

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.…

Python, PHP, and Rust do indeed use libm. Not LLVM's libm specifically, though, but rather just about anything they can find in runtime, and those libraries can have suboptimal accuracy. Node uses V8, which notably implements a ton of math manually, because it needs the results to be deterministic across devices. It seemingly uses LLVM's libm, which IIRC promises 0.5 ulp for most operations. ( https://github.com/v8/v…

Nice, thanks for tracking down those links, fascinating. Apparently being off by 1 ULP is within IEEE 754 floating-point spec, and "effectively negligible".

Re: Log is non-monotonic in PHP and Lua

#10
post #9

Earlier quoted context omitted.

Python, PHP, and Rust do indeed use libm. Not LLVM's libm specifically, though, but rather just about anything they can find in runtime, and those libraries can have suboptimal accuracy. Node uses V8, which notably implements a ton of math manually, because it needs the results to be deterministic across devices. It seemingly uses LLVM's libm, which IIRC promises 0.5 ulp for most operations. ( https://github.com/v8/v…

Nice, thanks for tracking down those links, fascinating. Apparently being off by 1 ULP is within IEEE 754 floating-point spec, and "effectively negligible".

Correctly rounding transcendental functions is very difficult because of something called the table maker's dilemma, so the standard didn't want to impose a potentially extreme performance cost if they didn't have to.

Correctly rounded single precision functions at a reasonable cost are much easier these days. LLVM 18+ implements them, but the gnu libraries have been slower to improve.

Post reply on HN