This is an automated email from the git hooks/post-receive script. ppm-guest pushed a commit to annotated tag v0.15 in repository libmath-prime-util-perl.
commit e04d2d3a57f6d993a8b6377a145d25be89708833 Author: Dana Jacobsen <d...@acm.org> Date: Sun Dec 2 04:48:41 2012 -0800 Use Math::MPFR if possible for Ei/li/Zeta/R functions. Huge speedup and accuracy gains for BigFloats. --- Changes | 13 ++ TODO | 5 - lib/Math/Prime/Util.pm | 110 +++++++++++----- lib/Math/Prime/Util/PP.pm | 243 ++++++++++++++++++++++++++++++++---- lib/Math/Prime/Util/ZetaBigFloat.pm | 86 +++++++++---- util.c | 25 ++-- 6 files changed, 387 insertions(+), 95 deletions(-) diff --git a/Changes b/Changes index f061020..c660517 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,18 @@ Revision history for Perl extension Math::Prime::Util. +0.15 ?? December 2012 + + - Lots of internal changes to Ei, li, Zeta, and R functions: + - Native Zeta and R have slightly more accurate results. + - For bignums, use Math::MPFR if possible. MUCH faster. + Also allows extended precision while still being fast. + - Better accuracy for standard bignums. + - All four functions do: + - XS if native input. + - MPFR to whatever accuracy is desired, if Math::MPFR installed. + - BigFloat versions if no MPFR and BigFloat input. + - standard version if no MPFR and not a BigFloat. + 0.14 29 November 2012 - Compilation and test issues: diff --git a/TODO b/TODO index f01fe4b..a83b661 100644 --- a/TODO +++ b/TODO @@ -29,11 +29,6 @@ - Make proper pminus1 in PP code, like factor.c. -- For bignums, RiemannZeta and RiemmannR are slow and give questionable - precision. We should be able to do better. One problem is the accuracy - bug in Math::BigFloat. Perhaps check for Math::MPFR installed and use it? - Alternately write real-number pow function using GMP. - - An assembler version of mulmod for i386 would be _really_ helpful for all the non-x86-64 Intel machines. diff --git a/lib/Math/Prime/Util.pm b/lib/Math/Prime/Util.pm index 0293972..855eec5 100644 --- a/lib/Math/Prime/Util.pm +++ b/lib/Math/Prime/Util.pm @@ -1172,9 +1172,11 @@ sub prime_count_approx { # my $result = int(LogarithmicIntegral($x) - LogarithmicIntegral(sqrt($x))/2); - my $xlen = (ref($x) eq 'Math::BigFloat') ? length($x->bfloor->bstr()) : length(int($x)); - my $tol = 10**-$xlen; - my $result = RiemannR($x, $tol) + 0.5; + if (ref($x) eq 'Math::BigFloat') { + # Make sure we get enough accuracy, and also not too much more than needed + $x->accuracy(length($x->bfloor->bstr())+2); + } + my $result = RiemannR($x) + 0.5; return Math::BigInt->new($result->bfloor->bstr()) if ref($result) eq 'Math::BigFloat'; return int($result); @@ -1418,18 +1420,18 @@ sub RiemannZeta { my($n) = @_; croak("Invalid input to ReimannZeta: x must be > 0") if $n <= 0; - return Math::Prime::Util::PP::RiemannZeta($n) if defined $bignum::VERSION || ref($n) eq 'Math::BigFloat'; - return _XS_RiemannZeta($n) if $n <= $_XS_MAXVAL; + return _XS_RiemannZeta($n) + if !defined $bignum::VERSION && ref($n) ne 'Math::BigFloat' && $n <= $_XS_MAXVAL; return Math::Prime::Util::PP::RiemannZeta($n); } sub RiemannR { - my($n, $tol) = @_; + my($n) = @_; croak("Invalid input to ReimannR: x must be > 0") if $n <= 0; - return Math::Prime::Util::PP::RiemannR($n, $tol) if defined $bignum::VERSION || ref($n) eq 'Math::BigFloat'; - return _XS_RiemannR($n) if $n <= $_XS_MAXVAL; - return Math::Prime::Util::PP::RiemannR($n, $tol); + return _XS_RiemannR($n) + if !defined $bignum::VERSION && ref($n) ne 'Math::BigFloat' && $n <= $_XS_MAXVAL; + return Math::Prime::Util::PP::RiemannR($n); } sub ExponentialIntegral { @@ -1438,9 +1440,10 @@ sub ExponentialIntegral { return 0 if $n == $_Neg_Infinity; return $_Infinity if $n == $_Infinity; - return Math::Prime::Util::PP::ExponentialIntegral($n) if defined $bignum::VERSION || ref($n) eq 'Math::BigFloat'; - return Math::Prime::Util::PP::ExponentialIntegral($n) if !$_Config{'xs'}; - return _XS_ExponentialIntegral($n); + return _XS_ExponentialIntegral($n) + if !defined $bignum::VERSION && ref($n) ne 'Math::BigFloat' && $_Config{'xs'}; + + return Math::Prime::Util::PP::ExponentialIntegral($n); } sub LogarithmicIntegral { @@ -1448,17 +1451,15 @@ sub LogarithmicIntegral { return 0 if $n == 0; return $_Neg_Infinity if $n == 1; return $_Infinity if $n == $_Infinity; - if ($n == 2) { - return (defined $bignum::VERSION || ref($n) eq 'Math::BigFloat') - ? Math::BigFloat->new('1.045163780117492784844588889194613136522615578151201575832909144075013205210359530172717405626383356306') - : 1.045163780117492784844588889194613136522615578151; - } croak("Invalid input to LogarithmicIntegral: x must be >= 0") if $n <= 0; - return Math::Prime::Util::PP::LogarithmicIntegral($n) - if defined $bignum::VERSION || ref($n) eq 'Math::BigFloat' || !$_Config{'xs'}; - return _XS_LogarithmicIntegral($n); + if (!defined $bignum::VERSION && ref($n) ne 'Math::BigFloat' && $_Config{'xs'}) { + return 1.045163780117492784844588889194613136522615578151 if $n == 2; + return _XS_LogarithmicIntegral($n); + } + + return Math::Prime::Util::PP::LogarithmicIntegral($n); } ############################################################################# @@ -2357,12 +2358,25 @@ find a factor C<p> of C<n> where C<p-1> is smooth (it has no large factors). Given a non-zero floating point input C<x>, this returns the real-valued exponential integral of C<x>, defined as the integral of C<e^t/t dt> from C<-infinity> to C<x>. -Depending on the input, the integral is calculated using + +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. + +For non-BigInt/BigFloat objects, the result should be accurate to at least 14 +digits. + +For BigInt / BigFloat objects, we first check to see if the Math::MPFR module +is installed. If so, then it is used, as it will return results much faster +and can be more accurate. Accuracy when using MPFR will be equal to the +C<accuracy()> value of the input (or the default BigFloat accuracy, which +is 40 by default). + +MPFR is used for positive inputs only. If Math::MPFR is not installed or the +input is negative, then other methods are used: continued fractions (C<x E<lt> -1>), rational Chebyshev approximation (C< -1 E<lt> x E<lt> 0>), a convergent series (small positive C<x>), or an asymptotic divergent series (large positive C<x>). - Accuracy should be at least 14 digits. @@ -2382,11 +2396,20 @@ term C<li0> for this function, and define C<li(x) = Li0(x) - li0(2)>. Due to this terminilogy confusion, it is important to check which exact definition is being used. +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. -This function is implemented as C<li(x) = Ei(ln x)> after handling special -values. +For non-BigInt/BigFloat objects, the result should be accurate to at least 14 +digits. -Accuracy should be at least 14 digits. +For BigInt / BigFloat objects, we first check to see if the Math::MPFR module +is installed. If so, then it is used, as it will return results much faster +and can be more accurate. Accuracy when using MPFR will be equal to the +C<accuracy()> value of the input (or the default BigFloat accuracy, which +is 40 by default). + +MPFR is used for inputs greater than 1 only. If Math::MPFR is not installed or +the input is less than 1, results will be calculated as C<Ei(ln x)>. =head2 RiemannZeta @@ -2399,11 +2422,24 @@ subtracted to ensure maximum precision for large values of C<s>. The zeta function is the sum from k=1 to infinity of C<1 / k^s>. This function only uses real arguments, so is basically the Euler Zeta function. -Accuracy should be at least 14 digits with native numbers and 35 digits with -bignum or a BigInt/BigFloat argument. Small integer values are returned from -a table. For native-precision numbers, a rational Chebyshev approximation is -used between 0.5 and 5, while larger values use a series. Multiple precision -numbers use Borwein (1991) algorithm 2 or the basic series. +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. + +For non-BigInt/BigFloat objects, the result should be accurate to at least 14 +digits. The XS code uses a rational Chebyshev approximation between 0.5 and 5, +and a series for larger values. + +For BigInt / BigFloat objects, we first check to see if the Math::MPFR module +is installed. If so, then it is used, as it will return results much faster +and can be more accurate. Accuracy when using MPFR will be equal to the +C<accuracy()> value of the input (or the default BigFloat accuracy, which +is 40 by default). + +If Math::MPFR is not installed, then results are calculated using either +Borwein (1991) algorithm 2, or the basic series. Full input accuracy is +attempted, but there are defects in Math::BigFloat with high accuracy +computations that make this difficult. It is also very slow. I highly +recommend installing Math::MPFR for BigFloat computations. =head2 RiemannR @@ -2414,8 +2450,18 @@ Given a positive non-zero floating point input, returns the floating point value of Riemann's R function. Riemann's R function gives a very close approximation to the prime counting function. -Accuracy should be at least 14 digits for native numbers and 35 digits with -bignum or a BigInt/BigFloat argument. +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. + +For non-BigInt/BigFloat objects, the result should be accurate to at least 14 +digits. + +For BigInt / BigFloat objects, we first check to see if the Math::MPFR module +is installed. If so, then it is used, as it will return results much faster +and can be more accurate. Accuracy when using MPFR will be equal to the +C<accuracy()> value of the input (or the default BigFloat accuracy, which +is 40 by default). Accuracy without MPFR should be 35 digits. + =head1 EXAMPLES diff --git a/lib/Math/Prime/Util/PP.pm b/lib/Math/Prime/Util/PP.pm index 253d15b..5de0e92 100644 --- a/lib/Math/Prime/Util/PP.pm +++ b/lib/Math/Prime/Util/PP.pm @@ -43,6 +43,8 @@ our $_Infinity = 0+'inf'; $_Infinity = 20**20**20 if 65535 > $_Infinity; # E.g. Windows our $_Neg_Infinity = -$_Infinity; +my $_have_MPFR = -1; + my $_precalc_size = 0; sub prime_precalc { my($n) = @_; @@ -1436,17 +1438,47 @@ my $_const_euler = 0.57721566490153286060651209008240243104215933593992; my $_const_li2 = 1.045163780117492784844588889194613136522615578151; sub ExponentialIntegral { - my($x, $tol) = @_; + my($x) = @_; return $_Neg_Infinity if $x == 0; return 0 if $x == $_Neg_Infinity; return $_Infinity if $x == $_Infinity; - $tol = 1e-16 unless defined $tol; - my $sum = 0.0; - my($y, $t); - my $c = 0.0; + + # Use MPFR if possible. + if ($_have_MPFR < 0) { + $_have_MPFR = 1; + eval { require Math::MPFR; 1; } or do { $_have_MPFR = 0; }; + } + # Gotcha -- MPFR decided to make negative inputs return NaN. Grrr. + if ($_have_MPFR && $x > 0) { + my $wantbf = 0; + my $xdigits = 17; + if (defined $bignum::VERSION || ref($x) =~ /^Math::Big/) { + if (!defined $MATH::BigFloat::VERSION) { + eval { require Math::BigFloat; Math::BigFloat->import(); 1; } + or do { croak "Cannot load Math::BigFloat "; } + } + $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; + $wantbf = 1; + $xdigits = $x->accuracy || Math::BigFloat->accuracy() || Math::BigFloat->div_scale(); + } + my $rnd = 0; # MPFR_RNDN; + my $bit_precision = int($xdigits * 3.322) + 4; + my $rx = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rx, $bit_precision); + Math::MPFR::Rmpfr_set_str($rx, "$x", 10, $rnd); + my $eix = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($eix, $bit_precision); + Math::MPFR::Rmpfr_eint($eix, $rx, $rnd); + my $strval = Math::MPFR::Rmpfr_get_str($eix, 10, 0, $rnd); + return ($wantbf) ? Math::BigFloat->new($strval) : 0.0 + $strval; + } $x = new Math::BigFloat "$x" if defined $bignum::VERSION && ref($x) ne 'Math::BigFloat'; + my $tol = 1e-16; + my $sum = 0.0; + my($y, $t); + my $c = 0.0; my $val; # The result from one of the four methods if ($x < -1) { @@ -1518,13 +1550,52 @@ sub LogarithmicIntegral { return 0 if $x == 0; return $_Neg_Infinity if $x == 1; return $_Infinity if $x == $_Infinity; - return $_const_li2 if $x == 2; croak "Invalid input to LogarithmicIntegral: x must be > 0" if $x <= 0; + # Use MPFR if possible. + if ($_have_MPFR < 0) { + $_have_MPFR = 1; + eval { require Math::MPFR; 1; } or do { $_have_MPFR = 0; }; + } + # Remember MPFR eint doesn't handle negative inputs + if ($_have_MPFR && $x >= 1) { + my $wantbf = 0; + my $xdigits = 17; + if (defined $bignum::VERSION || ref($x) =~ /^Math::Big/) { + if (!defined $MATH::BigFloat::VERSION) { + eval { require Math::BigFloat; Math::BigFloat->import(); 1; } + or do { croak "Cannot load Math::BigFloat "; } + } + $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; + $wantbf = 1; + $xdigits = $x->accuracy || Math::BigFloat->accuracy() || Math::BigFloat->div_scale(); + } + $x = log($x); # Use BigFloat to do the log to simplify precision tracking. + my $rnd = 0; # MPFR_RNDN; + my $bit_precision = int($xdigits * 3.322) + 4; + my $rx = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rx, $bit_precision); + Math::MPFR::Rmpfr_set_str($rx, "$x", 10, $rnd); + my $lix = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($lix, $bit_precision); + Math::MPFR::Rmpfr_eint($lix, $rx, $rnd); + my $strval = Math::MPFR::Rmpfr_get_str($lix, 10, 0, $rnd); + return ($wantbf) ? Math::BigFloat->new($strval) : 0.0 + $strval; + } + + if ($x == 2) { + my $li2const = (ref($x) eq 'Math::BigFloat') ? Math::BigFloat->new('1.04516378011749278484458888919461313652261557815120157583290914407501320521') : $_const_li2; + return $li2const; + } + $x = new Math::BigFloat "$x" if defined $bignum::VERSION && ref($x) ne 'Math::BigFloat'; my $logx = log($x); # Do divergent series here for big inputs. Common for big pc approximations. + # Why is this here? + # 1) exp(log(x)) results in a lot of lost precision + # 2) exp(x) with lots of precision turns out to be really slow, and in + # this case it was unnecessary. if ($x > 1e16) { my $tol = 1e-20; my $invx = 1.0 / $logx; @@ -1611,16 +1682,50 @@ my @_Riemann_Zeta_Table = ( sub RiemannZeta { - my($x, $tol) = @_; + my($x) = @_; + + # Use MPFR if possible. + if ($_have_MPFR < 0) { + $_have_MPFR = 1; + eval { require Math::MPFR; 1; } or do { $_have_MPFR = 0; }; + } + if ($_have_MPFR) { + my $wantbf = 0; + my $xdigits = 17; + if (defined $bignum::VERSION || ref($x) =~ /^Math::Big/) { + if (!defined $MATH::BigFloat::VERSION) { + eval { require Math::BigFloat; Math::BigFloat->import(); 1; } + or do { croak "Cannot load Math::BigFloat "; } + } + $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; + $wantbf = 1; + $xdigits = $x->accuracy || Math::BigFloat->accuracy() || Math::BigFloat->div_scale(); + } + my $rnd = 0; # MPFR_RNDN; + my $bit_precision = int($xdigits * 3.322) + 4; + my $rx = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rx, $bit_precision); + Math::MPFR::Rmpfr_set_str($rx, "$x", 10, $rnd); + my $zetax = Math::MPFR->new(); + # Add more bits to account for the leading zeros. + my $extra_bits = int(abs($x)); + Math::MPFR::Rmpfr_set_prec($zetax, $bit_precision + $extra_bits); + Math::MPFR::Rmpfr_zeta($zetax, $rx, $rnd); + Math::MPFR::Rmpfr_sub_ui($zetax, $zetax, 1, $rnd); + my $strval = Math::MPFR::Rmpfr_get_str($zetax, 10, $xdigits, $rnd); + return ($wantbf) ? Math::BigFloat->new($strval) : 0.0 + $strval; + } if (defined $bignum::VERSION || ref($x) =~ /^Math::Big/) { + # No MPFR, BigFloat require Math::Prime::Util::ZetaBigFloat; - return Math::Prime::Util::ZetaBigFloat::RiemannZeta($x, $tol); + return Math::Prime::Util::ZetaBigFloat::RiemannZeta($x); } + # No MPFR, no BigFloat. return 0.0 + $_Riemann_Zeta_Table[int($x)-2] if $x == int($x) && defined $_Riemann_Zeta_Table[int($x)-2]; - $tol = 1e-16 unless defined $tol; + my $tol = 1e-16; my($y, $t); my $sum = 0.0; my $c = 0.0; @@ -1640,17 +1745,76 @@ sub RiemannZeta { # Riemann R function sub RiemannR { - my($x, $tol) = @_; + my($x) = @_; croak "Invalid input to ReimannR: x must be > 0" if $x <= 0; + # Use MPFR if possible. + if ($_have_MPFR < 0) { + $_have_MPFR = 1; + eval { require Math::MPFR; 1; } or do { $_have_MPFR = 0; }; + } + if ($_have_MPFR) { + my $wantbf = 0; + my $xdigits = 17; + if (defined $bignum::VERSION || ref($x) =~ /^Math::Big/) { + if (!defined $MATH::BigFloat::VERSION) { + eval { require Math::BigFloat; Math::BigFloat->import(); 1; } + or do { croak "Cannot load Math::BigFloat "; } + } + $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; + $wantbf = 1; + $xdigits = $x->accuracy || Math::BigFloat->accuracy() || Math::BigFloat->div_scale(); + } + my $rnd = 0; # MPFR_RNDN; + my $bit_precision = int($xdigits * 3.322) + 8; # Add some extra + + my $rlogx = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rlogx, $bit_precision); + Math::MPFR::Rmpfr_set_str($rlogx, "$x", 10, $rnd); + Math::MPFR::Rmpfr_log($rlogx, $rlogx, $rnd); + + my $rpart_term = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rpart_term, $bit_precision); + Math::MPFR::Rmpfr_set_str($rpart_term, "1", 10, $rnd); + + my $rzeta = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rzeta, $bit_precision); + my $rterm = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rterm, $bit_precision); + + my $rsum = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rsum, $bit_precision); + Math::MPFR::Rmpfr_set_str($rsum, "1", 10, $rnd); + + my $rstop = Math::MPFR->new(); + Math::MPFR::Rmpfr_set_prec($rstop, $bit_precision); + Math::MPFR::Rmpfr_set_str($rstop, "1e-$xdigits", 10, $rnd); + + for my $k (1 .. 10000) { + Math::MPFR::Rmpfr_mul($rpart_term, $rpart_term, $rlogx, $rnd); + Math::MPFR::Rmpfr_div_ui($rpart_term, $rpart_term, $k, $rnd); + + Math::MPFR::Rmpfr_zeta_ui($rzeta, $k+1, $rnd); + Math::MPFR::Rmpfr_sub_ui($rzeta, $rzeta, 1, $rnd); + Math::MPFR::Rmpfr_mul_ui($rzeta, $rzeta, $k, $rnd); + Math::MPFR::Rmpfr_add_ui($rzeta, $rzeta, $k, $rnd); + Math::MPFR::Rmpfr_div($rterm, $rpart_term, $rzeta, $rnd); + + last if Math::MPFR::Rmpfr_less_p($rterm, $rstop); + Math::MPFR::Rmpfr_add($rsum, $rsum, $rterm, $rnd); + } + my $strval = Math::MPFR::Rmpfr_get_str($rsum, 10, $xdigits, $rnd); + return ($wantbf) ? Math::BigFloat->new($strval) : 0.0 + $strval; + } + if (defined $bignum::VERSION || ref($x) =~ /^Math::Big/) { require Math::Prime::Util::ZetaBigFloat; - return Math::Prime::Util::ZetaBigFloat::RiemannR($x, $tol); + return Math::Prime::Util::ZetaBigFloat::RiemannR($x); } - $tol = 1e-16 unless defined $tol; + my $tol = 1e-16; my $sum = 0.0; my($y, $t); my $c = 0.0; @@ -1850,6 +2014,8 @@ math packages. When given two arguments, it returns the inclusive count of primes between the ranges (e.g. C<(13,17)> returns 2, C<14,17> and C<13,16> return 1, and C<14,16> returns 0). +The Lehmer method is used for large values, which speeds up results greatly. + =head2 nth_prime @@ -1858,8 +2024,9 @@ and C<13,16> return 1, and C<14,16> returns 0). Returns the prime that lies in index C<n> in the array of prime numbers. Put another way, this returns the smallest C<p> such that C<Pi(p) E<gt>= n>. -This relies on generating primes, so can require a lot of time and space for -large inputs. +The Lehmer prime count is used to speed up results for large inputs, but both +methods take quite a bit of time and space. Think abut whether a bound or +approximation would be acceptable instead. =head2 is_strong_pseudoprime @@ -1999,12 +2166,22 @@ others they succeed in a remarkably short time. Given a non-zero floating point input C<x>, this returns the real-valued exponential integral of C<x>, defined as the integral of C<e^t/t dt> from C<-infinity> to C<x>. -Depending on the input, the integral is calculated using + +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. + +We first check to see if the Math::MPFR module is installed. If so, then +it is used, as it will return results much faster and can be more accurate. +Accuracy when using MPFR will be 17 digits for non-BigInt/BigFloats, and +for BigInt/BigFloat inputs will be equal to the C<accuracy()> value of the +input (or the default BigFloat accuracy, which is 40 by default). + +MPFR is used for positive inputs only. If Math::MPFR is not installed or the +input is negative, then other methods are used: continued fractions (C<x E<lt> -1>), rational Chebyshev approximation (C< -1 E<lt> x E<lt> 0>), a convergent series (small positive C<x>), or an asymptotic divergent series (large positive C<x>). - Accuracy should be at least 14 digits. @@ -2021,10 +2198,14 @@ This is often known as C<li(x)>. A related function is the offset logarithmic integral, sometimes known as C<Li(x)> which avoids the singularity at 1. It may be defined as C<Li(x) = li(x) - li(2)>. -This function is implemented as C<li(x) = Ei(ln x)> after handling special -values. +We first check to see if the Math::MPFR module is installed. If so, then +it is used, as it will return results much faster and can be more accurate. +Accuracy when using MPFR will be 17 digits for non-BigInt/BigFloats, and +for BigInt/BigFloat inputs will be equal to the C<accuracy()> value of the +input (or the default BigFloat accuracy, which is 40 by default). -Accuracy should be at least 14 digits. +MPFR is used for inputs greater than 1 only. If Math::MPFR is not installed or +the input is less than 1, results will be calculated as C<Ei(ln x)>. =head2 RiemannZeta @@ -2035,10 +2216,17 @@ point value of ζ(s)-1, where ζ(s) is the Riemann zeta function. One is subtracted to ensure maximum precision for large values of C<s>. The zeta function is the sum from k=1 to infinity of C<1 / k^s> -Accuracy should be at least 14 digits, but currently does not increase -accuracy with big floats. Small integer values are returned from a table, -values between 0.5 and 5 use rational Chebyshev approximation, and larger -values use a series. +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. + +We first check to see if the Math::MPFR module is installed. If so, then +it is used, as it will return results much faster and can be more accurate. +Accuracy when using MPFR will be 17 digits for non-BigInt/BigFloats, and +for BigInt/BigFloat inputs will be equal to the C<accuracy()> value of the +input (or the default BigFloat accuracy, which is 40 by default). + +If Math::MPFR is not installed, then results are calculated either from a +table, rational Chebyshev approximation, or via a series. =head2 RiemannR @@ -2049,7 +2237,14 @@ Given a positive non-zero floating point input, returns the floating point value of Riemann's R function. Riemann's R function gives a very close approximation to the prime counting function. -Accuracy should be at least 14 digits. +If the bignum module has been loaded, all inputs will be treated as if they +were Math::BigFloat objects. + +We first check to see if the Math::MPFR module is installed. If so, then +it is used, as it will return results much faster and can be more accurate. +Accuracy when using MPFR will be 17 digits for non-BigInt/BigFloats, and +for BigInt/BigFloat inputs will be equal to the C<accuracy()> value of the +input (or the default BigFloat accuracy, which is 40 by default). =head1 LIMITATIONS diff --git a/lib/Math/Prime/Util/ZetaBigFloat.pm b/lib/Math/Prime/Util/ZetaBigFloat.pm index 05aa3d2..54f036f 100644 --- a/lib/Math/Prime/Util/ZetaBigFloat.pm +++ b/lib/Math/Prime/Util/ZetaBigFloat.pm @@ -284,12 +284,17 @@ sub _Recompute_Dk { } sub RiemannZeta { - my($x, $tol) = @_; + my($x) = @_; + + $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; + my $xdigits = $x->accuracy || Math::BigFloat->accuracy() || Math::BigFloat->div_scale(); return $_Riemann_Zeta_Table[int($x)-2] - if $x == int($x) && defined $_Riemann_Zeta_Table[int($x)-2]; + if $x == int($x) + && defined $_Riemann_Zeta_Table[int($x)-2] + && $xdigits <= 44; - $tol = 1e-40 unless defined $tol; + my $tol = 0.0 + "1e-$xdigits"; # Trying to work around Math::BigFloat bugs RT 43692 and RT 77105 which make # a right mess of things. Watch this: @@ -299,7 +304,6 @@ sub RiemannZeta { # into (6^-(40.5/4))^4 (assuming the base is positive). Without that hack, # none of this would work at all. - $x = Math::BigFloat->new("$x"); my $superx = 1; my $subx = $x->copy; while ($subx > 8) { @@ -310,7 +314,8 @@ sub RiemannZeta { # Go with the basic formula for large x, as it best works around the mess, # though is unfortunately much slower. if ($x > 30) { - my $sum = 0.0; + my $sum = Math::BigFloat->bzero; + $sum->accuracy($xdigits); for my $k (4 .. 1000) { my $term = ( $k ** -$subx ) ** $superx; $sum += $term; @@ -335,9 +340,11 @@ sub RiemannZeta { # return $sum; #} - # If we wanted to change the Borwein series being used: - # _Recompute_Dk(55); - + { + my $dig = int($_Borwein_n / 1.3)+1; + _Recompute_Dk( int($xdigits * 1.3) + 4 ) if $dig < $xdigits; + } + if (ref $_Borwein_dk[0] ne 'Math::BigInt') { @_Borwein_dk = map { Math::BigInt->new("$_") } @_Borwein_dk; } @@ -371,23 +378,27 @@ sub RiemannZeta { $sum += Math::BigFloat->new( $one - $_Borwein_dk[$n] ); # term k=0 $sum->bdiv( $divisor ); $sum->bsub(1); + #$sum->fround($xdigits); return $sum; } # Riemann R function sub RiemannR { - my($x, $tol) = @_; + my($x) = @_; + $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; + my $xdigits = $x->accuracy || Math::BigFloat->accuracy() || Math::BigFloat->div_scale(); + my $tol = 0.0 + "1e-$xdigits"; + + # TODO: Changing input accuracy may mean we should recalculate this. Also, + # the default table is only 44 digits. if (scalar @_Riemann_Zeta_Premult == 0) { @_Riemann_Zeta_Premult = map { my $v = Math::BigFloat->bone; - $v->accuracy($x->accuracy() || 45); + $v->accuracy($xdigits); $v / ($_Riemann_Zeta_Table[$_-1] * $_ + $_) } (1 .. @_Riemann_Zeta_Table); } - $x = new Math::BigFloat "$x" if ref($x) ne 'Math::BigFloat'; - - $tol = 1e-35 unless defined $tol; my $sum = Math::BigFloat->bone; my $flogx = log($x); @@ -396,9 +407,19 @@ sub RiemannR { for my $k (1 .. 10000) { my $zeta_term = $_Riemann_Zeta_Premult[$k-1]; if (!defined $zeta_term) { - my $zeta = (($k-1) <= $#_Riemann_Zeta_Table) - ? $_Riemann_Zeta_Table[$k-1] - : RiemannZeta( $k+1 ); + my $zeta = $_Riemann_Zeta_Table[$k-1]; + if (!defined $zeta) { + my $kz = $k+1; + if ($kz >= 100 && $xdigits <= 40) { + # For this accuracy level, two terms are more than enough. Also, + # we should be able to miss the Math::BigFloat accuracy bug. If we + # try to do this for higher accuracy, things will go very bad. + $zeta = Math::BigFloat->new(3)->bpow(-$kz) + + Math::BigFloat->new(2)->bpow(-$kz); + } else { + $zeta = Math::Prime::Util::ZetaBigFloat::RiemannZeta( $kz ); + } + } $zeta_term = Math::BigFloat->bone / ($zeta * $k + $k); } $part_term *= $flogx / $k; @@ -436,12 +457,26 @@ Version 0.14 Math::BigFloat versions`of the Riemann Zeta and Riemann R functions. These are kept in a separate module because they use a lot of big tables that we'd -prefer not to have loaded all the time. +prefer to only load if needed. =head1 DESCRIPTION Pure Perl implementations of Riemann Zeta and Riemann R using Math::BigFloat. +These functions are used if: + +=over 4 + +=item The input is a BigInt, a BigFloat, or the bignum module has been loaded. + +=item The Math::MPFR module is not available. + +=back + +If you use these functions a lot, I B<highly> recommend you install +L<Math::MPFR>, which the main L<Math::Prime::Util> functions will find. +These give B<much> better performance, and better accuracy. You can also +use L<Math::Pari> for the Riemann Zeta function. =head1 FUNCTIONS @@ -455,10 +490,9 @@ point value of ζ(s)-1, where ζ(s) is the Riemann zeta function. One is subtracted to ensure maximum precision for large values of C<s>. The zeta function is the sum from k=1 to infinity of C<1 / k^s> -Accuracy should be at least 14 digits, but currently does not increase -accuracy with big floats. Small integer values are returned from a table, -values between 0.5 and 5 use rational Chebyshev approximation, and larger -values use a series. +Results are calculated using either Borwein (1991) algorithm 2, or the basic +series. Full input accuracy is attempted, but there are defects in +Math::BigFloat with high accuracy computations that make this difficult. =head2 RiemannR @@ -469,7 +503,7 @@ Given a positive non-zero floating point input, returns the floating point value of Riemann's R function. Riemann's R function gives a very close approximation to the prime counting function. -Accuracy should be at least 14 digits. +Accuracy should be about 35 digits. =head1 LIMITATIONS @@ -478,20 +512,20 @@ Bugs in Math::BigFloat (RT 43692, RT 77105) cause many problems with this code. I've attempted to apply workarounds, but it is possible there are cases they miss. -The accuracy goals (35 digits) are sometimes missed by a digit or two, and -extensive testing needs to be done to ensure we meet the goals. +The accuracy goals (35 digits) are sometimes missed by a digit or two. =head1 PERFORMANCE -Performance is not good at all. A version using XS+GMP would be good to have. -Pari can give better accuracy in a miniscule fraction of the time. +Performance is quite bad. =head1 SEE ALSO L<Math::Prime::Util> +L<Math::MPFR> + L<Math::Pari> diff --git a/util.c b/util.c index a67b506..9bedf96 100644 --- a/util.c +++ b/util.c @@ -846,22 +846,31 @@ long double ld_riemann_zeta(long double x) { long double sumd = C8q[0]+x*(C8q[1]+x*(C8q[2]+x*(C8q[3]+x*(C8q[4]+x*(C8q[5]+x*(C8q[6]+x*(C8q[7]+x*C8q[8]))))))); sum = (sumn - (x-1)*sumd) / ((x-1)*sumd); + } else if (x > 2000.0) { + + /* 1) zeta(2000)-1 is about 8.7E-603, which is far less than a IEEE-754 + * 64-bit double can represent. A 128-bit quad could go to ~16000. + * 2) pow / powl start getting obnoxiously slow with values like -7500. + */ + return 0.0; + } else { - /* This series needs about half the number of terms as the usual k^-x, - * and gets slightly better numerical results. + /* I was using this series: * functions.wolfram.com/ZetaFunctionsandPolylogarithms/Zeta/06/04/0003 + * which for integer values is great -- it needs half the number of terms + * and gives slightly better numerical results. However, for non-integer + * values using double precision, it's awful. + * Back to the defining equation. */ - for (k = 2; k <= 1000000; k++) { - term = powl(2*k+1, -x); + for (k = 5; k <= 1000000; k++) { + term = powl(k, -x); KAHAN_SUM(sum, term); if (term < tol*sum) break; } + KAHAN_SUM(sum, powl(4, -x) ); KAHAN_SUM(sum, powl(3, -x) ); - term = 1.0L / (1.0L - powl(2, -x)); - sum *= term; - sum += (term - 1.0L); - /* printf("zeta(%lf) = %.15lf in %d iterations\n", x, sum, k); */ + KAHAN_SUM(sum, powl(2, -x) ); } -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libmath-prime-util-perl.git _______________________________________________ Pkg-perl-cvs-commits mailing list Pkg-perl-cvs-commits@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-perl-cvs-commits