[goffice] Rangefuncs: improve precision
- From: Morten Welinder <mortenw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [goffice] Rangefuncs: improve precision
- Date: Wed, 23 Nov 2011 20:24:17 +0000 (UTC)
commit 8ea33770839d1132647b8d3d056706c06b140ccb
Author: Morten Welinder <terra gnome org>
Date: Wed Nov 23 15:23:23 2011 -0500
Rangefuncs: improve precision
ChangeLog | 8 ++
NEWS | 1 +
goffice/math/Makefile.am | 2 +
goffice/math/go-accumulator.c | 134 ++++++++++++++++++++++++++++++++++
goffice/math/go-accumulator.h | 38 ++++++++++
goffice/math/go-quad.c | 2 +-
goffice/math/go-rangefunc.c | 160 ++++++++++++++++++++++-------------------
goffice/math/go-rangefunc.h | 2 +
goffice/math/goffice-math.h | 5 +-
9 files changed, 274 insertions(+), 78 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index fa2d115..da2ec57 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2011-11-23 Morten Welinder <terra gnome org>
+
+ * goffice/math/go-accumulator.c: New file.
+
+ * goffice/math/go-rangefunc.c (go_range_devsq, go_range_sumsq)
+ (go_range_sum, go_range_average): Base on GOAccumulator.
+ (go_range_constant): New function.
+
2011-10-27 Andreas J. Guelzow <aguelzow pyrshep ca>
* goffice/gtk/goffice-gtk.c (recent_func_log_func): new
diff --git a/NEWS b/NEWS
index 586ffbd..8c9b22f 100644
--- a/NEWS
+++ b/NEWS
@@ -48,6 +48,7 @@ Morten:
* Recognize scientific formats with longer exponents.
* Fix problem with sticky format colouring.
* Improve certain range functions in corner cases. [#660564]
+ * Make go_range_sum perfect, modulo overflow. [#664656]
* Add resource manager for embedded files.
* Plug leaks.
diff --git a/goffice/math/Makefile.am b/goffice/math/Makefile.am
index 4ca683e..94f2c73 100644
--- a/goffice/math/Makefile.am
+++ b/goffice/math/Makefile.am
@@ -1,6 +1,7 @@
noinst_LTLIBRARIES = libgoffice-math.la
libgoffice_math_la_SOURCES = \
+ go-accumulator.c \
go-math.c \
go-rangefunc.c \
go-regression.c \
@@ -15,6 +16,7 @@ libgoffice_math_la_SOURCES = \
libgoffice_math_ladir = $(goffice_include_dir)/math
libgoffice_math_la_HEADERS = \
goffice-math.h \
+ go-accumulator.h \
go-math.h \
go-rangefunc.h \
go-regression.h \
diff --git a/goffice/math/go-accumulator.c b/goffice/math/go-accumulator.c
new file mode 100644
index 0000000..a9d44aa
--- /dev/null
+++ b/goffice/math/go-accumulator.c
@@ -0,0 +1,134 @@
+/*
+ * go-accumulator.c: high-precision sums
+ *
+ * Authors:
+ * Morten Welinder <terra gnome org>
+ *
+ * See http://code.activestate.com/recipes/393090/
+ *
+ * Note: for this to work, the processor must evaluate with the right
+ * precision. For ix86 that means trouble as the default is to evaluate
+ * with long-double precision internally. We solve this by setting the
+ * relevant x87 control flag.
+ *
+ * Note: the compiler should not rearrange expressions.
+ */
+
+#include <goffice/goffice-config.h>
+#include "go-accumulator.h"
+#include "go-quad.h"
+#include <math.h>
+#include <float.h>
+
+#ifndef DOUBLE
+
+#define DOUBLE double
+#define SUFFIX(_n) _n
+#define DOUBLE_MANT_DIG DBL_MANT_DIG
+
+struct GOAccumulator_ {
+ GArray *partials;
+};
+#define ACC SUFFIX(GOAccumulator)
+
+#ifdef GOFFICE_WITH_LONG_DOUBLE
+#include "go-accumulator.c"
+#undef DOUBLE
+#undef SUFFIX
+#undef DOUBLE_MANT_DIG
+#define DOUBLE long double
+#define SUFFIX(_n) _n ## l
+#define DOUBLE_MANT_DIG LDBL_MANT_DIG
+
+struct GOAccumulatorl_ {
+ GArray *partials;
+};
+
+#endif
+#endif
+
+
+
+
+gboolean
+SUFFIX(go_accumulator_functional) (void)
+{
+ return SUFFIX(go_quad_functional) ();
+}
+
+void *
+SUFFIX(go_accumulator_start) (void)
+{
+ return SUFFIX(go_quad_start) ();
+}
+
+void
+SUFFIX(go_accumulator_end) (void *state)
+{
+ SUFFIX(go_quad_end) (state);
+}
+
+ACC *
+SUFFIX(go_accumulator_new) (void)
+{
+ ACC *res = g_new (ACC, 1);
+ res->partials = g_array_sized_new (FALSE, FALSE, sizeof (DOUBLE), 10);
+ return res;
+}
+
+void
+SUFFIX(go_accumulator_free) (ACC *acc)
+{
+ g_return_if_fail (acc != NULL);
+
+ g_array_free (acc->partials, TRUE);
+ g_free (acc);
+}
+
+void
+SUFFIX(go_accumulator_clear) (ACC *acc)
+{
+ g_return_if_fail (acc != NULL);
+ g_array_set_size (acc->partials, 0);
+}
+
+void
+SUFFIX(go_accumulator_add) (ACC *acc, DOUBLE x)
+{
+ unsigned ui = 0, uj;
+
+ g_return_if_fail (acc != NULL);
+
+ for (uj = 0; uj < acc->partials->len; uj++) {
+ DOUBLE y = g_array_index (acc->partials, DOUBLE, uj);
+ DOUBLE hi, lo;
+ if (SUFFIX(fabs)(x) < SUFFIX(fabs)(y)) {
+ DOUBLE t = x;
+ x = y;
+ y = t;
+ }
+ hi = x + y;
+ lo = y - (hi - x);
+ if (lo != 0) {
+ g_array_index (acc->partials, DOUBLE, ui) = lo;
+ ui++;
+ }
+ x = hi;
+ }
+ g_array_set_size (acc->partials, ui + 1);
+ g_array_index (acc->partials, DOUBLE, ui) = x;
+}
+
+DOUBLE
+SUFFIX(go_accumulator_value) (ACC *acc)
+{
+ unsigned ui;
+ DOUBLE sum = 0;
+
+ g_return_val_if_fail (acc != NULL, 0);
+
+ for (ui = 0; ui < acc->partials->len; ui++)
+ sum += g_array_index (acc->partials, DOUBLE, ui);
+
+ return sum;
+}
diff --git a/goffice/math/go-accumulator.h b/goffice/math/go-accumulator.h
new file mode 100644
index 0000000..1b25d96
--- /dev/null
+++ b/goffice/math/go-accumulator.h
@@ -0,0 +1,38 @@
+#ifndef __GO_ACCUMULATOR_H
+#define __GO_ACCUMULATOR_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct GOAccumulator_ GOAccumulator;
+
+gboolean go_accumulator_functional (void);
+void *go_accumulator_start (void);
+void go_accumulator_end (void *state);
+
+GOAccumulator *go_accumulator_new (void);
+void go_accumulator_free (GOAccumulator *acc);
+void go_accumulator_clear (GOAccumulator *acc);
+void go_accumulator_add (GOAccumulator *acc, double x);
+double go_accumulator_value (GOAccumulator *acc);
+
+
+#ifdef GOFFICE_WITH_LONG_DOUBLE
+
+typedef struct GOAccumulatorl_ GOAccumulatorl;
+
+gboolean go_accumulator_functionall (void);
+void *go_accumulator_startl (void);
+void go_accumulator_endl (void *state);
+
+GOAccumulatorl *go_accumulator_newl (void);
+void go_accumulator_freel (GOAccumulatorl *acc);
+void go_accumulator_clearl (GOAccumulatorl *acc);
+void go_accumulator_addl (GOAccumulatorl *acc, long double x);
+long double go_accumulator_valuel (GOAccumulatorl *acc);
+#endif
+
+G_END_DECLS
+
+#endif
diff --git a/goffice/math/go-quad.c b/goffice/math/go-quad.c
index 8d76934..ba87492 100644
--- a/goffice/math/go-quad.c
+++ b/goffice/math/go-quad.c
@@ -4,7 +4,7 @@
* Authors:
* Morten Welinder <terra gnome org>
*
- * This follows "A Floating-Point Technoque for Extending the Available
+ * This follows "A Floating-Point Technique for Extending the Available
* Precision" by T. J. Dekker in _Numerische Mathematik_ 18.
* Springer Verlag 1971.
*
diff --git a/goffice/math/go-rangefunc.c b/goffice/math/go-rangefunc.c
index 1faa6f2..70e2eae 100644
--- a/goffice/math/go-rangefunc.c
+++ b/goffice/math/go-rangefunc.c
@@ -8,6 +8,8 @@
#include <goffice/goffice-config.h>
#include "go-rangefunc.h"
+#include "go-accumulator.h"
+#include "go-quad.h"
#include <math.h>
#include <stdlib.h>
@@ -42,54 +44,27 @@
/* ------------------------------------------------------------------------- */
-static int
-SUFFIX(identical_helper) (DOUBLE const *xs, int n)
+static SUFFIX(GOAccumulator)*
+SUFFIX(sum_helper) (DOUBLE const *xs, int n)
{
- DOUBLE x;
- int i;
-
- if (n <= 1)
- return n;
-
- x = xs[0];
- for (i = 1; i < n; i++)
- if (x != xs[i])
- break;
-
- return i;
-}
-
-static LDOUBLE
-SUFFIX(sum_helper) (DOUBLE const *xs, int n, int *all_id)
-{
- LDOUBLE sum;
- int i;
-
- /*
- * We treat the an initial constant part special in order to catch
- * the important special case of completely constant. We do not
- * want to accumulate rounding errors even though the extra 11?
- * bits in long double will shield us from n<2^11, give or take.
- */
-
- i = SUFFIX(identical_helper) (xs, n);
- *all_id = (i == n);
- sum = i ? i * (LDOUBLE)(xs[0]) : 0;
-
- for (; i < n; i++)
- sum += xs[i];
-
- return sum;
+ SUFFIX(GOAccumulator) *acc = SUFFIX(go_accumulator_new) ();
+ while (n > 0) {
+ n--;
+ SUFFIX(go_accumulator_add) (acc, xs[n]);
+ }
+ return acc;
}
-/* ------------------------------------------------------------------------- */
/* Arithmetic sum. */
int
SUFFIX(go_range_sum) (DOUBLE const *xs, int n, DOUBLE *res)
{
- int all_id;
- *res = SUFFIX(sum_helper) (xs, n, &all_id);
+ void *state = SUFFIX(go_accumulator_start) ();
+ SUFFIX(GOAccumulator) *acc = SUFFIX(sum_helper) (xs, n);
+ *res = SUFFIX(go_accumulator_value) (acc);
+ SUFFIX(go_accumulator_free) (acc);
+ SUFFIX(go_accumulator_end) (state);
return 0;
}
@@ -97,20 +72,19 @@ SUFFIX(go_range_sum) (DOUBLE const *xs, int n, DOUBLE *res)
int
SUFFIX(go_range_sumsq) (DOUBLE const *xs, int n, DOUBLE *res)
{
- /* http://bugzilla.gnome.org/show_bug.cgi?id=131588 */
-#ifdef HAVE_LONG_DOUBLE
- long double x, sum = 0;
-#else
- DOUBLE x, sum = 0;
-#endif
- int i;
-
- for (i = 0; i < n; i++) {
- x = xs[i];
- sum += x * x;
+ void *state = SUFFIX(go_accumulator_start) ();
+ SUFFIX(GOAccumulator) *acc = SUFFIX(go_accumulator_new) ();
+ while (n > 0) {
+ SUFFIX(GOQuad) q;
+ n--;
+ SUFFIX(go_quad_init) (&q, xs[n]);
+ SUFFIX(go_quad_mul) (&q, &q, &q);
+ SUFFIX(go_accumulator_add) (acc, q.h);
+ SUFFIX(go_accumulator_add) (acc, q.l);
}
-
- *res = sum;
+ *res = SUFFIX(go_accumulator_value) (acc);
+ SUFFIX(go_accumulator_free) (acc);
+ SUFFIX(go_accumulator_end) (state);
return 0;
}
@@ -118,15 +92,16 @@ SUFFIX(go_range_sumsq) (DOUBLE const *xs, int n, DOUBLE *res)
int
SUFFIX(go_range_average) (DOUBLE const *xs, int n, DOUBLE *res)
{
- int all_id;
-
if (n <= 0)
return 1;
- *res = SUFFIX(sum_helper) (xs, n, &all_id) / n;
- if (all_id)
+ if (SUFFIX(go_range_constant) (xs, n)) {
*res = xs[0];
+ return 0;
+ }
+ SUFFIX(go_range_sum) (xs, n, res);
+ *res /= n;
return 0;
}
@@ -172,9 +147,11 @@ SUFFIX(go_range_maxabs) (DOUBLE const *xs, int n, DOUBLE *res)
DOUBLE max = SUFFIX(fabs) (xs[0]);
int i;
- for (i = 1; i < n; i++)
- if (SUFFIX(fabs) (xs[i]) > max)
- max = SUFFIX(fabs) (xs[i]);
+ for (i = 1; i < n; i++) {
+ DOUBLE xabs = SUFFIX(fabs) (xs[i]);
+ if (xabs > max)
+ max = xabs;
+ }
*res = max;
return 0;
} else
@@ -185,23 +162,46 @@ SUFFIX(go_range_maxabs) (DOUBLE const *xs, int n, DOUBLE *res)
int
SUFFIX(go_range_devsq) (DOUBLE const *xs, int n, DOUBLE *res)
{
- LDOUBLE q = 0;
-
- if (n > 0) {
- int i, all_id;
- LDOUBLE m = SUFFIX(sum_helper) (xs, n, &all_id) / n;
-
- if (all_id) {
- *res = 0;
- return 0;
- }
-
- for (i = 0; i < n; i++) {
- LDOUBLE dx = xs[i] - m;
- q += dx * dx;
+ if (SUFFIX(go_range_constant) (xs, n))
+ *res = 0;
+ else {
+ void *state;
+ SUFFIX(GOAccumulator) *acc;
+ DOUBLE sum, sumh, suml;
+ SUFFIX(GOQuad) qavg, qtmp, qn;
+
+ state = SUFFIX(go_accumulator_start) ();
+ acc = SUFFIX(sum_helper) (xs, n);
+ sumh = SUFFIX(go_accumulator_value) (acc);
+ SUFFIX(go_accumulator_add) (acc, -sumh);
+ suml = SUFFIX(go_accumulator_value) (acc);
+
+ SUFFIX(go_quad_init) (&qavg, sumh);
+ SUFFIX(go_quad_init) (&qtmp, suml);
+ SUFFIX(go_quad_add) (&qavg, &qavg, &qtmp);
+ SUFFIX(go_range_sum) (xs, n, &sum);
+ SUFFIX(go_quad_init) (&qn, n);
+ SUFFIX(go_quad_div) (&qavg, &qavg, &qn);
+ /*
+ * The just-generated qavg isn't exact, but should be
+ * good to near-GOQuad precision. We hope that's good
+ * enough.
+ */
+
+ SUFFIX(go_accumulator_clear) (acc);
+ while (n > 0) {
+ SUFFIX(GOQuad) q;
+ n--;
+ SUFFIX(go_quad_init) (&q, xs[n]);
+ SUFFIX(go_quad_sub) (&q, &q, &qavg);
+ SUFFIX(go_quad_mul) (&q, &q, &q);
+ SUFFIX(go_accumulator_add) (acc, q.h);
+ SUFFIX(go_accumulator_add) (acc, q.l);
}
+ *res = SUFFIX(go_accumulator_value) (acc);
+ SUFFIX(go_accumulator_free) (acc);
+ SUFFIX(go_accumulator_end) (state);
}
- *res = q;
return 0;
}
@@ -285,6 +285,16 @@ SUFFIX(go_range_median_inter_nonconst) (DOUBLE *xs, int n, DOUBLE *res)
}
int
+SUFFIX(go_range_constant) (DOUBLE const *xs, int n)
+{
+ int i;
+ for (i = 1; i < n; i++)
+ if (xs[0] != xs[i])
+ return 0;
+ return 1;
+}
+
+int
SUFFIX(go_range_increasing) (DOUBLE const *xs, int n)
{
int i;
diff --git a/goffice/math/go-rangefunc.h b/goffice/math/go-rangefunc.h
index 006ccfb..4c9ff4c 100644
--- a/goffice/math/go-rangefunc.h
+++ b/goffice/math/go-rangefunc.h
@@ -17,6 +17,7 @@ int go_range_fractile_inter_sorted (double const *xs, int n, double *res, double
int go_range_median_inter (double const *xs, int n, double *res);
int go_range_median_inter_nonconst (double *xs, int n, double *res);
int go_range_median_inter_sorted (double const *xs, int n, double *res);
+int go_range_constant (double const *xs, int n);
int go_range_increasing (double const *xs, int n);
int go_range_decreasing (double const *xs, int n);
int go_range_vary_uniformly (double const *xs, int n);
@@ -36,6 +37,7 @@ int go_range_fractile_inter_sortedl (long double const *xs, int n, long double *
int go_range_median_interl (long double const *xs, int n, long double *res);
int go_range_median_inter_nonconstl (long double *xs, int n, long double *res);
int go_range_median_inter_sortedl (long double const *xs, int n, long double *res);
+int go_range_constantl (long double const *xs, int n);
int go_range_increasingl (long double const *xs, int n);
int go_range_decreasingl (long double const *xs, int n);
int go_range_vary_uniformlyl (long double const *xs, int n);
diff --git a/goffice/math/goffice-math.h b/goffice/math/goffice-math.h
index c307224..d0ae6f0 100644
--- a/goffice/math/goffice-math.h
+++ b/goffice/math/goffice-math.h
@@ -1,15 +1,16 @@
#ifndef __GOFFICE_MATH_H
#define __GOFFICE_MATH_H
+#include <goffice/math/go-accumulator.h>
#include <goffice/math/go-complex.h>
#include <goffice/math/go-cspline.h>
#include <goffice/math/go-distribution.h>
#include <goffice/math/go-fft.h>
#include <goffice/math/go-math.h>
#include <goffice/math/go-matrix3x3.h>
-#include <goffice/math/go-rangefunc.h>
-#include <goffice/math/go-regression.h>
#include <goffice/math/go-quad.h>
#include <goffice/math/go-R.h>
+#include <goffice/math/go-rangefunc.h>
+#include <goffice/math/go-regression.h>
#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]