Article 779RN Inverse factorial improved

Inverse factorial improved

by
John
from John D. Cook on (#779RN)

A couple years ago I wrote about how to compute the inverse of factorial. I used that code in writing the previous post because the post required solving the equation

log2(n!) >= b

given b. That is, given a number of bits b, find the smallest value of nsuch that n! >= 2b.

What the code got right

Looking back on the code in that post, there are a few changes I'd like to make. But first of all, I'd like to point out something the post does right: instead of trying to solve

(y) = x

it solves

log (y) = log x.

That's why the argument to inverse_log_gamma is logarg. That makes the code useful for values ofx that would far exceed the maximum floating point value, such as in the calculations for the previous post.

What I'd changeRounding

The function inverse_factorial from the old post solves finds the closest integer solution. It would be better for it to return the solution without rounding and then let the user round result if they want to. In my calculations in the previous post, I wanted to take the floor, not round.

Newton's method

The code in the previous post uses the bisection method. This method is very safe, and fast enough for my purposes, but it could be made faster. Newton's method is faster, but it can be ill-behaved if you don't start close enough to the solution.

It's safe to use Newton's method to invert log for two reasons. First, you can get a good starting point based on Stirling's approximation. Second, and more importantly, log is convex. Newton's method will converge fromany starting point when applied to a convex function. A little caution is necessary because log is not convex everywhere, but it is convex on the positive real axis.

Another difficulty with Newton's method is that you need to supply the derivative of the function whose root you're trying to find. But this isn't an issue here because the derivative of log is the digamma function, which is implemented in SciPy.

Tolerance

Finally, the previous code used the default tolerance for deciding when to stop refining the solution. The revised method lets the user specify tolerance. It provides a default value, but that default is visible in the function call, not hidden down in SciPy.

Revised code

Here's the revised code.

from scipy.special import gammaln, digammafrom scipy.optimize import newtondef inverse_log_gamma(logarg, tol=1e-12): assert(logarg > 0) x0 = logarg / log(logarg + 1) + 1 if logarg > 1 else 2.0 def f(z): return gammaln(z) - logarg return newton(f, x0, fprime=digamma, tol=tol)def inverse_factorial(logarg): g = inverse_log_gamma(logarg) return g - 1 
The post Inverse factorial improved first appeared on John D. Cook.
External Content
Source RSS or Atom Feed
Feed Location http://feeds.feedburner.com/TheEndeavour?format=xml
Feed Title John D. Cook
Feed Link https://www.johndcook.com/blog
Reply 0 comments