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