--- /dev/null
+/* 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;
+}