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