]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Cleanup for cash patch .
[postgresql] / src / backend / utils / adt / cash.c
1 /*
2  * cash.c
3  * Written by D'Arcy J.M. Cain
4  *
5  * Functions to allow input and output of money normally but store
6  * and handle it as int4s
7  *
8  * A slightly modified version of this file and a discussion of the
9  * workings can be found in the book "Software Solutions in C" by
10  * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7.
11  *
12  * $Header: /cvsroot/pgsql/src/backend/utils/adt/cash.c,v 1.14 1997/09/13 12:05:32 momjian Exp $
13  */
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <limits.h>
18 #include <ctype.h>
19 #include <locale.h>
20
21 #include "postgres.h"
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/cash.h"
25
26 static const char *num_word(Cash value);
27
28 /* when we go to 64 bit values we will have to modify this */
29 #define CASH_BUFSZ              24
30
31 #define TERMINATOR              (CASH_BUFSZ - 1)
32 #define LAST_PAREN              (TERMINATOR - 1)
33 #define LAST_DIGIT              (LAST_PAREN - 1)
34
35 #ifdef USE_LOCALE
36 static struct lconv *lconv = NULL;
37
38 #endif
39
40 /* cash_in()
41  * Convert a string to a cash data type.
42  * Format is [$]###[,]###[.##]
43  * Examples: 123.45 $123.45 $123,456.78
44  *
45  * This is currently implemented as a 32-bit integer.
46  * XXX HACK It looks as though some of the symbols for
47  *      monetary values returned by localeconv() can be multiple
48  *      bytes/characters. This code assumes one byte only. - tgl 97/04/14
49  */
50 Cash       *
51 cash_in(const char *str)
52 {
53         Cash       *result;
54
55         Cash            value = 0;
56         Cash            dec = 0;
57         Cash            sgn = 1;
58         int                     seen_dot = 0;
59         const char *s = str;
60         int                     fpoint;
61         char            dsymbol,
62                                 ssymbol,
63                                 psymbol,
64                                 nsymbol,
65                                 csymbol;
66
67 #ifdef USE_LOCALE
68         if (lconv == NULL)
69                 lconv = localeconv();
70
71         /* frac_digits in the C locale seems to return CHAR_MAX */
72         /* best guess is 2 in this case I think */
73         fpoint = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2);           /* int_frac_digits? */
74
75         dsymbol = *lconv->mon_decimal_point;
76         ssymbol = *lconv->mon_thousands_sep;
77         csymbol = *lconv->currency_symbol;
78         psymbol = *lconv->positive_sign;
79         nsymbol = *lconv->negative_sign;
80 #else
81         fpoint = 2;
82         dsymbol = '.';
83         ssymbol = ',';
84         csymbol = '$';
85         psymbol = '+';
86         nsymbol = '-';
87 #endif
88
89         /* we need to add all sorts of checking here.  For now just */
90         /* strip all leading whitespace and any leading dollar sign */
91         while (isspace(*s) || *s == csymbol)
92                 s++;
93
94         /* a leading minus or paren signifies a negative number */
95         /* again, better heuristics needed */
96         if (*s == nsymbol || *s == '(')
97         {
98                 sgn = -1;
99                 s++;
100
101         }
102         else if (*s == psymbol)
103         {
104                 s++;
105         }
106
107         while (isspace(*s) || *s == csymbol)
108                 s++;
109
110         for (;; s++)
111         {
112                 /* we look for digits as int4 as we have less */
113                 /* than the required number of decimal places */
114                 if (isdigit(*s) && dec < fpoint)
115                 {
116                         value = (value * 10) + *s - '0';
117
118                         if (seen_dot)
119                                 dec++;
120
121                         /* decimal point? then start counting fractions... */
122                 }
123                 else if (*s == dsymbol && !seen_dot)
124                 {
125                         seen_dot = 1;
126
127                         /* "thousands" separator? then skip... */
128                 }
129                 else if (*s == ssymbol)
130                 {
131
132                 }
133                 else
134                 {
135                         /* round off */
136                         if (isdigit(*s) && *s >= '5')
137                                 value++;
138
139                         /* adjust for less than required decimal places */
140                         for (; dec < fpoint; dec++)
141                                 value *= 10;
142
143                         break;
144                 }
145         }
146
147         while (isspace(*s) || *s == '0' || *s == ')')
148                 s++;
149
150         if (*s != '\0')
151                 elog(WARN, "Bad money external representation %s", str);
152
153         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
154                 elog(WARN, "Memory allocation failed, can't input cash '%s'", str);
155
156         *result = (value * sgn);
157
158         return (result);
159 }                                                               /* cash_in() */
160
161
162 /* cash_out()
163  * Function to convert cash to a dollars and cents representation.
164  * XXX HACK This code appears to assume US conventions for
165  *      positive-valued amounts. - tgl 97/04/14
166  */
167 const char *
168 cash_out(Cash *in_value)
169 {
170         Cash            value = *in_value;
171         char       *result;
172         char            buf[CASH_BUFSZ];
173         int                     minus = 0;
174         int                     count = LAST_DIGIT;
175         int                     point_pos;
176         int                     comma_position = 0;
177         char            mon_group,
178                                 comma,
179                                 points;
180         char            csymbol,
181                                 dsymbol,
182                            *nsymbol;
183         char            convention;
184
185 #ifdef USE_LOCALE
186         if (lconv == NULL)
187                 lconv = localeconv();
188
189         mon_group = *lconv->mon_grouping;
190         comma = *lconv->mon_thousands_sep;
191         csymbol = *lconv->currency_symbol;
192         dsymbol = *lconv->mon_decimal_point;
193         nsymbol = lconv->negative_sign;
194         /* frac_digits in the C locale seems to return CHAR_MAX */
195         /* best guess is 2 in this case I think */
196         points = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2);           /* int_frac_digits? */
197         convention = lconv->n_sign_posn;
198 #else
199         mon_group = 3;
200         comma = ',';
201         csymbol = '$';
202         dsymbol = '.';
203         nsymbol = "-";
204         points = 2;
205         convention = 0;
206 #endif
207
208         point_pos = LAST_DIGIT - points;
209
210         /* We're playing a little fast and loose with this.  Shoot me. */
211         if (!mon_group || mon_group == CHAR_MAX)
212                 mon_group = 3;
213
214         /* allow more than three decimal points and separate them */
215         if (comma)
216         {
217                 point_pos -= (points - 1) / mon_group;
218                 comma_position = point_pos % (mon_group + 1);
219         }
220
221         /* we work with positive amounts and add the minus sign at the end */
222         if (value < 0)
223         {
224                 minus = 1;
225                 value *= -1;
226         }
227
228         /* allow for trailing negative strings */
229         memset(buf, ' ', CASH_BUFSZ);
230         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
231
232         while (value || count > (point_pos - 2))
233         {
234                 if (points && count == point_pos)
235                         buf[count--] = dsymbol;
236                 else if (comma && count % (mon_group + 1) == comma_position)
237                         buf[count--] = comma;
238
239                 buf[count--] = (value % 10) + '0';
240                 value /= 10;
241         }
242
243         buf[count] = csymbol;
244
245         if (buf[LAST_DIGIT] == ',')
246                 buf[LAST_DIGIT] = buf[LAST_PAREN];
247
248         /* see if we need to signify negative amount */
249         if (minus)
250         {
251                 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
252                         elog(WARN, "Memory allocation failed, can't output cash", NULL);
253
254                 /* Position code of 0 means use parens */
255                 if (convention == 0)
256                         sprintf(result, "(%s)", buf + count);
257                 else if (convention == 2)
258                         sprintf(result, "%s%s", buf + count, nsymbol);
259                 else
260                         sprintf(result, "%s%s", nsymbol, buf + count);
261         }
262         else
263         {
264                 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
265                         elog(WARN, "Memory allocation failed, can't output cash", NULL);
266
267                 strcpy(result, buf + count);
268         }
269
270         return (result);
271 }                                                               /* cash_out() */
272
273
274 bool
275 cash_eq(Cash *c1, Cash *c2)
276 {
277         if (!PointerIsValid(c1) || !PointerIsValid(c2))
278                 return (FALSE);
279
280         return (*c1 == *c2);
281 }                                                               /* cash_eq() */
282
283 bool
284 cash_ne(Cash *c1, Cash *c2)
285 {
286         if (!PointerIsValid(c1) || !PointerIsValid(c2))
287                 return (FALSE);
288
289         return (*c1 != *c2);
290 }                                                               /* cash_ne() */
291
292 bool
293 cash_lt(Cash *c1, Cash *c2)
294 {
295         if (!PointerIsValid(c1) || !PointerIsValid(c2))
296                 return (FALSE);
297
298         return (*c1 < *c2);
299 }                                                               /* cash_lt() */
300
301 bool
302 cash_le(Cash *c1, Cash *c2)
303 {
304         if (!PointerIsValid(c1) || !PointerIsValid(c2))
305                 return (FALSE);
306
307         return (*c1 <= *c2);
308 }                                                               /* cash_le() */
309
310 bool
311 cash_gt(Cash *c1, Cash *c2)
312 {
313         if (!PointerIsValid(c1) || !PointerIsValid(c2))
314                 return (FALSE);
315
316         return (*c1 > *c2);
317 }                                                               /* cash_gt() */
318
319 bool
320 cash_ge(Cash *c1, Cash *c2)
321 {
322         if (!PointerIsValid(c1) || !PointerIsValid(c2))
323                 return (FALSE);
324
325         return (*c1 >= *c2);
326 }                                                               /* cash_ge() */
327
328
329 /* cash_pl()
330  * Add two cash values.
331  */
332 Cash       *
333 cash_pl(Cash *c1, Cash *c2)
334 {
335         Cash       *result;
336
337         if (!PointerIsValid(c1) || !PointerIsValid(c2))
338                 return (NULL);
339
340         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
341                 elog(WARN, "Memory allocation failed, can't add cash", NULL);
342
343         *result = (*c1 + *c2);
344
345         return (result);
346 }                                                               /* cash_pl() */
347
348
349 /* cash_mi()
350  * Subtract two cash values.
351  */
352 Cash       *
353 cash_mi(Cash *c1, Cash *c2)
354 {
355         Cash       *result;
356
357         if (!PointerIsValid(c1) || !PointerIsValid(c2))
358                 return (NULL);
359
360         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
361                 elog(WARN, "Memory allocation failed, can't subtract cash", NULL);
362
363         *result = (*c1 - *c2);
364
365         return (result);
366 }                                                               /* cash_mi() */
367
368
369 /* cash_mul()
370  * Multiply cash by floating point number.
371  */
372 Cash       *
373 cash_mul(Cash *c, float8 *f)
374 {
375         Cash       *result;
376
377         if (!PointerIsValid(f) || !PointerIsValid(c))
378                 return (NULL);
379
380         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
381                 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
382
383         *result = ((*f) * (*c));
384
385         return (result);
386 }                                                               /* cash_mul() */
387
388
389 /* cash_div()
390  * Divide cash by floating point number.
391  *
392  * XXX Don't know if rounding or truncating is correct behavior.
393  * Round for now. - tgl 97/04/15
394  */
395 Cash       *
396 cash_div(Cash *c, float8 *f)
397 {
398         Cash       *result;
399
400         if (!PointerIsValid(f) || !PointerIsValid(c))
401                 return (NULL);
402
403         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
404                 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
405
406         if (*f == 0.0)
407                 elog(WARN, "cash_div:  divide by 0.0 error");
408
409         *result = rint(*c / *f);
410
411         return (result);
412 }                                                               /* cash_div() */
413
414
415 /* cashlarger()
416  * Return larger of two cash values.
417  */
418 Cash       *
419 cashlarger(Cash *c1, Cash *c2)
420 {
421         Cash       *result;
422
423         if (!PointerIsValid(c1) || !PointerIsValid(c2))
424                 return (NULL);
425
426         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
427                 elog(WARN, "Memory allocation failed, can't return larger cash", NULL);
428
429         *result = ((*c1 > *c2) ? *c1 : *c2);
430
431         return (result);
432 }                                                               /* cashlarger() */
433
434
435 /* cashsmaller()
436  * Return smaller of two cash values.
437  */
438 Cash       *
439 cashsmaller(Cash *c1, Cash *c2)
440 {
441         Cash       *result;
442
443         if (!PointerIsValid(c1) || !PointerIsValid(c2))
444                 return (NULL);
445
446         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
447                 elog(WARN, "Memory allocation failed, can't return smaller cash", NULL);
448
449         *result = ((*c1 < *c2) ? *c1 : *c2);
450
451         return (result);
452 }                                                               /* cashsmaller() */
453
454
455 /* cash_words_out()
456  * This converts a int4 as well but to a representation using words
457  * Obviously way North American centric - sorry
458  */
459 const char *
460 cash_words_out(Cash *value)
461 {
462         static char buf[128];
463         char       *p = buf;
464         Cash            m0;
465         Cash            m1;
466         Cash            m2;
467         Cash            m3;
468
469         /* work with positive numbers */
470         if (*value < 0)
471         {
472                 *value *= -1;
473                 strcpy(buf, "minus ");
474                 p += 6;
475         }
476         else
477         {
478                 *buf = 0;
479         }
480
481         m0 = *value % 100;                      /* cents */
482         m1 = (*value / 100) % 1000; /* hundreds */
483         m2 = (*value / 100000) % 1000;          /* thousands */
484         m3 = *value / 100000000 % 1000;         /* millions */
485
486         if (m3)
487         {
488                 strcat(buf, num_word(m3));
489                 strcat(buf, " million ");
490         }
491
492         if (m2)
493         {
494                 strcat(buf, num_word(m2));
495                 strcat(buf, " thousand ");
496         }
497
498         if (m1)
499                 strcat(buf, num_word(m1));
500
501         if (!*p)
502                 strcat(buf, "zero");
503
504         strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
505         strcat(buf, num_word(m0));
506         strcat(buf, m0 == 1 ? " cent" : " cents");
507         *buf = toupper(*buf);
508         return (buf);
509 }                                                               /* cash_words_out() */
510
511
512 /*************************************************************************
513  * Private routines
514  ************************************************************************/
515
516 static const char *
517 num_word(Cash value)
518 {
519         static char buf[128];
520         static const char *small[] = {
521                 "zero", "one", "two", "three", "four", "five", "six", "seven",
522                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
523                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
524                 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
525         };
526         const char **big = small + 18;
527         int                     tu = value % 100;
528
529         /* deal with the simple cases first */
530         if (value <= 20)
531                 return (small[value]);
532
533         /* is it an even multiple of 100? */
534         if (!tu)
535         {
536                 sprintf(buf, "%s hundred", small[value / 100]);
537                 return (buf);
538         }
539
540         /* more than 99? */
541         if (value > 99)
542         {
543                 /* is it an even multiple of 10 other than 10? */
544                 if (value % 10 == 0 && tu > 10)
545                         sprintf(buf, "%s hundred %s",
546                                         small[value / 100], big[tu / 10]);
547                 else if (tu < 20)
548                         sprintf(buf, "%s hundred and %s",
549                                         small[value / 100], small[tu]);
550                 else
551                         sprintf(buf, "%s hundred %s %s",
552                                         small[value / 100], big[tu / 10], small[tu % 10]);
553
554         }
555         else
556         {
557                 /* is it an even multiple of 10 other than 10? */
558                 if (value % 10 == 0 && tu > 10)
559                         sprintf(buf, "%s", big[tu / 10]);
560                 else if (tu < 20)
561                         sprintf(buf, "%s", small[tu]);
562                 else
563                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
564         }
565
566         return (buf);
567 }                                                               /* num_word() */