[goffice] Tests: add test program for approximating constants as doubles.
- From: Morten Welinder <mortenw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [goffice] Tests: add test program for approximating constants as doubles.
- Date: Tue, 8 Mar 2016 02:57:18 +0000 (UTC)
commit 451e3ea2e9dd512fd8ed944e86fed2c4904de829
Author: Morten Welinder <terra gnome org>
Date: Mon Mar 7 21:55:57 2016 -0500
Tests: add test program for approximating constants as doubles.
See comments in the program.
tests/.gitignore | 2 +
tests/Makefile.am | 6 +-
tests/constants.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 208 insertions(+), 2 deletions(-)
---
diff --git a/tests/.gitignore b/tests/.gitignore
index fbb41c5..14e1d9e 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,6 +5,7 @@
*.o
.deps
.libs
+*~
Makefile
Makefile.in
go-demo
@@ -14,3 +15,4 @@ shapes-demo
test-format
test-math
test-quad
+constants
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 56de845..5429508 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,4 @@
-check_PROGRAMS=test-quad test-math test-format
+check_PROGRAMS=test-quad test-math test-format constants
if WITH_GTK
check_PROGRAMS += pie-demo go-demo shapes-demo mf-demo
endif
@@ -9,6 +9,9 @@ AM_CFLAGS = $(GOFFICE_CFLAGS)
TESTS = test-quad test-math
+constants_LDADD = $(GOFFICE_PLUGIN_LIBADD)
+constants_SOURCES = constants.c
+
pie_demo_LDADD = $(GOFFICE_PLUGIN_LIBADD)
pie_demo_SOURCES = pie-demo.c
@@ -31,4 +34,3 @@ test_format_LDADD = $(GOFFICE_PLUGIN_LIBADD)
test_format_SOURCES = test-format.c
EXTRA_DIST = go-demo.ui
-
diff --git a/tests/constants.c b/tests/constants.c
new file mode 100644
index 0000000..3a4dc0f
--- /dev/null
+++ b/tests/constants.c
@@ -0,0 +1,202 @@
+#include <goffice/goffice.h>
+
+// This program answers the question of whether a constant, c, is more
+// accurately represented as a double than its inverse, 1/c.
+//
+// The purpose is to help squeeze a few more bits out of calculations
+// that involve multiplying or dividing by the constant.
+//
+// Consider the density function for the normal distribution:
+// d(x) = (1/sqrt(2*pi)) * exp(-x^2/2)
+// As written, this multiplies by a constant, 1/sqrt(2*pi), whose value
+// we could compute and state with enough decimals to guarantee that we
+// end up using the best possible value of type double. But we could
+// also use division by the constant sqrt(2*pi). Which is best?
+//
+// The traditional answer has been that division is expensive, so avoid
+// it, but that isn't really true anymore. Here we are only interested
+// in accuracy.
+//
+// Assumptions:
+// 1. double is base-2 floating-point with 53 mantissa bits (including
+// the implied leading bit).
+// 2. we want to minimize the error on the product or quotient.
+// 3. we know nothing special about the second factor that would
+// force one choice or the other.
+//
+// For sqrt(2pi) the answer is that it is ever so slightly better to use
+// multiplication. But it's a very close call. But to get the right value,
+// one cannot start with the best double approximating sqrt(2pi) and compute
+// 1/sqrt(2pi) from that.
+
+// ----------------------------------------
+// Examining constant sqrt(2pi)
+//
+// Value bit pattern: 1.0100000011011001001100011111111101100010011100000101|10011 * 2^1
+// Value as double: 2.50662827463100068570156508940272033214569091796875
+// Relative error: -7.31205e-17
+// Correct digits: 16.14
+//
+// Inverse bit pattern: 1.1001100010000100010100110011110101000011011001010000|10001 * 2^-2
+// Inverse as double: 0.398942280401432702863218082711682654917240142822265625
+// Relative error: -6.24734e-17
+// Correct digits: 16.20
+// Can inverse be computed as double: no
+//
+// Conclusion: for sqrt(2pi), use inverse
+// ----------------------------------------
+
+
+
+static GOQuad qln10;
+
+static void
+print_bits (GOQuad const *qc)
+{
+ GOQuad qx = *qc;
+ GOQuad qhalf;
+ int e;
+ double s;
+ int b;
+ int N = 53 + 5;
+
+ qhalf.h = 0.5;
+ qhalf.l = 0;
+
+ if (qx.h < 0) {
+ g_printerr ("-");
+ qx.h = -qx.h;
+ qx.l = -qx.l;
+ }
+
+ (void)frexp (go_quad_value (&qx), &e);
+ s = ldexp (1.0, -e);
+ qx.h *= s;
+ qx.l *= s;
+
+ for (b = 0; b < N; b++) {
+ GOQuad d;
+ qx.h *= 2;
+ qx.l *= 2;
+ // For the last bit we compare against 1/2 to simulate rounding.
+ go_quad_sub (&d, &qx, (b == (N - 1) ? &qhalf : &go_quad_one));
+ if (go_quad_value (&d) >= 0) {
+ qx = d;
+ g_printerr ("1");
+ } else {
+ g_printerr ("0");
+ }
+ if (b == 0)
+ g_printerr (".");
+ if (b == 52)
+ g_printerr ("|");
+ }
+
+ g_printerr (" * 2^%d", e - 1);
+}
+
+
+static double
+qmlogabs (GOQuad const *qx)
+{
+ GOQuad qy;
+ qy.h = fabs (qx->h); qy.l = fabs (qx->l);
+ go_quad_log (&qy, &qy);
+ go_quad_div (&qy, &qy, &qln10);
+ return -go_quad_value (&qy);
+}
+
+
+static void
+examine_constant (const char *descr, GOQuad const *qc)
+{
+ double dc, dic, dcic;
+ GOQuad qd, qic;
+ double dig_direct, dig_inverse;
+
+ g_printerr ("-----------------------------------------------------------------------------\n");
+ g_printerr ("Examining constant %s\n", descr);
+ g_printerr ("\n");
+
+ dc = go_quad_value (qc);
+ g_printerr ("Value bit pattern: ");
+ print_bits (qc);
+ g_printerr ("\n");
+ g_printerr ("Value as double: %.55g\n", dc);
+ go_quad_init (&qd, dc);
+ go_quad_sub (&qd, qc, &qd);
+ go_quad_div (&qd, &qd, qc);
+ g_printerr ("Relative error: %g\n", go_quad_value (&qd));
+ dig_direct = qmlogabs (&qd);
+ g_printerr ("Correct digits: %.2f\n", dig_direct);
+
+ g_printerr ("\n");
+
+ // For the inverse we assume that using double-double is good enough to
+ // answer our question.
+ go_quad_div (&qic, &go_quad_one, qc);
+ dic = go_quad_value (&qic);
+ g_printerr ("Inverse bit pattern: ");
+ print_bits (&qic);
+ g_printerr ("\n");
+ g_printerr ("Inverse as double: %.55g\n", dic);
+ go_quad_init (&qd, dic);
+ go_quad_sub (&qd, &qic, &qd);
+ go_quad_div (&qd, &qd, &qic);
+ g_printerr ("Relative error: %g\n", go_quad_value (&qd));
+ dig_inverse = qmlogabs (&qd);
+ g_printerr ("Correct digits: %.2f\n", dig_inverse);
+
+ dcic = (double)(1 / dc);
+ g_printerr ("Can inverse be computed as double: %s\n",
+ (dcic == dic ? "yes" : "no"));
+
+ g_printerr ("\n");
+
+ g_printerr ("Conclusion: for %s, use %s\n", descr,
+ (dig_direct >= dig_inverse ? "direct" : "inverse"));
+}
+
+int
+main (int argc, char **argv)
+{
+ void *state;
+ GOQuad qc;
+
+ g_printerr ("Determine for certain constants, c, whether its representation\n");
+ g_printerr ("as a double is more accurate than its inverse's.\n\n");
+
+ state = go_quad_start ();
+
+ go_quad_init (&qln10, 10);
+ go_quad_log (&qln10, &qln10);
+
+ examine_constant ("pi", &go_quad_pi);
+ examine_constant ("e", &go_quad_e);
+ examine_constant ("EulerGamma", &go_quad_euler);
+
+ examine_constant ("log(2)", &go_quad_ln2);
+ examine_constant ("log(10)", &qln10);
+ go_quad_div (&qc, &go_quad_ln2, &qln10);
+ examine_constant ("log10(2)", &qc);
+
+ examine_constant ("sqrt(2)", &go_quad_sqrt2);
+ go_quad_init (&qc, 3);
+ go_quad_sqrt (&qc, &qc);
+ examine_constant ("sqrt(3)", &qc);
+ go_quad_init (&qc, 5);
+ go_quad_sqrt (&qc, &qc);
+ examine_constant ("sqrt(5)", &qc);
+ go_quad_sqrt (&qc, &go_quad_pi);
+ examine_constant ("sqrt(pi)", &qc);
+ go_quad_sqrt (&qc, &go_quad_2pi);
+ examine_constant ("sqrt(2pi)", &qc);
+
+ go_quad_init (&qc, 180);
+ go_quad_div (&qc, &qc, &go_quad_pi);
+ examine_constant ("180/pi", &qc);
+
+ go_quad_end (state);
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]