e586b19d6915a91445bc98251fa3fa35cfece8b4
[pspp] / tests / math / decimal-test.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2015 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <stdlib.h>
20 #include <stdbool.h>
21 #include <assert.h>
22 #include <string.h>
23
24 #include "libpspp/compiler.h"
25 #include "math/decimal.h"
26 #include <limits.h>
27 #include <float.h>
28 #include <math.h>
29
30 /* Canonicalise a string  holding the decimal representation of a number.
31    For example, leading zeros left of the decimal point are removed, as are
32    trailing zeros to the right.
33
34    This function is used purely for testing, and need not and is not intended
35    to be efficient.
36  */
37 static char *
38 canonicalise_string (const char *s)
39 {
40   char *out;
41   char *dot = NULL;
42   bool negative = false;
43   char *p;
44   char *temp = malloc (strlen (s) + 3);
45   char *last_leading_zero = NULL;
46
47   /* Strip leading - if present */
48   if (*s == '-')
49     {
50       negative = true;
51       s++;
52     }
53   
54   strcpy (temp, "00");
55   strcat (temp, s);
56
57   char *first_trailing_zero = NULL;
58   char *significant_digit = NULL;
59   for (p = temp; *p; p++)
60     {
61       if (*p == '0' && dot == NULL && significant_digit == NULL)
62         last_leading_zero = p;
63
64       if (*p == '0' && first_trailing_zero == NULL)
65         first_trailing_zero = p;
66
67       if (*p == '.')
68         {
69           dot = p;
70           first_trailing_zero = NULL;
71         }
72
73       if (*p >= '1' && *p <= '9')
74         {
75           significant_digit = p;
76           first_trailing_zero = NULL;
77         }
78     }
79
80   if (first_trailing_zero && dot)
81     *first_trailing_zero = '\0';
82
83   if (last_leading_zero)
84     {
85       /* Strip leading zeros */
86       out = last_leading_zero + 1;
87
88       /* But if we now start with . put a zero back again */
89       if (dot == last_leading_zero + 1)
90         out--;
91     }
92
93
94   if (negative)
95     {
96       out--;
97       out[0] = '-';
98     }
99   
100   if (!significant_digit)
101     {
102       *out = '0';
103       *(out+1) = '\0';
104     }
105     
106
107   return out;
108 }
109
110
111 /* Tests both the decimal_to_string function, and the decimal_input_from_string 
112    function */
113 static void
114 test_run (const char *input)
115   {
116     struct decimal test;
117     struct decimal number;
118     decimal_init_from_string (&number, input);
119
120     char *s = decimal_to_string (&number);
121     char *canon = canonicalise_string (input);
122     if (0 != strcmp (canon, s))
123       {
124         fprintf (stdout, "\"%s\" does not match \n\"%s\"\n", canon, s);
125         exit (1);
126       }
127
128     decimal_init_from_string (&test, s);
129     assert (0 == decimal_cmp (&test, &number));
130
131     free (s);
132   }
133
134
135 static void
136 test_can (const char *in, const char *soll)
137 {
138   char *ist = canonicalise_string (in);
139   if (0 != strcmp (soll, ist))
140     {
141       printf ("\"%s\" canonicalises to \"%s\" (should be \"%s\")\n", in, ist, soll);
142     }
143 }
144
145
146 #if 0
147 static void
148 dump_scale (const struct decimal *low, const struct decimal *interval, int n_ticks)
149 {
150   int i;
151   struct decimal tick = *interval;
152   printf ("Lowest: %s\n", decimal_to_string (low));
153   for (i = 0; i <= n_ticks; ++i)
154     {
155       printf ("Tick %d: %s (%g)\n", i, decimal_to_string (&tick), decimal_to_double (&tick));
156       decimal_add (&tick, interval);
157     }
158 }
159 #endif
160
161
162
163 static void
164 test_ceil (double x)
165 {
166   struct decimal dx;
167   decimal_from_double (&dx, x);
168   int act = decimal_ceil (&dx);
169   int expected = ceil (x);
170   
171   assert (act == expected);
172 }
173
174 static void
175 test_floor (double x)
176 {
177   struct decimal dx;
178   decimal_from_double (&dx, x);
179   int act = decimal_floor (&dx);
180   int expected = floor (x);
181   
182   assert (act == expected);
183 }
184
185
186 static void
187 test_addition (const struct decimal *one_, const struct decimal *two)
188 {
189   struct decimal one = *one_;
190   
191   decimal_add (&one, two);
192   
193   double dsum = decimal_to_double (&one);
194
195   char sdsum1[256];
196   char sdsum2[256];
197
198   snprintf (sdsum1, 256, "%s", decimal_to_string (&one));
199   snprintf (sdsum2, 256, "%g", dsum);
200
201   assert (strcmp (sdsum1, sdsum2) == 0);
202 }
203
204
205 static void
206 test_multiplication (const struct decimal *d, int m)
207 {
208   char b1[256];
209   char b2[256];
210   struct decimal dest = *d;
211   double x = decimal_to_double (&dest);
212
213   decimal_int_multiply (&dest, m);
214
215   double y = decimal_to_double (&dest);
216
217   snprintf (b1, 256, "%g", m * x);
218   snprintf (b2, 256, "%g", y);
219   assert (0 == strcmp (b1, b2));
220 }
221
222
223
224 int 
225 main (int argc UNUSED, char **argv UNUSED)
226 {
227   /* Test that our canonicalise function works for all corner cases we
228      can think of. */
229
230   test_can ("500", "500");
231   test_can ("5", "5");
232   test_can ("-3", "-3");
233   test_can ("-3.001", "-3.001");
234   test_can ("-03.001", "-3.001");
235   test_can ("-.0301", "-0.0301");
236   test_can ("0314.09", "314.09");
237   test_can ("0314.090", "314.09");
238   test_can ("0314.0900340", "314.090034");
239   test_can ("0.0", "0");
240   test_can ("0.", "0");
241   test_can (".0", "0");
242   test_can ("-.1", "-0.1");
243   test_can (".090", "0.09");
244   test_can ("03410.098700", "3410.0987");
245   test_can ("-03410.098700", "-3410.0987");
246
247   /* Test the conversion functions */
248   test_run ("-90000");
249   test_run ("-3");
250   test_run ("50001");
251   test_run ("500");
252   test_run ("350");
253   test_run ("050");
254   test_run ("4");
255   test_run ("0");
256   test_run (".45");
257   test_run ("-.45");
258   test_run ("666666666");
259   test_run ("6000000000");
260   test_run ("0.000000005");
261   test_run ("0.00000000000000000000000000000000000000005");
262   test_run ("0.0234");
263   test_run ("0.234");
264   test_run ("-0123.45600");
265
266   test_ceil (5.21);
267   test_ceil (-4.32);
268   test_ceil (0);
269   test_ceil (0.0009);
270
271   test_floor (4.09);
272   test_floor (-4.09);
273   test_floor (0);
274   test_floor (0.004);
275
276
277   {
278     struct decimal high = {2, 0};
279     struct decimal low = {2, -1};
280
281     test_addition (&high, &low);
282   }
283
284
285   {
286     struct decimal high = {10, 0};
287     struct decimal low = {2, -1};
288
289     test_addition (&high, &low);
290   }
291
292
293   {
294     struct decimal high = {10, 0};
295     struct decimal low = {-2, -1};
296
297     test_addition (&high, &low);
298   }
299
300   {
301     struct decimal high = {12, -5};
302     struct decimal low = {-2, -1};
303
304     test_addition (&high, &low);
305   }
306
307   {
308     struct decimal high = {-112, -1};
309     struct decimal low = {2, -1};
310
311     test_addition (&high, &low);
312   }
313
314
315   {
316     struct decimal m = {10, 0};
317
318     test_multiplication (&m, 11);
319   }
320
321
322   {
323     struct decimal m = {ORD_MAX - 2, 0};
324
325     test_multiplication (&m, 11);
326   }
327
328
329   {
330     struct decimal m = {34, 0};
331
332     test_multiplication (&m, 0);
333   }
334
335   {
336     struct decimal m = {34, -20};
337
338     test_multiplication (&m, 33);
339   }
340
341   {
342     struct decimal m = {304, 2};
343
344     test_multiplication (&m, -33);
345   }
346
347   return 0;
348 }