aleaxit / gmpy

General Multi-Precision arithmetic for Python 2.6+/3+ (GMP, MPIR, MPFR, MPC)
https://gmpy2.readthedocs.io/en/latest/
GNU Lesser General Public License v3.0
506 stars 84 forks source link

Issue with large factorial #464

Open wjamesjr opened 6 months ago

wjamesjr commented 6 months ago

I know that I am doing something wrong but just can't figure what. The largest factorial that I can get converted to a mpfr is 44787927. Above this number I get inf as a result. I am on gmpy2 2.15 and python 3.11.7 on an Apple silicon Mac M3.

if name == "main": n = 44787927 gmpy2.get_context().emin = gmpy2.get_emin_min() gmpy2.get_context().emax = gmpy2.get_emax_max() print("emin", gmpy2.get_context().emin) print("emax", gmpy2.get_context().emax) print("max precision", gmpy2.get_max_precision()) gmpy2.get_context().precision = 100 print("precision set to", gmpy2.get_context().precision) factn = gmpy2.factorial(n) print(factn) n = 44787928 factn = gmpy2.factorial(n) print(factn) exit(0)

I get the following result running the above code.

emin -4611686018427387903 emax 4611686018427387903 max precision 9223372036854775551 precision set to 100 1.9481086289383819889097566536014e+323228493 inf

casevh commented 6 months ago

gmpy2.gamma() has a similar behavior. I vaguely recollect another report of this issue and it was a limitation of the MPFR library. I haven't found the details but I'll keep looking.

casevh commented 6 months ago

I found this comment in MPFR's gamma.c:

 if (u < 44787929UL && bits_fac (u - 1) <= p + (rnd_mode == MPFR_RNDN))
    /* bits_fac: lower bound on the number of bits of m,
       where gamma(x) = (u-1)! = m*2^e with m odd. */
    return mpfr_fac_ui (gamma, u - 1, rnd_mode);
  /* if bits_fac(...) > p (resp. p+1 for rounding to nearest),
     then gamma(x) cannot be exact in precision p (resp. p+1).
     FIXME: remove the test u < 44787929UL after changing bits_fac
     to return a mpz_t or mpfr_t. */
wjamesjr commented 6 months ago

Yep, that looks like the issue. Strange number to set as a cutoff! I will fall back to a python factorial algorithm with some bit slicing. Thanks for the quick response!

wjamesjr commented 6 months ago

Looks like the issue runs deeper than just gamma(). The below code computing a mpz factorial fails as well, with result inf. 44787927 still works. Is there some trick to converting very large mpz values to mpfr??

n = 44787928
gmpy2.get_context().emin = gmpy2.get_emin_min()
gmpy2.get_context().emax = gmpy2.get_emax_max()
print("emin", gmpy2.get_context().emin)
print("emax", gmpy2.get_context().emax)
print("max precision", gmpy2.get_max_precision())
gmpy2.get_context().precision = 100
print("precision set to", gmpy2.get_context().precision)
factn = gmpy2.fac(n)
print(factn.num_digits())
factn2 = mpfr(factn)
print(factn2)
exit(0)
wjamesjr commented 6 months ago

Looks like I have a hard limit on size of mpz that will convert to mpfr. The below code works. Change 2 29 to 2 30 and it goes to inf again. Should mpfr be limited in bit size?? I thought that's what precision provided.

gmpy2.get_context().emin = gmpy2.get_emin_min()
gmpy2.get_context().emax = gmpy2.get_emax_max()
print("emin", gmpy2.get_context().emin)
print("emax", gmpy2.get_context().emax)
print("max precision", gmpy2.get_max_precision())
gmpy2.get_context().precision = 100
print("precision set to", gmpy2.get_context().precision)
t1 = mpz(1) << (2 ** 29)
t2 = mpfr(t1)
print(t2)
casevh commented 6 months ago

It looks like a deeper issue:

t1=mpz(1) << (2*27) mpfr(t1) mpfr('1.196380724997376356710237763087067030291123782412927478906332372851427131104587e+40403562',256) _ mpfr('1.431326839145247872477712623353078898059627334067519357500412952958814141885275e+80807124',256) mpfr('2.048696520457526277391095958728021868321933030871131210018127684872659300422929e+161614248',256) _ mpfr('inf')

and

mpfr(123456.789)63483756.2 mpfr('3.15691251903978272292764153590394795083483376196033776445078487109637376386208e+323228495',256) mpfr(123456.789)63483756.3 mpfr('1.019562878429473795214570128278084403612032953011088359304266502177487364234525e+323228496',256) mpfr(123456.789)**63483756.4 mpfr('inf')

For reference

gmpy2.get_context() context(precision=256, real_prec=Default, imag_prec=Default, round=RoundToNearest, real_round=Default, imag_round=Default, emax=4611686018427387903, emin=-4611686018427387903, subnormalize=False, trap_underflow=False, underflow=False, trap_overflow=False, overflow=True, trap_inexact=False, inexact=True, trap_invalid=False, invalid=False, trap_erange=False, erange=False, trap_divzero=False, divzero=False, allow_complex=False, rational_division=False, allow_release_gil=False)

Time to dig into the MPFR source code.

On Fri, Jan 5, 2024 at 8:11 AM Bill James @.***> wrote:

Looks like I have a hard limit on size of mpz that will convert to mpfr. The below code works. Change 2 29 to 2 30 and it goes to inf again. Should mpfr be limited in bit size?? I thought that's what precision provided.

gmpy2.get_context().emin = gmpy2.get_emin_min() gmpy2.get_context().emax = gmpy2.get_emax_max() print("emin", gmpy2.get_context().emin) print("emax", gmpy2.get_context().emax) print("max precision", gmpy2.get_max_precision()) gmpy2.get_context().precision = 100 print("precision set to", gmpy2.get_context().precision) t1 = mpz(1) << (2 ** 29) t2 = mpfr(t1) print(t2)

— Reply to this email directly, view it on GitHub https://github.com/aleaxit/gmpy/issues/464#issuecomment-1878912852, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAMR23YSAMWNKZSKGSIAL2LYNAQ2BAVCNFSM6AAAAABBN54TPKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNZYHEYTEOBVGI . You are receiving this because you commented.Message ID: @.***>