New module to perform decimal floating point arithmetic for charts.
[pspp] / tests / math / decimal-test.c
diff --git a/tests/math/decimal-test.c b/tests/math/decimal-test.c
new file mode 100644 (file)
index 0000000..3e1e64e
--- /dev/null
@@ -0,0 +1,347 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+#include "math/decimal.h"
+#include <limits.h>
+#include <float.h>
+#include <math.h>
+
+/* Canonicalise a string  holding the decimal representation of a number.
+   For example, leading zeros left of the decimal point are removed, as are
+   trailing zeros to the right.
+
+   This function is used purely for testing, and need not and is not intended
+   to be efficient.
+ */
+char *
+canonicalise_string (const char *s)
+{
+  char *out;
+  char *dot = NULL;
+  bool negative = false;
+  char *p;
+  char *temp = malloc (strlen (s) + 3);
+  char *last_leading_zero = NULL;
+
+  /* Strip leading - if present */
+  if (*s == '-')
+    {
+      negative = true;
+      s++;
+    }
+  
+  strcpy (temp, "00");
+  strcat (temp, s);
+
+  char *first_trailing_zero = NULL;
+  char *significant_digit = NULL;
+  for (p = temp; *p; p++)
+    {
+      if (*p == '0' && dot == NULL && significant_digit == NULL)
+       last_leading_zero = p;
+
+      if (*p == '0' && first_trailing_zero == NULL)
+       first_trailing_zero = p;
+
+      if (*p == '.')
+       {
+         dot = p;
+         first_trailing_zero = NULL;
+       }
+
+      if (*p >= '1' && *p <= '9')
+       {
+         significant_digit = p;
+         first_trailing_zero = NULL;
+       }
+    }
+
+  if (first_trailing_zero && dot)
+    *first_trailing_zero = '\0';
+
+  if (last_leading_zero)
+    {
+      /* Strip leading zeros */
+      out = last_leading_zero + 1;
+
+      /* But if we now start with . put a zero back again */
+      if (dot == last_leading_zero + 1)
+       out--;
+    }
+
+
+  if (negative)
+    {
+      out--;
+      out[0] = '-';
+    }
+  
+  if (!significant_digit)
+    {
+      *out = '0';
+      *(out+1) = '\0';
+    }
+    
+
+  return out;
+}
+
+
+/* Tests both the decimal_to_string function, and the decimal_input_from_string 
+   function */
+void
+test_run (const char *input)
+  {
+    struct decimal test;
+    struct decimal number;
+    decimal_init_from_string (&number, input);
+
+    char *s = decimal_to_string (&number);
+    char *canon = canonicalise_string (input);
+    if (0 != strcmp (canon, s))
+      {
+       fprintf (stdout, "\"%s\" does not match \n\"%s\"\n", canon, s);
+       exit (1);
+      }
+
+    decimal_init_from_string (&test, s);
+    assert (0 == decimal_cmp (&test, &number));
+
+    free (s);
+  }
+
+
+void
+test_can (const char *in, const char *soll)
+{
+  char *ist = canonicalise_string (in);
+  if (0 != strcmp (soll, ist))
+    {
+      printf ("\"%s\" canonicalises to \"%s\" (should be \"%s\")\n", in, ist, soll);
+    }
+}
+
+
+void
+dump_scale (const struct decimal *low, const struct decimal *interval, int n_ticks)
+{
+  int i;
+  struct decimal tick = *interval;
+  printf ("Lowest: %s\n", decimal_to_string (low));
+  for (i = 0; i <= n_ticks; ++i)
+    {
+      printf ("Tick %d: %s (%g)\n", i, decimal_to_string (&tick), decimal_to_double (&tick));
+      decimal_add (&tick, interval);
+    }
+}
+
+
+
+void
+test_ceil (double x)
+{
+  struct decimal dx;
+  decimal_from_double (&dx, x);
+  int act = decimal_ceil (&dx);
+  int expected = ceil (x);
+  
+  assert (act == expected);
+}
+
+void
+test_floor (double x)
+{
+  struct decimal dx;
+  decimal_from_double (&dx, x);
+  int act = decimal_floor (&dx);
+  int expected = floor (x);
+  
+  assert (act == expected);
+}
+
+
+void
+test_addition (const struct decimal *one_, const struct decimal *two)
+{
+  struct decimal one = *one_;
+  double d1 = decimal_to_double (&one);
+  double d2 = decimal_to_double (two);
+  
+  decimal_add (&one, two);
+  
+  double dsum = decimal_to_double (&one);
+
+  char sdsum1[256];
+  char sdsum2[256];
+
+  snprintf (sdsum1, 256, "%s", decimal_to_string (&one));
+  snprintf (sdsum2, 256, "%g", dsum);
+
+  assert (strcmp (sdsum1, sdsum2) == 0);
+}
+
+
+void
+test_multiplication (const struct decimal *d, int m)
+{
+  char b1[256];
+  char b2[256];
+  struct decimal dest = *d;
+  double x = decimal_to_double (&dest);
+
+  decimal_int_multiply (&dest, m);
+
+  double y = decimal_to_double (&dest);
+
+  snprintf (b1, 256, "%g", m * x);
+  snprintf (b2, 256, "%g", y);
+  assert (0 == strcmp (b1, b2));
+}
+
+
+
+int 
+main (int argc, char **argv)
+{
+  /* Test that our canonicalise function works for all corner cases we
+     can think of. */
+
+  test_can ("500", "500");
+  test_can ("5", "5");
+  test_can ("-3", "-3");
+  test_can ("-3.001", "-3.001");
+  test_can ("-03.001", "-3.001");
+  test_can ("-.0301", "-0.0301");
+  test_can ("0314.09", "314.09");
+  test_can ("0314.090", "314.09");
+  test_can ("0314.0900340", "314.090034");
+  test_can ("0.0", "0");
+  test_can ("0.", "0");
+  test_can (".0", "0");
+  test_can ("-.1", "-0.1");
+  test_can (".090", "0.09");
+  test_can ("03410.098700", "3410.0987");
+  test_can ("-03410.098700", "-3410.0987");
+
+  /* Test the conversion functions */
+  test_run ("-90000");
+  test_run ("-3");
+  test_run ("50001");
+  test_run ("500");
+  test_run ("350");
+  test_run ("050");
+  test_run ("4");
+  test_run ("0");
+  test_run (".45");
+  test_run ("-.45");
+  test_run ("666666666");
+  test_run ("6000000000");
+  test_run ("0.000000005");
+  test_run ("0.00000000000000000000000000000000000000005");
+  test_run ("0.0234");
+  test_run ("0.234");
+  test_run ("-0123.45600");
+
+  test_ceil (5.21);
+  test_ceil (-4.32);
+  test_ceil (0);
+  test_ceil (0.0009);
+
+  test_floor (4.09);
+  test_floor (-4.09);
+  test_floor (0);
+  test_floor (0.004);
+
+
+  {
+    struct decimal high = {2, 0};
+    struct decimal low = {2, -1};
+
+    test_addition (&high, &low);
+  }
+
+
+  {
+    struct decimal high = {10, 0};
+    struct decimal low = {2, -1};
+
+    test_addition (&high, &low);
+  }
+
+
+  {
+    struct decimal high = {10, 0};
+    struct decimal low = {-2, -1};
+
+    test_addition (&high, &low);
+  }
+
+  {
+    struct decimal high = {12, -5};
+    struct decimal low = {-2, -1};
+
+    test_addition (&high, &low);
+  }
+
+  {
+    struct decimal high = {-112, -1};
+    struct decimal low = {2, -1};
+
+    test_addition (&high, &low);
+  }
+
+
+  {
+    struct decimal m = {10, 0};
+
+    test_multiplication (&m, 11);
+  }
+
+
+  {
+    struct decimal m = {ORD_MAX - 2, 0};
+
+    test_multiplication (&m, 11);
+  }
+
+
+  {
+    struct decimal m = {34, 0};
+
+    test_multiplication (&m, 0);
+  }
+
+  {
+    struct decimal m = {34, -20};
+
+    test_multiplication (&m, 33);
+  }
+
+  {
+    struct decimal m = {304, 2};
+
+    test_multiplication (&m, -33);
+  }
+
+  return 0;
+}