3 * Written by D'Arcy J.M. Cain
5 * Functions to allow input and output of money normally but store
6 * and handle it as int4s
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.
12 * $Header: /cvsroot/pgsql/src/backend/utils/adt/cash.c,v 1.29 1999/07/14 01:19:57 momjian Exp $
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/cash.h"
25 #include "utils/mcxt.h"
27 static const char *num_word(Cash value);
29 /* when we go to 64 bit values we will have to modify this */
32 #define TERMINATOR (CASH_BUFSZ - 1)
33 #define LAST_PAREN (TERMINATOR - 1)
34 #define LAST_DIGIT (LAST_PAREN - 1)
37 static struct lconv *lconvert = NULL;
42 * Convert a string to a cash data type.
43 * Format is [$]###[,]###[.##]
44 * Examples: 123.45 $123.45 $123,456.78
46 * This is currently implemented as a 32-bit integer.
47 * XXX HACK It looks as though some of the symbols for
48 * monetary values returned by localeconv() can be multiple
49 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
50 * XXX UNHACK Allow the currency symbol to be multi-byte.
54 cash_in(const char *str)
72 setlocale(LC_ALL, "");
73 lconvert = localeconv();
76 lconvert = localeconv();
78 /* frac_digits in the C locale seems to return CHAR_MAX */
79 /* best guess is 2 in this case I think */
80 fpoint = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
82 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
83 ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
84 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
85 psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
86 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
97 printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
98 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
101 /* we need to add all sorts of checking here. For now just */
102 /* strip all leading whitespace and any leading currency symbol */
105 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
106 s += strlen(csymbol);
109 printf("cashin- string is '%s'\n", s);
112 /* a leading minus or paren signifies a negative number */
113 /* again, better heuristics needed */
114 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
117 s += strlen(nsymbol);
119 printf("cashin- negative symbol; string is '%s'\n", s);
128 else if (*s == psymbol)
132 printf("cashin- string is '%s'\n", s);
137 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
138 s += strlen(csymbol);
141 printf("cashin- string is '%s'\n", s);
146 /* we look for digits as int4 as we have less */
147 /* than the required number of decimal places */
148 if (isdigit(*s) && dec < fpoint)
150 value = (value * 10) + *s - '0';
155 /* decimal point? then start counting fractions... */
157 else if (*s == dsymbol && !seen_dot)
161 /* "thousands" separator? then skip... */
163 else if (*s == ssymbol)
170 if (isdigit(*s) && *s >= '5')
173 /* adjust for less than required decimal places */
174 for (; dec < fpoint; dec++)
181 while (isspace(*s) || *s == '0' || *s == ')')
185 elog(ERROR, "Bad money external representation %s", str);
187 if (!PointerIsValid(result = palloc(sizeof(Cash))))
188 elog(ERROR, "Memory allocation failed, can't input cash '%s'", str);
190 *result = (value * sgn);
193 printf("cashin- result is %d\n", *result);
201 * Function to convert cash to a dollars and cents representation.
202 * XXX HACK This code appears to assume US conventions for
203 * positive-valued amounts. - tgl 97/04/14
206 cash_out(Cash *in_value)
208 Cash value = *in_value;
210 char buf[CASH_BUFSZ];
212 int count = LAST_DIGIT;
214 int comma_position = 0;
224 if (lconvert == NULL)
225 lconvert = localeconv();
227 mon_group = *lconvert->mon_grouping;
228 comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
229 /* frac_digits in the C locale seems to return CHAR_MAX */
230 /* best guess is 2 in this case I think */
231 points = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
232 convention = lconvert->n_sign_posn;
233 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
234 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
235 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
246 point_pos = LAST_DIGIT - points;
248 /* We're playing a little fast and loose with this. Shoot me. */
249 /* Not me, that was the other guy. Haven't fixed it yet - thomas */
250 if (!mon_group || mon_group == CHAR_MAX)
253 /* allow more than three decimal points and separate them */
256 point_pos -= (points - 1) / mon_group;
257 comma_position = point_pos % (mon_group + 1);
260 /* we work with positive amounts and add the minus sign at the end */
267 /* allow for trailing negative strings */
268 MemSet(buf, ' ', CASH_BUFSZ);
269 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
271 while (value || count > (point_pos - 2))
273 if (points && count == point_pos)
274 buf[count--] = dsymbol;
275 else if (comma && count % (mon_group + 1) == comma_position)
276 buf[count--] = comma;
278 buf[count--] = (value % 10) + '0';
282 strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
283 count -= strlen(csymbol) - 1;
285 if (buf[LAST_DIGIT] == ',')
286 buf[LAST_DIGIT] = buf[LAST_PAREN];
288 /* see if we need to signify negative amount */
291 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
292 elog(ERROR, "Memory allocation failed, can't output cash", NULL);
294 /* Position code of 0 means use parens */
296 sprintf(result, "(%s)", buf + count);
297 else if (convention == 2)
298 sprintf(result, "%s%s", buf + count, nsymbol);
300 sprintf(result, "%s%s", nsymbol, buf + count);
304 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
305 elog(ERROR, "Memory allocation failed, can't output cash", NULL);
307 strcpy(result, buf + count);
315 cash_eq(Cash *c1, Cash *c2)
317 if (!PointerIsValid(c1) || !PointerIsValid(c2))
324 cash_ne(Cash *c1, Cash *c2)
326 if (!PointerIsValid(c1) || !PointerIsValid(c2))
333 cash_lt(Cash *c1, Cash *c2)
335 if (!PointerIsValid(c1) || !PointerIsValid(c2))
342 cash_le(Cash *c1, Cash *c2)
344 if (!PointerIsValid(c1) || !PointerIsValid(c2))
351 cash_gt(Cash *c1, Cash *c2)
353 if (!PointerIsValid(c1) || !PointerIsValid(c2))
360 cash_ge(Cash *c1, Cash *c2)
362 if (!PointerIsValid(c1) || !PointerIsValid(c2))
370 * Add two cash values.
373 cash_pl(Cash *c1, Cash *c2)
377 if (!PointerIsValid(c1) || !PointerIsValid(c2))
380 if (!PointerIsValid(result = palloc(sizeof(Cash))))
381 elog(ERROR, "Memory allocation failed, can't add cash", NULL);
383 *result = (*c1 + *c2);
390 * Subtract two cash values.
393 cash_mi(Cash *c1, Cash *c2)
397 if (!PointerIsValid(c1) || !PointerIsValid(c2))
400 if (!PointerIsValid(result = palloc(sizeof(Cash))))
401 elog(ERROR, "Memory allocation failed, can't subtract cash", NULL);
403 *result = (*c1 - *c2);
410 * Multiply cash by float8.
413 cash_mul_flt8(Cash *c, float8 *f)
417 if (!PointerIsValid(f) || !PointerIsValid(c))
420 if (!PointerIsValid(result = palloc(sizeof(Cash))))
421 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
423 *result = ((*f) * (*c));
426 } /* cash_mul_flt8() */
430 * Multiply float8 by cash.
433 flt8_mul_cash(float8 *f, Cash *c)
435 return cash_mul_flt8(c, f);
436 } /* flt8_mul_cash() */
440 * Divide cash by float8.
442 * XXX Don't know if rounding or truncating is correct behavior.
443 * Round for now. - tgl 97/04/15
446 cash_div_flt8(Cash *c, float8 *f)
450 if (!PointerIsValid(f) || !PointerIsValid(c))
453 if (!PointerIsValid(result = palloc(sizeof(Cash))))
454 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
457 elog(ERROR, "cash_div: divide by 0.0 error");
459 *result = rint(*c / *f);
462 } /* cash_div_flt8() */
465 * Multiply cash by float4.
468 cash_mul_flt4(Cash *c, float4 *f)
472 if (!PointerIsValid(f) || !PointerIsValid(c))
475 if (!PointerIsValid(result = palloc(sizeof(Cash))))
476 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
478 *result = ((*f) * (*c));
481 } /* cash_mul_flt4() */
485 * Multiply float4 by float4.
488 flt4_mul_cash(float4 *f, Cash *c)
490 return cash_mul_flt4(c, f);
491 } /* flt4_mul_cash() */
495 * Divide cash by float4.
497 * XXX Don't know if rounding or truncating is correct behavior.
498 * Round for now. - tgl 97/04/15
501 cash_div_flt4(Cash *c, float4 *f)
505 if (!PointerIsValid(f) || !PointerIsValid(c))
508 if (!PointerIsValid(result = palloc(sizeof(Cash))))
509 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
512 elog(ERROR, "cash_div: divide by 0.0 error");
514 *result = rint(*c / *f);
517 } /* cash_div_flt4() */
521 * Multiply cash by int4.
524 cash_mul_int4(Cash *c, int4 i)
528 if (!PointerIsValid(c))
531 if (!PointerIsValid(result = palloc(sizeof(Cash))))
532 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
534 *result = ((i) * (*c));
537 } /* cash_mul_int4() */
541 * Multiply int4 by cash.
544 int4_mul_cash(int4 i, Cash *c)
546 return cash_mul_int4(c, i);
547 } /* int4_mul_cash() */
551 * Divide cash by 4-byte integer.
553 * XXX Don't know if rounding or truncating is correct behavior.
554 * Round for now. - tgl 97/04/15
557 cash_div_int4(Cash *c, int4 i)
561 if (!PointerIsValid(c))
564 if (!PointerIsValid(result = palloc(sizeof(Cash))))
565 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
568 elog(ERROR, "cash_idiv: divide by 0 error");
570 *result = rint(*c / i);
573 } /* cash_div_int4() */
577 * Multiply cash by int2.
580 cash_mul_int2(Cash *c, int2 s)
584 if (!PointerIsValid(c))
587 if (!PointerIsValid(result = palloc(sizeof(Cash))))
588 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
590 *result = ((s) * (*c));
593 } /* cash_mul_int2() */
597 * Multiply int2 by cash.
600 int2_mul_cash(int2 s, Cash *c)
602 return cash_mul_int2(c, s);
603 } /* int2_mul_cash() */
607 * Divide cash by int2.
609 * XXX Don't know if rounding or truncating is correct behavior.
610 * Round for now. - tgl 97/04/15
613 cash_div_int2(Cash *c, int2 s)
617 if (!PointerIsValid(c))
620 if (!PointerIsValid(result = palloc(sizeof(Cash))))
621 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
624 elog(ERROR, "cash_div: divide by 0 error");
626 *result = rint(*c / s);
629 } /* cash_div_int2() */
633 * Return larger of two cash values.
636 cashlarger(Cash *c1, Cash *c2)
640 if (!PointerIsValid(c1) || !PointerIsValid(c2))
643 if (!PointerIsValid(result = palloc(sizeof(Cash))))
644 elog(ERROR, "Memory allocation failed, can't return larger cash", NULL);
646 *result = ((*c1 > *c2) ? *c1 : *c2);
653 * Return smaller of two cash values.
656 cashsmaller(Cash *c1, Cash *c2)
660 if (!PointerIsValid(c1) || !PointerIsValid(c2))
663 if (!PointerIsValid(result = palloc(sizeof(Cash))))
664 elog(ERROR, "Memory allocation failed, can't return smaller cash", NULL);
666 *result = ((*c1 < *c2) ? *c1 : *c2);
669 } /* cashsmaller() */
673 * This converts a int4 as well but to a representation using words
674 * Obviously way North American centric - sorry
677 cash_words_out(Cash *value)
679 static char buf[128];
687 /* work with positive numbers */
691 strcpy(buf, "minus ");
697 m0 = *value % 100; /* cents */
698 m1 = (*value / 100) % 1000; /* hundreds */
699 m2 = (*value / 100000) % 1000; /* thousands */
700 m3 = *value / 100000000 % 1000; /* millions */
704 strcat(buf, num_word(m3));
705 strcat(buf, " million ");
710 strcat(buf, num_word(m2));
711 strcat(buf, " thousand ");
715 strcat(buf, num_word(m1));
720 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
721 strcat(buf, num_word(m0));
722 strcat(buf, m0 == 1 ? " cent" : " cents");
724 /* capitalize output */
725 *buf = toupper(*buf);
727 /* make a text type for output */
728 result = (text *) palloc(strlen(buf) + VARHDRSZ);
729 VARSIZE(result) = strlen(buf) + VARHDRSZ;
730 StrNCpy(VARDATA(result), buf, strlen(buf));
733 } /* cash_words_out() */
736 /*************************************************************************
738 ************************************************************************/
743 static char buf[128];
744 static const char *small[] = {
745 "zero", "one", "two", "three", "four", "five", "six", "seven",
746 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
747 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
748 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
750 const char **big = small + 18;
751 int tu = value % 100;
753 /* deal with the simple cases first */
757 /* is it an even multiple of 100? */
760 sprintf(buf, "%s hundred", small[value / 100]);
767 /* is it an even multiple of 10 other than 10? */
768 if (value % 10 == 0 && tu > 10)
769 sprintf(buf, "%s hundred %s",
770 small[value / 100], big[tu / 10]);
772 sprintf(buf, "%s hundred and %s",
773 small[value / 100], small[tu]);
775 sprintf(buf, "%s hundred %s %s",
776 small[value / 100], big[tu / 10], small[tu % 10]);
781 /* is it an even multiple of 10 other than 10? */
782 if (value % 10 == 0 && tu > 10)
783 sprintf(buf, "%s", big[tu / 10]);
785 sprintf(buf, "%s", small[tu]);
787 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);