[gnumeric] BESSELY: Use J series when possible.
- From: Morten Welinder <mortenw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnumeric] BESSELY: Use J series when possible.
- Date: Mon, 2 Dec 2013 17:44:46 +0000 (UTC)
commit 8a89952bf4ae5ae3e708c835a2c8e8b89abd1f2e
Author: Morten Welinder <terra gnome org>
Date: Mon Dec 2 12:44:17 2013 -0500
BESSELY: Use J series when possible.
Also make J series much more accurate.
ChangeLog | 4 ++
NEWS | 2 +-
src/sf-bessel.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 119 insertions(+), 14 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index e89879d..43acdc1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2013-12-02 Morten Welinder <terra gnome org>
+
+ * src/sf-bessel.c (bessel_y): Use the J series when possible.
+
2013-12-01 welinder <terra gnome org>
* src/sf-bessel.c (bessel_j): Use the taylor series in the
diff --git a/NEWS b/NEWS
index c3a3f87..0b9eff5 100644
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,7 @@ Andreas:
Morten:
* Extend POCHHAMMER to negative values.
* Improve accuracy of BETA and BETALN.
- * Improve accuracy of BESSELJ.
+ * Improve accuracy of BESSELJ and BESSELY.
--------------------------------------------------------------------------
Gnumeric 1.12.9
diff --git a/src/sf-bessel.c b/src/sf-bessel.c
index 97cb122..119baa4 100644
--- a/src/sf-bessel.c
+++ b/src/sf-bessel.c
@@ -11,6 +11,7 @@
#define MATHLIB_WARNING2 g_warning
#define MATHLIB_WARNING4 g_warning
+static gboolean bessel_j_series_domain (gnm_float x, gnm_float v);
static gnm_float bessel_j_series (gnm_float x, gnm_float alpha);
static gnm_float bessel_y_ex(gnm_float x, gnm_float alpha, gnm_float *by);
@@ -685,7 +686,7 @@ static gnm_float bessel_j(gnm_float x, gnm_float alpha)
((alpha == na) ? 0 :
bessel_y(x, -alpha) * gnm_sinpi(alpha)));
}
- if (x < 2 || x * x / 4 < alpha)
+ if (bessel_j_series_domain (x, alpha))
return bessel_j_series (x, alpha);
nb = 1 + (long)na; /* nb-1 <= alpha < nb */
@@ -1781,6 +1782,15 @@ static gnm_float bessel_y(gnm_float x, gnm_float alpha)
return gnm_nan;
}
na = gnm_floor(alpha);
+
+ if (alpha != na &&
+ bessel_j_series_domain (x, alpha) &&
+ bessel_j_series_domain (x, -alpha)) {
+ gnm_float s = gnm_sinpi (alpha);
+ gnm_float c = gnm_cospi (alpha);
+ return ((c ? bessel_j_series (x, alpha) * c : 0) - bessel_j_series (x, -alpha)) / s;
+ }
+
if (alpha < 0) {
/* Using Abramowitz & Stegun 9.1.2
* this may not be quite optimal (CPU and accuracy wise) */
@@ -2235,22 +2245,113 @@ L450:
/* ------------------------------------------------------------------------ */
+static gboolean
+bessel_j_series_domain (gnm_float x, gnm_float v)
+{
+ /*
+ * The series is valid for all possible values of x and v,
+ * but it isn't efficient for all.
+ */
+
+ if (v <= -0.999) {
+ /*
+ * For negative v we must ensure that nothing crazy
+ * happens when |v+k| reaches its minimum.
+ */
+ gnm_float rv = gnm_floor (v + 0.5);
+ if (gnm_abs (v - rv) * v * v < 1)
+ return FALSE;
+
+ /*
+ * The above condition ensure that at most one term
+ * increases relative to the prior term and that the
+ * next term undoes all of that increase.
+ */
+ }
+
+ /* For small x, the factorials will dominate quickly. */
+ if (gnm_abs (x) < 4)
+ return TRUE;
+
+ /*
+ * The factorials also dominate immediately if x*x/4<|v|.
+ * We must not go too far beyond that.
+ */
+ if (x * x / 16 > gnm_abs (v))
+ return FALSE;
+
+ return TRUE;
+}
+
+
static gnm_float
-bessel_j_series (gnm_float x, gnm_float alpha)
+bessel_j_series (gnm_float x, gnm_float v)
{
- gnm_float s, t;
- gnm_float xh = x / 2, xh2 = xh * xh;
- int k;
-
- t = gnm_pow (xh, alpha) / gnm_fact (alpha);
- s = t;
- for (k = 1; k < 1000; k++) {
- t *= -xh2 / (k * (k + alpha));
- s += t;
- if (k > 5 && gnm_abs (t) < GNM_EPSILON * gnm_abs (s))
- break;
+ GnmQuad qv, qxh, qfv, qs, qt;
+ int efv;
+ void *state = gnm_quad_start ();
+ gnm_float e, s;
+
+ gnm_quad_init (&qxh, x / 2);
+ gnm_quad_init (&qv, v);
+
+ gnm_quad_pow (&qt, &e, &qxh, &qv);
+
+ switch (qfactf (v, &qfv, &efv)) {
+ case 0:
+ gnm_quad_div (&qt, &qt, &qfv);
+ e -= efv;
+ break;
+ case 1:
+ qt = gnm_quad_zero;
+ e = 0;
+ break;
+ default:
+ gnm_quad_init (&qt, gnm_nan);
+ e = 0;
+ break;
+ }
+
+ qs = qt;
+ s = gnm_quad_value (&qs);
+ if (gnm_finite (s) && s != 0) {
+ int k;
+ GnmQuad qxh2;
+
+ gnm_quad_mul (&qxh2, &qxh, &qxh);
+
+ for (k = 1; k < 200; k++) {
+ GnmQuad qa, qb;
+ gnm_float t;
+
+ gnm_quad_mul (&qt, &qt, &qxh2);
+ gnm_quad_init (&qa, -k);
+ gnm_quad_sub (&qb, &qv, &qa);
+ gnm_quad_mul (&qa, &qa, &qb);
+ gnm_quad_div (&qt, &qt, &qa);
+ t = gnm_quad_value (&qt);
+#if 0
+ g_printerr ("%5d: %g\n", k, t);
+#endif
+ if (t == 0)
+ break;
+ gnm_quad_add (&qs, &qs, &qt);
+ s = gnm_quad_value (&qs);
+ if (k > 5 &&
+ gnm_abs (t) <= GNM_EPSILON / 1024 * gnm_abs (s) &&
+ gnm_abs (k + v) > 2)
+ break;
+ }
}
+ s = gnm_ldexp (s, (int)CLAMP (e, G_MININT, G_MAXINT));
+
+ gnm_quad_end (state);
+
+#if 0
+ g_printerr ("J_%g(%g) = %g\n", v, x, s);
+#endif
+
return s;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]