[goffice] Math: Introduce go_ascii_dtoa.



commit e5e569d3fa6d391547804b57cd2b54efcc2f6175
Author: Morten Welinder <terra gnome org>
Date:   Wed Apr 2 09:15:19 2014 -0400

    Math: Introduce go_ascii_dtoa.
    
    This will format a round-trip safe string from a double with a nod to
    avoiding unnecessary ...00003 and ...99998 endings.

 ChangeLog              |    4 +
 NEWS                   |    1 +
 goffice/math/go-math.c |  177 +++++++++++++++++++++++++++++++++++++++++++++++-
 goffice/math/go-math.h |    4 +
 tests/test-math.c      |   34 +++++++++
 5 files changed, 219 insertions(+), 1 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 0ba26b5..c86a6f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2014-04-02  Morten Welinder  <terra gnome org>
+
+       * goffice/math/go-math.c (go_ascii_dtoa): New function.
+
 2014-04-01  Morten Welinder  <terra gnome org>
 
        * goffice/utils/go-format.c (go_format_output_conditional_to_odf):
diff --git a/NEWS b/NEWS
index 417b573..1fdf864 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ Morten:
        * Implement numbered colours in number formats.
        * Fix plugin deactivation.
        * Plug leaks.
+       * New function go_ascii_dtoa.
 
 --------------------------------------------------------------------------
 goffice 0.10.13:
diff --git a/goffice/math/go-math.c b/goffice/math/go-math.c
index 574403a..7e5efde 100644
--- a/goffice/math/go-math.c
+++ b/goffice/math/go-math.c
@@ -29,6 +29,7 @@
 #include <locale.h>
 #include <signal.h>
 #include <errno.h>
+#include <string.h>
 
 double go_nan;
 double go_pinf;
@@ -68,7 +69,7 @@ running_under_buggy_valgrind (void)
 void
 _go_math_init (void)
 {
-       const char *bug_url = "http://bugzilla.gnome.org/enter_bug.cgi?product=libgoffice";;
+       const char *bug_url = "https://bugzilla.gnome.org/enter_bug.cgi?product=libgoffice";;
        char *old_locale;
        double d;
 #ifdef SIGFPE
@@ -436,6 +437,180 @@ go_ascii_strtod (const char *s, char **end)
        return res;
 }
 
+static void
+go_ascii_dtoa_fmt_helper (char *buf, size_t bufsiz, char fmt,
+                         double d, int prec)
+{
+       char fmtstr[8], *p = fmtstr;
+
+       *p++ = '%';
+       *p++ = '.';
+       if (prec >= 10) *p++ = '0' + (prec / 10);
+       *p++ = '0' + (prec % 10);
+       *p++ = fmt;
+       *p = 0;
+
+       g_ascii_formatd (buf, bufsiz, fmtstr, d);
+}
+
+/**
+ * go_ascii_dtoa:
+ * @d: value to convert
+ * @fmt: printf-style format specified; 'e', 'E', or 'g'.
+ *
+ * Returns: (transfer full): a string that when converted back into a floating-
+ * point value will produce @d.  This function will use '.' and decimal
+ * point.
+ *
+ * The resulting string is not necessarily the shortest possible, but some
+ * care is put into avoiding needless ...0003 and ...9998 endings.
+ */
+char *
+go_ascii_dtoa (double d, char fmt)
+{
+       char buf[128];
+       char *epos;
+       gboolean enotation;
+       static int prec;
+
+       if (!prec) {
+               double l10 = log10 (FLT_RADIX);
+               prec = (int)ceil (DBL_MANT_DIG * l10) + (l10 != (int)l10);
+               g_assert (prec >= 11 && prec <= 99);
+       }
+
+       if (!go_finite (d)) {
+               if (d > 0)
+                       return g_strdup ("+inf");
+               if (d < 0)
+                       return g_strdup ("-inf");
+               return g_strdup ("nan");
+       }
+
+       if (fabs (d) >= 1e16 || (d != 0 && fabs (d) < 1e-4))
+               fmt = g_ascii_islower (fmt) ? 'e' : 'E';
+       enotation = g_ascii_tolower (fmt) == 'e';
+
+       /* e-notation counts digits after the decimal point.  */
+       if (enotation)
+               prec--;
+
+       go_ascii_dtoa_fmt_helper (buf, 128, fmt, d, prec);
+       epos = (enotation ? strchr (buf, fmt) : buf + strlen (buf));
+       if (epos[-1] == '0') {
+               /* Nothing */
+       } else if ((epos[-1] <= '5' && epos[-2] == '0' && epos[-3] == '0') ||
+                  (epos[-1] >= '5' && epos[-2] == '9' && epos[-3] == '9')) {
+               char bufm1[128];
+               go_ascii_dtoa_fmt_helper (bufm1, 128, fmt, d, prec - 1);
+               if (go_ascii_strtod (bufm1, NULL) == d) {
+                       strcpy (buf, bufm1);
+                       epos = (enotation ? strchr (buf, fmt) : buf + strlen (buf));
+               }
+       }
+
+       if (epos[-1] == '0' && strchr (buf, '.')) {
+               char *p = epos;
+               while (p[-1] == '0') p--;
+               if (p[-1] == '.') p--;
+               memmove (p, epos, strlen (epos) + 1);
+       }
+
+       return g_strdup (buf);
+}
+
+
+#ifdef GOFFICE_WITH_LONG_DOUBLE
+
+static void
+go_ascii_ldtoa_fmt_helper (char *buf, size_t bufsiz, char fmt,
+                          long double d, int prec)
+{
+       char fmtstr[8], *p = fmtstr;
+       char *old_locale;
+
+       *p++ = '%';
+       *p++ = '.';
+       if (prec >= 10) *p++ = '0' + (prec / 10);
+       *p++ = '0' + (prec % 10);
+       *p++ = fmt;
+       *p = 0;
+
+       /* Just use setlocale and hope for the best.  */
+       old_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
+       g_snprintf (buf, bufsiz, fmtstr, d);
+       setlocale (LC_NUMERIC, old_locale);
+       g_free (old_locale);
+}
+
+
+/**
+ * go_ascii_ldtoa:
+ * @d: value to convert
+ * @fmt: printf-style format specified; 'e', 'E', or 'g'.
+ *
+ * Returns: (transfer full): a string that when converted back into a floating-
+ * point value will produce @d.  This function will use '.' and decimal
+ * point.
+ *
+ * The resulting string is not necessarily the shortest possible, but some
+ * care is put into avoiding needless ...0003 and ...9998 endings.
+ */
+char *
+go_ascii_ldtoa (long double d, char fmt)
+{
+       char buf[128];
+       char *epos;
+       gboolean enotation;
+       static int prec;
+
+       if (!prec) {
+               double l10 = log10 (FLT_RADIX);
+               prec = (int)ceil (LDBL_MANT_DIG * l10) + (l10 != (int)l10);
+               g_assert (prec >= 11 && prec <= 99);
+       }
+
+       if (!go_finitel (d)) {
+               if (d > 0)
+                       return g_strdup ("+inf");
+               if (d < 0)
+                       return g_strdup ("-inf");
+               return g_strdup ("nan");
+       }
+
+       if (fabsl (d) >= 1e16 || (d != 0 && fabsl (d) < 1e-4))
+               fmt = g_ascii_islower (fmt) ? 'e' : 'E';
+       enotation = g_ascii_tolower (fmt) == 'e';
+
+       /* e-notation counts digits after the decimal point.  */
+       if (enotation)
+               prec--;
+
+       go_ascii_ldtoa_fmt_helper (buf, 128, fmt, d, prec);
+       epos = (enotation ? strchr (buf, fmt) : buf + strlen (buf));
+       if (epos[-1] == '0') {
+               /* Nothing */
+       } else if ((epos[-1] <= '5' && epos[-2] == '0' && epos[-3] == '0') ||
+                  (epos[-1] >= '5' && epos[-2] == '9' && epos[-3] == '9')) {
+               char bufm1[128];
+               go_ascii_ldtoa_fmt_helper (bufm1, 128, fmt, d, prec - 1);
+               if (go_ascii_strtold (bufm1, NULL) == d) {
+                       strcpy (buf, bufm1);
+                       epos = (enotation ? strchr (buf, fmt) : buf + strlen (buf));
+               }
+       }
+
+       if (epos[-1] == '0' && strchr (buf, '.')) {
+               char *p = epos;
+               while (p[-1] == '0') p--;
+               if (p[-1] == '.') p--;
+               memmove (p, epos, strlen (epos) + 1);
+       }
+
+       return g_strdup (buf);
+}
+#endif
+
 
 #ifdef GOFFICE_SUPPLIED_LOG1P
 double
diff --git a/goffice/math/go-math.h b/goffice/math/go-math.h
index 07bd147..5a05fbc 100644
--- a/goffice/math/go-math.h
+++ b/goffice/math/go-math.h
@@ -39,6 +39,8 @@ double go_pow10 (int n);
 double go_strtod (const char *s, char **end);
 double go_ascii_strtod (const char *s, char **end);
 
+char *go_ascii_dtoa (double d, char fmt);
+
 double go_sinpi (double x);
 double go_cospi (double x);
 double go_tanpi (double x);
@@ -85,6 +87,8 @@ long double go_pow10l (int n);
 long double go_strtold (const char *s, char **end);
 long double go_ascii_strtold (const char *s, char **end);
 
+char *go_ascii_ldtoa (long double d, char fmt);
+
 long double go_sinpil (long double x);
 long double go_cospil (long double x);
 long double go_tanpil (long double x);
diff --git a/tests/test-math.c b/tests/test-math.c
index bc49bbb..f3d7c2b 100644
--- a/tests/test-math.c
+++ b/tests/test-math.c
@@ -91,10 +91,44 @@ trig_tests (void)
 
 /* ------------------------------------------------------------------------- */
 
+#define TEST1(a_) do {                                         \
+       double d = (a_);                                        \
+       char *s = go_ascii_dtoa (d, 'g');                       \
+       double r = go_ascii_strtod (s, NULL);                   \
+       g_printerr ("dtoa: %.17g --> \"%s\" --> %.17g\n", d, s, r);     \
+       g_free (s);                                             \
+        g_assert (r == d);                                     \
+} while (0)
+
+static void
+test_dtoa (void)
+{
+       TEST1 (0.1);
+       TEST1 (go_pinf);
+       TEST1 (50388143.0682372152805328369140625);
+       TEST1 (54167628.179999999701976776123046875);
+       TEST1 (9161196241250.05078125);
+       TEST1 (9.87e+031);
+       TEST1 (9.944932e+031);
+       TEST1 (8.948471e+015);
+       TEST1 (1.23456789012345e+300);
+       TEST1 (1.23456e-300);
+       TEST1 (1e-006);
+}
+
+#undef TEST1
+
+/* ------------------------------------------------------------------------- */
+
 int
 main (int argc, char **argv)
 {
+       libgoffice_init ();
+
+       test_dtoa ();
        trig_tests ();
 
+       libgoffice_shutdown ();
+
        return 0;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]