]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Add debugging statement enabled by CASHDEBUG symbol definition.
[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.18 1997/10/25 05:11:06 thomas 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 *lconvert = NULL;
37 #endif
38
39 /* cash_in()
40  * Convert a string to a cash data type.
41  * Format is [$]###[,]###[.##]
42  * Examples: 123.45 $123.45 $123,456.78
43  *
44  * This is currently implemented as a 32-bit integer.
45  * XXX HACK It looks as though some of the symbols for
46  *      monetary values returned by localeconv() can be multiple
47  *      bytes/characters. This code assumes one byte only. - tgl 97/04/14
48  */
49 Cash       *
50 cash_in(const char *str)
51 {
52         Cash       *result;
53
54         Cash            value = 0;
55         Cash            dec = 0;
56         Cash            sgn = 1;
57         int                     seen_dot = 0;
58         const char *s = str;
59         int                     fpoint;
60         char            dsymbol,
61                                 ssymbol,
62                                 psymbol,
63                                 nsymbol,
64                                 csymbol;
65
66 #ifdef USE_LOCALE
67 #ifdef CASHDEBUG
68         setlocale(LC_ALL, "");
69         lconvert = localeconv();
70 #endif
71         if (lconvert == NULL)
72                 lconvert = localeconv();
73
74         /* frac_digits in the C locale seems to return CHAR_MAX */
75         /* best guess is 2 in this case I think */
76         fpoint = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2);             /* int_frac_digits? */
77
78         dsymbol = *lconvert->mon_decimal_point;
79         ssymbol = *lconvert->mon_thousands_sep;
80         csymbol = *lconvert->currency_symbol;
81         psymbol = *lconvert->positive_sign;
82         nsymbol = *lconvert->negative_sign;
83 #else
84         fpoint = 2;
85         dsymbol = '.';
86         ssymbol = ',';
87         csymbol = '$';
88         psymbol = '+';
89         nsymbol = '-';
90 #endif
91
92 #ifdef CASHDEBUG
93 printf( "cashin- precision %d; decimal %c; thousands %c; currency %c; positive %c; negative %c\n",
94  fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
95 #endif
96
97         /* we need to add all sorts of checking here.  For now just */
98         /* strip all leading whitespace and any leading dollar sign */
99         while (isspace(*s) || *s == csymbol)
100                 s++;
101
102         /* a leading minus or paren signifies a negative number */
103         /* again, better heuristics needed */
104         if (*s == nsymbol || *s == '(')
105         {
106                 sgn = -1;
107                 s++;
108
109         }
110         else if (*s == psymbol)
111         {
112                 s++;
113         }
114
115         while (isspace(*s) || *s == csymbol)
116                 s++;
117
118         for (;; s++)
119         {
120                 /* we look for digits as int4 as we have less */
121                 /* than the required number of decimal places */
122                 if (isdigit(*s) && dec < fpoint)
123                 {
124                         value = (value * 10) + *s - '0';
125
126                         if (seen_dot)
127                                 dec++;
128
129                         /* decimal point? then start counting fractions... */
130                 }
131                 else if (*s == dsymbol && !seen_dot)
132                 {
133                         seen_dot = 1;
134
135                         /* "thousands" separator? then skip... */
136                 }
137                 else if (*s == ssymbol)
138                 {
139
140                 }
141                 else
142                 {
143                         /* round off */
144                         if (isdigit(*s) && *s >= '5')
145                                 value++;
146
147                         /* adjust for less than required decimal places */
148                         for (; dec < fpoint; dec++)
149                                 value *= 10;
150
151                         break;
152                 }
153         }
154
155         while (isspace(*s) || *s == '0' || *s == ')')
156                 s++;
157
158         if (*s != '\0')
159                 elog(WARN, "Bad money external representation %s", str);
160
161         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
162                 elog(WARN, "Memory allocation failed, can't input cash '%s'", str);
163
164         *result = (value * sgn);
165
166         return (result);
167 }                                                               /* cash_in() */
168
169
170 /* cash_out()
171  * Function to convert cash to a dollars and cents representation.
172  * XXX HACK This code appears to assume US conventions for
173  *      positive-valued amounts. - tgl 97/04/14
174  */
175 const char *
176 cash_out(Cash *in_value)
177 {
178         Cash            value = *in_value;
179         char       *result;
180         char            buf[CASH_BUFSZ];
181         int                     minus = 0;
182         int                     count = LAST_DIGIT;
183         int                     point_pos;
184         int                     comma_position = 0;
185         char            mon_group,
186                                 comma,
187                                 points;
188         char            csymbol,
189                                 dsymbol,
190                            *nsymbol;
191         char            convention;
192
193 #ifdef USE_LOCALE
194         if (lconvert == NULL)
195                 lconvert = localeconv();
196
197         mon_group = *lconvert->mon_grouping;
198         comma = *lconvert->mon_thousands_sep;
199         csymbol = *lconvert->currency_symbol;
200         dsymbol = *lconvert->mon_decimal_point;
201         nsymbol = lconvert->negative_sign;
202         /* frac_digits in the C locale seems to return CHAR_MAX */
203         /* best guess is 2 in this case I think */
204         points = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2);             /* int_frac_digits? */
205         convention = lconvert->n_sign_posn;
206 #else
207         mon_group = 3;
208         comma = ',';
209         csymbol = '$';
210         dsymbol = '.';
211         nsymbol = "-";
212         points = 2;
213         convention = 0;
214 #endif
215
216         point_pos = LAST_DIGIT - points;
217
218         /* We're playing a little fast and loose with this.  Shoot me. */
219         if (!mon_group || mon_group == CHAR_MAX)
220                 mon_group = 3;
221
222         /* allow more than three decimal points and separate them */
223         if (comma)
224         {
225                 point_pos -= (points - 1) / mon_group;
226                 comma_position = point_pos % (mon_group + 1);
227         }
228
229         /* we work with positive amounts and add the minus sign at the end */
230         if (value < 0)
231         {
232                 minus = 1;
233                 value *= -1;
234         }
235
236         /* allow for trailing negative strings */
237         MemSet(buf, ' ', CASH_BUFSZ);
238         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
239
240         while (value || count > (point_pos - 2))
241         {
242                 if (points && count == point_pos)
243                         buf[count--] = dsymbol;
244                 else if (comma && count % (mon_group + 1) == comma_position)
245                         buf[count--] = comma;
246
247                 buf[count--] = (value % 10) + '0';
248                 value /= 10;
249         }
250
251         buf[count] = csymbol;
252
253         if (buf[LAST_DIGIT] == ',')
254                 buf[LAST_DIGIT] = buf[LAST_PAREN];
255
256         /* see if we need to signify negative amount */
257         if (minus)
258         {
259                 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
260                         elog(WARN, "Memory allocation failed, can't output cash", NULL);
261
262                 /* Position code of 0 means use parens */
263                 if (convention == 0)
264                         sprintf(result, "(%s)", buf + count);
265                 else if (convention == 2)
266                         sprintf(result, "%s%s", buf + count, nsymbol);
267                 else
268                         sprintf(result, "%s%s", nsymbol, buf + count);
269         }
270         else
271         {
272                 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
273                         elog(WARN, "Memory allocation failed, can't output cash", NULL);
274
275                 strcpy(result, buf + count);
276         }
277
278         return (result);
279 }                                                               /* cash_out() */
280
281
282 bool
283 cash_eq(Cash *c1, Cash *c2)
284 {
285         if (!PointerIsValid(c1) || !PointerIsValid(c2))
286                 return (FALSE);
287
288         return (*c1 == *c2);
289 }                                                               /* cash_eq() */
290
291 bool
292 cash_ne(Cash *c1, Cash *c2)
293 {
294         if (!PointerIsValid(c1) || !PointerIsValid(c2))
295                 return (FALSE);
296
297         return (*c1 != *c2);
298 }                                                               /* cash_ne() */
299
300 bool
301 cash_lt(Cash *c1, Cash *c2)
302 {
303         if (!PointerIsValid(c1) || !PointerIsValid(c2))
304                 return (FALSE);
305
306         return (*c1 < *c2);
307 }                                                               /* cash_lt() */
308
309 bool
310 cash_le(Cash *c1, Cash *c2)
311 {
312         if (!PointerIsValid(c1) || !PointerIsValid(c2))
313                 return (FALSE);
314
315         return (*c1 <= *c2);
316 }                                                               /* cash_le() */
317
318 bool
319 cash_gt(Cash *c1, Cash *c2)
320 {
321         if (!PointerIsValid(c1) || !PointerIsValid(c2))
322                 return (FALSE);
323
324         return (*c1 > *c2);
325 }                                                               /* cash_gt() */
326
327 bool
328 cash_ge(Cash *c1, Cash *c2)
329 {
330         if (!PointerIsValid(c1) || !PointerIsValid(c2))
331                 return (FALSE);
332
333         return (*c1 >= *c2);
334 }                                                               /* cash_ge() */
335
336
337 /* cash_pl()
338  * Add two cash values.
339  */
340 Cash       *
341 cash_pl(Cash *c1, Cash *c2)
342 {
343         Cash       *result;
344
345         if (!PointerIsValid(c1) || !PointerIsValid(c2))
346                 return (NULL);
347
348         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
349                 elog(WARN, "Memory allocation failed, can't add cash", NULL);
350
351         *result = (*c1 + *c2);
352
353         return (result);
354 }                                                               /* cash_pl() */
355
356
357 /* cash_mi()
358  * Subtract two cash values.
359  */
360 Cash       *
361 cash_mi(Cash *c1, Cash *c2)
362 {
363         Cash       *result;
364
365         if (!PointerIsValid(c1) || !PointerIsValid(c2))
366                 return (NULL);
367
368         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
369                 elog(WARN, "Memory allocation failed, can't subtract cash", NULL);
370
371         *result = (*c1 - *c2);
372
373         return (result);
374 }                                                               /* cash_mi() */
375
376
377 /* cash_mul_flt8()
378  * Multiply cash by float8.
379  */
380 Cash       *
381 cash_mul_flt8(Cash *c, float8 *f)
382 {
383         Cash       *result;
384
385         if (!PointerIsValid(f) || !PointerIsValid(c))
386                 return (NULL);
387
388         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
389                 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
390
391         *result = ((*f) * (*c));
392
393         return (result);
394 }                                                               /* cash_mul_flt8() */
395
396
397 /* flt8_mul_cash()
398  * Multiply float8 by cash.
399  */
400 Cash       *
401 flt8_mul_cash(float8 *f, Cash *c)
402 {
403         return (cash_mul_flt8(c, f));
404 }                                                               /* flt8_mul_cash() */
405
406
407 /* cash_div_flt8()
408  * Divide cash by float8.
409  *
410  * XXX Don't know if rounding or truncating is correct behavior.
411  * Round for now. - tgl 97/04/15
412  */
413 Cash       *
414 cash_div_flt8(Cash *c, float8 *f)
415 {
416         Cash       *result;
417
418         if (!PointerIsValid(f) || !PointerIsValid(c))
419                 return (NULL);
420
421         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
422                 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
423
424         if (*f == 0.0)
425                 elog(WARN, "cash_div:  divide by 0.0 error");
426
427         *result = rint(*c / *f);
428
429         return (result);
430 }                                                               /* cash_div_flt8() */
431
432 /* cash_mul_flt4()
433  * Multiply cash by float4.
434  */
435 Cash       *
436 cash_mul_flt4(Cash *c, float4 *f)
437 {
438         Cash       *result;
439
440         if (!PointerIsValid(f) || !PointerIsValid(c))
441                 return (NULL);
442
443         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
444                 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
445
446         *result = ((*f) * (*c));
447
448         return (result);
449 }                                                               /* cash_mul_flt4() */
450
451
452 /* flt4_mul_cash()
453  * Multiply float4 by float4.
454  */
455 Cash       *
456 flt4_mul_cash(float4 *f, Cash *c)
457 {
458         return (cash_mul_flt4(c, f));
459 }                                                               /* flt4_mul_cash() */
460
461
462 /* cash_div_flt4()
463  * Divide cash by float4.
464  *
465  * XXX Don't know if rounding or truncating is correct behavior.
466  * Round for now. - tgl 97/04/15
467  */
468 Cash       *
469 cash_div_flt4(Cash *c, float4 *f)
470 {
471         Cash       *result;
472
473         if (!PointerIsValid(f) || !PointerIsValid(c))
474                 return (NULL);
475
476         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
477                 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
478
479         if (*f == 0.0)
480                 elog(WARN, "cash_div:  divide by 0.0 error");
481
482         *result = rint(*c / *f);
483
484         return (result);
485 }                                                               /* cash_div_flt4() */
486
487
488 /* cash_mul_int4()
489  * Multiply cash by int4.
490  */
491 Cash       *
492 cash_mul_int4(Cash *c, int4 i)
493 {
494         Cash       *result;
495
496         if (!PointerIsValid(c))
497                 return (NULL);
498
499         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
500                 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
501
502         *result = ((i) * (*c));
503
504         return (result);
505 }                                                               /* cash_mul_int4() */
506
507
508 /* int4_mul_cash()
509  * Multiply int4 by cash.
510  */
511 Cash       *
512 int4_mul_cash(int4 i, Cash *c)
513 {
514         return (cash_mul_int4(c, i));
515 }                                                               /* int4_mul_cash() */
516
517
518 /* cash_div_int4()
519  * Divide cash by 4-byte integer.
520  *
521  * XXX Don't know if rounding or truncating is correct behavior.
522  * Round for now. - tgl 97/04/15
523  */
524 Cash       *
525 cash_div_int4(Cash *c, int4 i)
526 {
527         Cash       *result;
528
529         if (!PointerIsValid(c))
530                 return (NULL);
531
532         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
533                 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
534
535         if (i == 0)
536                 elog(WARN, "cash_idiv:  divide by 0 error");
537
538         *result = rint(*c / i);
539
540         return (result);
541 }                                                               /* cash_div_int4() */
542
543
544 /* cash_mul_int2()
545  * Multiply cash by int2.
546  */
547 Cash       *
548 cash_mul_int2(Cash *c, int2 s)
549 {
550         Cash       *result;
551
552         if (!PointerIsValid(c))
553                 return (NULL);
554
555         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
556                 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
557
558         *result = ((s) * (*c));
559
560         return (result);
561 }                                                               /* cash_mul_int2() */
562
563
564 /* int2_mul_cash()
565  * Multiply int2 by cash.
566  */
567 Cash       *
568 int2_mul_cash(int2 s, Cash *c)
569 {
570         return (cash_mul_int2(c, s));
571 }                                                               /* int2_mul_cash() */
572
573
574 /* cash_div_int2()
575  * Divide cash by int2.
576  *
577  * XXX Don't know if rounding or truncating is correct behavior.
578  * Round for now. - tgl 97/04/15
579  */
580 Cash       *
581 cash_div_int2(Cash *c, int2 s)
582 {
583         Cash       *result;
584
585         if (!PointerIsValid(c))
586                 return (NULL);
587
588         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
589                 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
590
591         if (s == 0)
592                 elog(WARN, "cash_div:  divide by 0 error");
593
594         *result = rint(*c / s);
595
596         return (result);
597 }                                                               /* cash_div_int2() */
598
599
600 /* cashlarger()
601  * Return larger of two cash values.
602  */
603 Cash       *
604 cashlarger(Cash *c1, Cash *c2)
605 {
606         Cash       *result;
607
608         if (!PointerIsValid(c1) || !PointerIsValid(c2))
609                 return (NULL);
610
611         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
612                 elog(WARN, "Memory allocation failed, can't return larger cash", NULL);
613
614         *result = ((*c1 > *c2) ? *c1 : *c2);
615
616         return (result);
617 }                                                               /* cashlarger() */
618
619
620 /* cashsmaller()
621  * Return smaller of two cash values.
622  */
623 Cash       *
624 cashsmaller(Cash *c1, Cash *c2)
625 {
626         Cash       *result;
627
628         if (!PointerIsValid(c1) || !PointerIsValid(c2))
629                 return (NULL);
630
631         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
632                 elog(WARN, "Memory allocation failed, can't return smaller cash", NULL);
633
634         *result = ((*c1 < *c2) ? *c1 : *c2);
635
636         return (result);
637 }                                                               /* cashsmaller() */
638
639
640 /* cash_words_out()
641  * This converts a int4 as well but to a representation using words
642  * Obviously way North American centric - sorry
643  */
644 const char *
645 cash_words_out(Cash *value)
646 {
647         static char buf[128];
648         char       *p = buf;
649         Cash            m0;
650         Cash            m1;
651         Cash            m2;
652         Cash            m3;
653
654         /* work with positive numbers */
655         if (*value < 0)
656         {
657                 *value *= -1;
658                 strcpy(buf, "minus ");
659                 p += 6;
660         }
661         else
662         {
663                 *buf = 0;
664         }
665
666         m0 = *value % 100;                      /* cents */
667         m1 = (*value / 100) % 1000; /* hundreds */
668         m2 = (*value / 100000) % 1000;          /* thousands */
669         m3 = *value / 100000000 % 1000;         /* millions */
670
671         if (m3)
672         {
673                 strcat(buf, num_word(m3));
674                 strcat(buf, " million ");
675         }
676
677         if (m2)
678         {
679                 strcat(buf, num_word(m2));
680                 strcat(buf, " thousand ");
681         }
682
683         if (m1)
684                 strcat(buf, num_word(m1));
685
686         if (!*p)
687                 strcat(buf, "zero");
688
689         strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
690         strcat(buf, num_word(m0));
691         strcat(buf, m0 == 1 ? " cent" : " cents");
692         *buf = toupper(*buf);
693         return (buf);
694 }                                                               /* cash_words_out() */
695
696
697 /*************************************************************************
698  * Private routines
699  ************************************************************************/
700
701 static const char *
702 num_word(Cash value)
703 {
704         static char buf[128];
705         static const char *small[] = {
706                 "zero", "one", "two", "three", "four", "five", "six", "seven",
707                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
708                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
709                 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
710         };
711         const char **big = small + 18;
712         int                     tu = value % 100;
713
714         /* deal with the simple cases first */
715         if (value <= 20)
716                 return (small[value]);
717
718         /* is it an even multiple of 100? */
719         if (!tu)
720         {
721                 sprintf(buf, "%s hundred", small[value / 100]);
722                 return (buf);
723         }
724
725         /* more than 99? */
726         if (value > 99)
727         {
728                 /* is it an even multiple of 10 other than 10? */
729                 if (value % 10 == 0 && tu > 10)
730                         sprintf(buf, "%s hundred %s",
731                                         small[value / 100], big[tu / 10]);
732                 else if (tu < 20)
733                         sprintf(buf, "%s hundred and %s",
734                                         small[value / 100], small[tu]);
735                 else
736                         sprintf(buf, "%s hundred %s %s",
737                                         small[value / 100], big[tu / 10], small[tu % 10]);
738
739         }
740         else
741         {
742                 /* is it an even multiple of 10 other than 10? */
743                 if (value % 10 == 0 && tu > 10)
744                         sprintf(buf, "%s", big[tu / 10]);
745                 else if (tu < 20)
746                         sprintf(buf, "%s", small[tu]);
747                 else
748                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
749         }
750
751         return (buf);
752 }                                                               /* num_word() */