[libmath-prime-util-perl] 01/50: Export RiemannZeta function
Partha P. Mukherjee
ppm-guest at moszumanska.debian.org
Thu May 21 18:45:30 UTC 2015
This is an automated email from the git hooks/post-receive script.
ppm-guest pushed a commit to annotated tag v0.13
in repository libmath-prime-util-perl.
commit 585537df5a2695639c5aec6308b518277aa91bb9
Author: Dana Jacobsen <dana at acm.org>
Date: Mon Jul 23 20:06:40 2012 -0600
Export RiemannZeta function
---
Changes | 5 ++
XS.xs | 3 +
lib/Math/Prime/Util.pm | 28 ++++++++-
lib/Math/Prime/Util/PP.pm | 40 ++++++++----
util.c | 151 ++++++++++++++++++++++++++++++++++------------
util.h | 1 +
6 files changed, 176 insertions(+), 52 deletions(-)
diff --git a/Changes b/Changes
index 4e2f05c..358dd09 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,10 @@
Revision history for Perl extension Math::Prime::Util.
+0.12 30 July 2012
+ - Add RiemannZeta function. We used this before for Riemann R, but now
+ it is exported and should be accurate for small numbers (it was only
+ used for integers > 40 before).
+
0.11 23 July 2012
- Turn of threading tests on Cygwin, as threads on some Cygwin platforms
give random panics (my Win7 64-bit works fine, XP 32-bit does not).
diff --git a/XS.xs b/XS.xs
index 604edff..3e18598 100644
--- a/XS.xs
+++ b/XS.xs
@@ -428,4 +428,7 @@ double
_XS_LogarithmicIntegral(double x)
double
+_XS_RiemannZeta(double x)
+
+double
_XS_RiemannR(double x)
diff --git a/lib/Math/Prime/Util.pm b/lib/Math/Prime/Util.pm
index 85bb493..984c7ef 100644
--- a/lib/Math/Prime/Util.pm
+++ b/lib/Math/Prime/Util.pm
@@ -23,7 +23,7 @@ our @EXPORT_OK = qw(
nth_prime nth_prime_lower nth_prime_upper nth_prime_approx
random_prime random_ndigit_prime random_nbit_prime random_maurer_prime
factor all_factors moebius euler_phi
- ExponentialIntegral LogarithmicIntegral RiemannR
+ ExponentialIntegral LogarithmicIntegral RiemannZeta RiemannR
);
our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
@@ -1153,6 +1153,15 @@ sub nth_prime_upper {
#############################################################################
+sub RiemannZeta {
+ my($n) = @_;
+ croak("Invalid input to ReimannZeta: x must be > 0") if $n <= 0;
+
+ #return Math::Prime::Util::PP::RiemannZeta($n, 1e-30) if defined $bignum::VERSION || ref($n) eq 'Math::BigFloat';
+ return Math::Prime::Util::PP::RiemannZeta($n) if !$_Config{'xs'};
+ return _XS_RiemannZeta($n);
+}
+
sub RiemannR {
my($n) = @_;
croak("Invalid input to ReimannR: x must be > 0") if $n <= 0;
@@ -1940,6 +1949,21 @@ values.
Accuracy should be at least 14 digits.
+=head2 RiemannZeta
+
+ my $z = RiemannZeta($s);
+
+Given a floating point input C<s> where C<s E<gt>= 0.5>, returns the floating
+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.
+
+
=head2 RiemannR
my $r = RiemannR($x);
@@ -2149,6 +2173,8 @@ excellent versions in the public domain).
=item W. J. Cody and Henry C. Thacher, Jr., "Rational Chevyshev Approximations for the Exponential Integral E_1(x)".
+=item W. J. Cody, K. E. Hillstrom, and Henry C. Thacher Jr., "Chebyshev Approximations for the Riemann Zeta Function", Mathematics of Computation, v25, n115, pp 537-547, July 1971.
+
=item Ueli M. Maurer, "Fast Generation of Prime Numbers and Secure Public-Key Cryptographic Parameters", 1995. L<http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.26.2151>
=item Pierre-Alain Fouque and Mehdi Tibouchi, "Close to Uniform Prime Number Generation With Fewer Random Bits", 2011. L<http://eprint.iacr.org/2011/481>
diff --git a/lib/Math/Prime/Util/PP.pm b/lib/Math/Prime/Util/PP.pm
index 6a59411..edc4425 100644
--- a/lib/Math/Prime/Util/PP.pm
+++ b/lib/Math/Prime/Util/PP.pm
@@ -1270,22 +1270,26 @@ my @_Riemann_Zeta_Table = (
0.0000000000009094947840263889282533118,
);
-# Compute Riemann Zeta function. Slow and inaccurate near x = 1, but improves
-# very rapidly (x = 5 is quite reasonable).
-sub _evaluate_zeta {
- my($x) = @_;
- my $tol = 1e-16;
+# Compute Riemann Zeta function minus 1.
+sub RiemannZeta {
+ my($x, $tol) = @_;
+ $tol = 1e-16 unless defined $tol;
my $sum = 0.0;
my($y, $t);
my $c = 0.0;
- $y = (2.0 ** -$x)-$c; $t = $sum+$y; $c = ($t-$sum)-$y; $sum = $t;
-
- for my $k (3 .. 100000) {
- my $term = $k ** -$x;
+ for my $k (2 .. 1000000) {
+ my $term = (2*$k+1) ** -$x;
$y = $term-$c; $t = $sum+$y; $c = ($t-$sum)-$y; $sum = $t;
- last if abs($term) < $tol;
+ last if abs($term/$sum) < $tol;
}
+ my $term = 3 ** -$x;
+ $y = $term-$c; $t = $sum+$y; $c = ($t-$sum)-$y; $sum = $t;
+ $t = 1.0 / (1.0 - (2 ** -$x));
+ $sum *= $t;
+ $term = $t - 1.0;
+ $y = $term-$c; $t = $sum+$y; $c = ($t-$sum)-$y; $sum = $t;
+
$sum;
}
@@ -1308,7 +1312,7 @@ sub RiemannR {
for my $k (1 .. 10000) {
# Small k from table, larger k from function
my $zeta = ($k <= $#_Riemann_Zeta_Table) ? $_Riemann_Zeta_Table[$k+1-2]
- : _evaluate_zeta($k+1);
+ : RiemannZeta($k+1);
$part_term *= $flogx / $k;
my $term = $part_term / ($k + $k * $zeta);
$y = $term-$c; $t = $sum+$y; $c = ($t-$sum)-$y; $sum = $t;
@@ -1663,6 +1667,20 @@ values.
Accuracy should be at least 14 digits.
+=head2 RiemannZeta
+
+ my $z = RiemannZeta($s);
+
+Given a floating point input C<s> where C<s E<gt>= 0.5>, returns the floating
+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.
+
=head2 RiemannR
diff --git a/util.c b/util.c
index 1e8a152..d5b340f 100644
--- a/util.c
+++ b/util.c
@@ -608,12 +608,21 @@ UV _XS_nth_prime(UV n)
static double const euler_mascheroni = 0.57721566490153286060651209008240243104215933593992;
static double const li2 = 1.045163780117492784844588889194613136522615578151;
+#define KAHAN_INIT(s) \
+ double s ## _y, s ## _t; \
+ double s ## _c = 0.0; \
+ double s = 0.0;
+
+#define KAHAN_SUM(s, term) \
+ s ## _y = term - s ## _c; \
+ s ## _t = s + s ## _y; \
+ s ## _c = (s ## _t - s) - s ## _y; \
+ s = s ## _t;
+
double _XS_ExponentialIntegral(double x) {
double const tol = 1e-16;
double val, term, fact_n;
- double y, t;
- double sum = 0.0;
- double c = 0.0;
+ KAHAN_INIT(sum);
int n;
if (x == 0) croak("Invalid input to ExponentialIntegral: x must be != 0");
@@ -662,13 +671,13 @@ double _XS_ExponentialIntegral(double x) {
/* Convergent series */
fact_n = 1;
- y = euler_mascheroni-c; t = sum+y; c = (t-sum)-y; sum = t;
- y = log(x)-c; t = sum+y; c = (t-sum)-y; sum = t;
+ KAHAN_SUM(sum, euler_mascheroni);
+ KAHAN_SUM(sum, log(x));
for (n = 1; n <= 200; n++) {
fact_n *= x/n;
term = fact_n/n;
- y = term-c; t = sum+y; c = (t-sum)-y; sum = t;
+ KAHAN_SUM(sum, term);
/* printf("C after adding %.8lf, val = %.8lf\n", term, sum); */
if (term < tol) break;
}
@@ -679,17 +688,17 @@ double _XS_ExponentialIntegral(double x) {
val = exp(x) / x;
term = 1.0;
- y = 1.0-c; t = sum+y; c = (t-sum)-y; sum = t;
+ KAHAN_SUM(sum, term);
for (n = 1; n <= 200; n++) {
last_term = term;
term *= n/x;
if (term < tol) break;
if (term < last_term) {
- y = term-c; t = sum+y; c = (t-sum)-y; sum = t;
+ KAHAN_SUM(sum, term);
/* printf("A after adding %.8lf, sum = %.8lf\n", term, sum); */
} else {
- y = (-last_term/3)-c; t = sum+y; c = (t-sum)-y; sum = t;
+ KAHAN_SUM(sum, (-last_term/3) );
/* printf("A after adding %.8lf, sum = %.8lf\n", -last_term/3, sum); */
break;
}
@@ -755,60 +764,122 @@ static const double riemann_zeta_table[] = {
};
#define NPRECALC_ZETA (sizeof(riemann_zeta_table)/sizeof(riemann_zeta_table[0]))
-static double evaluate_zeta(double x) {
+/* Riemann Zeta on the real line. Compare to Math::Cephes::zetac */
+double _XS_RiemannZeta(double x) {
double const tol = 1e-16;
- double y, t;
- double sum = 0.0;
- double c = 0.0;
+ KAHAN_INIT(sum);
double term;
int k;
- /* Simple method. Slow and inaccurate near x=1, but iterations and accuracy
- * improve quickly.
- */
+ if (x < 0.5) croak("Invalid input to RiemannZeta: x must be >= 0.5");
- /* term = 1.0; y = term-c; t = sum+y; c = (t-sum)-y; sum = t; */
- term = pow(2, -x); y = term-c; t = sum+y; c = (t-sum)-y; sum = t;
+ if (x == (unsigned int)x) {
+ k = x - 2;
+ if ((k >= 0) && (k < NPRECALC_ZETA))
+ return riemann_zeta_table[k];
+ }
+
+ /* Use Cody et al. rational Chebyshev approx for small values. This is the
+ * range where the series methods take a long time and are inaccurate. This
+ * method is fast and quite accurate over the range 0.5 - 5. */
+ if (x <= 5.0) {
+ static const double C8p[9] = { 1.287168121482446392809e10,
+ 1.375396932037025111825e10,
+ 5.106655918364406103683e09,
+ 8.561471002433314862469e08,
+ 7.483618124380232984824e07,
+ 4.860106585461882511535e06,
+ 2.739574990221406087728e05,
+ 4.631710843183427123061e03,
+ 5.787581004096660659109e01 };
+ static const double C8q[9] = { 2.574336242964846244667e10,
+ 5.938165648679590160003e09,
+ 9.006330373261233439089e08,
+ 8.042536634283289888587e07,
+ 5.609711759541920062814e06,
+ 2.247431202899137523543e05,
+ 7.574578909341537560115e03,
+ -2.373835781373772623086e01,
+ 1.000000000000000000000 };
+ double sumn = C8p[0]+x*(C8p[1]+x*(C8p[2]+x*(C8p[3]+x*(C8p[4]+x*(C8p[5]+x*(C8p[6]+x*(C8p[7]+x*C8p[8])))))));
+ 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);
+ return sum;
+ }
- for (k = 3; k <= 100000; k++) {
- if (fabs(term) < tol) break;
+#if 0
+ /* Standard method, pushing the biggest two terms to the end. */
+ for (k = 4; k <= 1000000; k++) {
term = pow(k, -x);
- y = term-c; t = sum+y; c = (t-sum)-y; sum = t;
+ if (fabs(term/sum) < tol) break;
+ KAHAN_SUM(sum, term);
+ }
+ KAHAN_SUM(sum, pow(3, -x) );
+ KAHAN_SUM(sum, pow(2, -x) );
+ /* printf("zeta(%lf) = %.15lf in %d iterations\n", x, sum, k-2); */
+#endif
+
+#if 1
+ /* A different series using odd powers. About half the number of terms are
+ * needed vs. the normal mathod, and we get better numerical results.
+ * http://functions.wolfram.com/ZetaFunctionsandPolylogarithms/Zeta/06/04/0003
+ */
+ for (k = 2; k <= 1000000; k++) {
+ term = pow(2*k+1, -x);
+ KAHAN_SUM(sum, term);
+ if (fabs(term/sum) < tol) break;
}
+ KAHAN_SUM(sum, pow(3, -x) );
+ double t = 1.0 / (1.0 - pow(2, -x));
+ sum *= t;
+ sum += (t - 1.0);
+ /* printf("zeta(%lf) = %.15lf in %d iterations\n", x, sum, k); */
+#endif
+
+#if 0
+ /* An example using the Euler product. Many fewer terms are needed vs. the
+ * two series above, but we can't get the same accuracy for large input
+ * because we've got the leading 1.0.
+ */
+ {
+ double t;
+ int iter = 1;
+ sum = 1.0;
+ t = sum; sum *= 1.0 / (1.0 - pow(2, -x)); term = sum-t;
+ k = 3;
+ while (k < 1000000) {
+ if (fabs(term/sum) < tol) break;
+ t = sum; sum *= 1.0 / (1.0 - pow(k, -x)); term = sum-t;
+ iter++;
+ k = _XS_next_prime(k);
+ }
+ sum -= 1.0;
+ /* printf("zeta(%lf) = %.15lf in %d iterations\n", x, sum, iter); */
+ }
+#endif
return sum;
}
double _XS_RiemannR(double x) {
double const tol = 1e-16;
- double y, t, part_term, term, flogx, zeta;
- double sum = 0.0;
- double c = 0.0;
+ KAHAN_INIT(sum);
+ double part_term, term, flogx, zeta;
int k;
if (x <= 0) croak("Invalid input to ReimannR: x must be > 0");
- y = 1.0-c; t = sum+y; c = (t-sum)-y; sum = t;
+ KAHAN_SUM(sum, 1.0);
flogx = log(x);
part_term = 1;
- /* Do small k with zetas from table */
- for (k = 1; k <= (int)NPRECALC_ZETA; k++) {
- zeta = riemann_zeta_table[k+1-2];
- part_term *= flogx / k;
- term = part_term / (k + k * zeta);
- y = term-c; t = sum+y; c = (t-sum)-y; sum = t;
- if (fabs(term) < tol) break;
- /* printf("R after adding %.8lf, sum = %.8lf\n", term, sum); */
- }
- /* Finish with function */
- for (; k <= 10000; k++) {
- if (fabs(term) < tol) break;
- zeta = evaluate_zeta(k+1);
+ for (k = 1; k <= 10000; k++) {
+ zeta = _XS_RiemannZeta(k+1);
part_term *= flogx / k;
term = part_term / (k + k * zeta);
- y = term-c; t = sum+y; c = (t-sum)-y; sum = t;
- /* printf("R after adding %.8lf, sum = %.8lf\n", term, sum); */
+ KAHAN_SUM(sum, term);
+ /* printf("R after adding %.15lg, sum = %.15lg\n", term, sum); */
+ if (fabs(term/sum) < tol) break;
}
return sum;
diff --git a/util.h b/util.h
index 69873c4..75cede0 100644
--- a/util.h
+++ b/util.h
@@ -14,6 +14,7 @@ extern UV _XS_nth_prime(UV x);
extern double _XS_ExponentialIntegral(double x);
extern double _XS_LogarithmicIntegral(double x);
+extern double _XS_RiemannZeta(double x);
extern double _XS_RiemannR(double x);
/* Above this value, is_prime will do deterministic Miller-Rabin */
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libmath-prime-util-perl.git
More information about the Pkg-perl-cvs-commits
mailing list