Project

General

Profile

Actions

Feature #21527

open

Proposal: Math.log1p and Math.expm1

Added by mame (Yusuke Endoh) 6 days ago. Updated 2 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:122898]

Description

Let's add Math.log1p and Math.expm1.

  • Math.log1p(x): Computes Math.log(x + 1)
  • Math.expm1(x): Computes Math.exp(x) - 1

These methods are often more accurate than the straightforward computation, especially when x is close to zero.

# The current approach loses precision
p Math.log(1 + 1.0e-16) #=> 0.0
p Math.exp(1.0e-16) - 1 #=> 0.0

# The proposed methods return the accurate result
p Math.log1p(1.0e-16)   #=> 1.0e-16
p Math.expm1(1.0e-16)   #=> 1.0e-16

Note that they are very standard; the C99 even defines log1p() and expm1(). Other major programming languages (Python, JavaScript, Java, Go, Rust, etc.) also provide them.

PR: https://github.com/ruby/ruby/pull/14087

Updated by Eregon (Benoit Daloze) 3 days ago ยท Edited

I find these method names pretty cryptic, typical of the libc (libm here actually) I guess.
How about:

  • Math.log_of_one_plus(x) / Math.log_one_plus(x)
  • Math.exp_of_minus_one(x) / Math.exp_minus_one(x)

A bit verbose, but I think these methods are (I think) very rarely needed, so that seems acceptable.

Actually, given they are probably so rarely needed, why not just using FFI or Fiddle to call them for the few cases that need them?

Updated by Eregon (Benoit Daloze) 3 days ago

require 'fiddle'
log1p = Fiddle::Function.new(Fiddle.dlopen(nil)["log1p"], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE)
p log1p.call(1.0e-16) # => 1.0e-16

Updated by mame (Yusuke Endoh) 2 days ago

I investigated how other languages name the log1p function:

  • log1p: Python, Java, JavaScript, Go, PHP, .NET, R, MATLAB, Kotlin, Swift, Julia, Haskell, OCaml, Crystal
  • ln_1p: Rust
  • Not natively provided (unless I missed it): Perl, Lua, Dart, Erlang

As you can see, most languages provide this function under the name log1p. Rust uses a slightly different name, which is more cryptic to me :-)

In fact, I first noticed Ruby didn't have "Math.log1p" when I tried to write code sample code written in Python into Ruby.

Given this, I believe it's best to follow the common naming convention here.

POSIX, libc, and many languages provide them. Needing FFI to access it in Ruby isn't ideal.

https://docs.python.org/3.13/library/math.html#math.log1p
https://numpy.org/devdocs/reference/generated/numpy.log1p.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p
https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/Math.html#log1p(double)
https://pkg.go.dev/math#Log1p
https://www.php.net/manual/en/function.log1p.php
https://learn.microsoft.com/en-us/dotnet/api/java.lang.math.log1p?view=net-android-34.0
https://www.mathworks.com/help/matlab/ref/double.log1p.html
https://doc.rust-lang.org/std/primitive.f64.html#method.ln_1p
https://developer.android.com/reference/kotlin/java/lang/Math#log1p
https://developer.apple.com/documentation/simd/log1p
https://docs.julialang.org/en/v1/base/math/#Base.log1p
https://hackage.haskell.org/package/base-4.21.0.0/docs/Numeric.html#v:log1p
https://ocaml.org/manual/4.02/libref/Pervasives.html#VALlog1p
https://crystal-lang.org/api/1.17.1/Math.html#log1p%28value%29-instance-method

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0