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 <d...@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

_______________________________________________
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

Reply via email to