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.36 2000/05/16 20:48:49 momjian Exp $
20 #include "miscadmin.h"
21 #include "utils/builtins.h"
22 #include "utils/cash.h"
24 static const char *num_word(Cash value);
26 /* when we go to 64 bit values we will have to modify this */
29 #define TERMINATOR (CASH_BUFSZ - 1)
30 #define LAST_PAREN (TERMINATOR - 1)
31 #define LAST_DIGIT (LAST_PAREN - 1)
34 static struct lconv *lconvert = NULL;
39 * Convert a string to a cash data type.
40 * Format is [$]###[,]###[.##]
41 * Examples: 123.45 $123.45 $123,456.78
43 * This is currently implemented as a 32-bit integer.
44 * XXX HACK It looks as though some of the symbols for
45 * monetary values returned by localeconv() can be multiple
46 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
47 * XXX UNHACK Allow the currency symbol to be multi-byte.
51 cash_in(const char *str)
69 setlocale(LC_ALL, "");
70 lconvert = localeconv();
73 lconvert = localeconv();
75 /* frac_digits in the C locale seems to return CHAR_MAX */
76 /* best guess is 2 in this case I think */
77 fpoint = ((lconvert->frac_digits != (char)CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
79 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
80 ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
81 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
82 psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
83 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
94 printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
95 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
98 /* we need to add all sorts of checking here. For now just */
99 /* strip all leading whitespace and any leading currency symbol */
102 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
103 s += strlen(csymbol);
106 printf("cashin- string is '%s'\n", s);
109 /* a leading minus or paren signifies a negative number */
110 /* again, better heuristics needed */
111 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
114 s += strlen(nsymbol);
116 printf("cashin- negative symbol; string is '%s'\n", s);
125 else if (*s == psymbol)
129 printf("cashin- string is '%s'\n", s);
134 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
135 s += strlen(csymbol);
138 printf("cashin- string is '%s'\n", s);
143 /* we look for digits as int4 as we have less */
144 /* than the required number of decimal places */
145 if (isdigit(*s) && dec < fpoint)
147 value = (value * 10) + *s - '0';
152 /* decimal point? then start counting fractions... */
154 else if (*s == dsymbol && !seen_dot)
158 /* "thousands" separator? then skip... */
160 else if (*s == ssymbol)
167 if (isdigit(*s) && *s >= '5')
170 /* adjust for less than required decimal places */
171 for (; dec < fpoint; dec++)
178 while (isspace(*s) || *s == '0' || *s == ')')
182 elog(ERROR, "Bad money external representation %s", str);
184 if (!PointerIsValid(result = palloc(sizeof(Cash))))
185 elog(ERROR, "Memory allocation failed, can't input cash '%s'", str);
187 *result = (value * sgn);
190 printf("cashin- result is %d\n", *result);
198 * Function to convert cash to a dollars and cents representation.
199 * XXX HACK This code appears to assume US conventions for
200 * positive-valued amounts. - tgl 97/04/14
203 cash_out(Cash *in_value)
205 Cash value = *in_value;
207 char buf[CASH_BUFSZ];
209 int count = LAST_DIGIT;
211 int comma_position = 0;
221 if (lconvert == NULL)
222 lconvert = localeconv();
224 mon_group = *lconvert->mon_grouping;
225 comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
226 /* frac_digits in the C locale seems to return CHAR_MAX */
227 /* best guess is 2 in this case I think */
228 points = ((lconvert->frac_digits != (char)CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
229 convention = lconvert->n_sign_posn;
230 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
231 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
232 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
243 point_pos = LAST_DIGIT - points;
245 /* We're playing a little fast and loose with this. Shoot me. */
246 /* Not me, that was the other guy. Haven't fixed it yet - thomas */
247 if (!mon_group || mon_group == (char)CHAR_MAX)
250 /* allow more than three decimal points and separate them */
253 point_pos -= (points - 1) / mon_group;
254 comma_position = point_pos % (mon_group + 1);
257 /* we work with positive amounts and add the minus sign at the end */
264 /* allow for trailing negative strings */
265 MemSet(buf, ' ', CASH_BUFSZ);
266 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
268 while (value || count > (point_pos - 2))
270 if (points && count == point_pos)
271 buf[count--] = dsymbol;
272 else if (comma && count % (mon_group + 1) == comma_position)
273 buf[count--] = comma;
275 buf[count--] = (value % 10) + '0';
279 strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
280 count -= strlen(csymbol) - 1;
282 if (buf[LAST_DIGIT] == ',')
283 buf[LAST_DIGIT] = buf[LAST_PAREN];
285 /* see if we need to signify negative amount */
288 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
289 elog(ERROR, "Memory allocation failed, can't output cash");
291 /* Position code of 0 means use parens */
293 sprintf(result, "(%s)", buf + count);
294 else if (convention == 2)
295 sprintf(result, "%s%s", buf + count, nsymbol);
297 sprintf(result, "%s%s", nsymbol, buf + count);
301 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
302 elog(ERROR, "Memory allocation failed, can't output cash");
304 strcpy(result, buf + count);
312 cash_eq(Cash *c1, Cash *c2)
314 if (!PointerIsValid(c1) || !PointerIsValid(c2))
321 cash_ne(Cash *c1, Cash *c2)
323 if (!PointerIsValid(c1) || !PointerIsValid(c2))
330 cash_lt(Cash *c1, Cash *c2)
332 if (!PointerIsValid(c1) || !PointerIsValid(c2))
339 cash_le(Cash *c1, Cash *c2)
341 if (!PointerIsValid(c1) || !PointerIsValid(c2))
348 cash_gt(Cash *c1, Cash *c2)
350 if (!PointerIsValid(c1) || !PointerIsValid(c2))
357 cash_ge(Cash *c1, Cash *c2)
359 if (!PointerIsValid(c1) || !PointerIsValid(c2))
367 * Add two cash values.
370 cash_pl(Cash *c1, Cash *c2)
374 if (!PointerIsValid(c1) || !PointerIsValid(c2))
377 if (!PointerIsValid(result = palloc(sizeof(Cash))))
378 elog(ERROR, "Memory allocation failed, can't add cash");
380 *result = (*c1 + *c2);
387 * Subtract two cash values.
390 cash_mi(Cash *c1, Cash *c2)
394 if (!PointerIsValid(c1) || !PointerIsValid(c2))
397 if (!PointerIsValid(result = palloc(sizeof(Cash))))
398 elog(ERROR, "Memory allocation failed, can't subtract cash");
400 *result = (*c1 - *c2);
407 * Multiply cash by float8.
410 cash_mul_flt8(Cash *c, float8 *f)
414 if (!PointerIsValid(f) || !PointerIsValid(c))
417 if (!PointerIsValid(result = palloc(sizeof(Cash))))
418 elog(ERROR, "Memory allocation failed, can't multiply cash");
420 *result = ((*f) * (*c));
423 } /* cash_mul_flt8() */
427 * Multiply float8 by cash.
430 flt8_mul_cash(float8 *f, Cash *c)
432 return cash_mul_flt8(c, f);
433 } /* flt8_mul_cash() */
437 * Divide cash by float8.
439 * XXX Don't know if rounding or truncating is correct behavior.
440 * Round for now. - tgl 97/04/15
443 cash_div_flt8(Cash *c, float8 *f)
447 if (!PointerIsValid(f) || !PointerIsValid(c))
450 if (!PointerIsValid(result = palloc(sizeof(Cash))))
451 elog(ERROR, "Memory allocation failed, can't divide cash");
454 elog(ERROR, "cash_div: divide by 0.0 error");
456 *result = rint(*c / *f);
459 } /* cash_div_flt8() */
462 * Multiply cash by float4.
465 cash_mul_flt4(Cash *c, float4 *f)
469 if (!PointerIsValid(f) || !PointerIsValid(c))
472 if (!PointerIsValid(result = palloc(sizeof(Cash))))
473 elog(ERROR, "Memory allocation failed, can't multiply cash");
475 *result = ((*f) * (*c));
478 } /* cash_mul_flt4() */
482 * Multiply float4 by float4.
485 flt4_mul_cash(float4 *f, Cash *c)
487 return cash_mul_flt4(c, f);
488 } /* flt4_mul_cash() */
492 * Divide cash by float4.
494 * XXX Don't know if rounding or truncating is correct behavior.
495 * Round for now. - tgl 97/04/15
498 cash_div_flt4(Cash *c, float4 *f)
502 if (!PointerIsValid(f) || !PointerIsValid(c))
505 if (!PointerIsValid(result = palloc(sizeof(Cash))))
506 elog(ERROR, "Memory allocation failed, can't divide cash");
509 elog(ERROR, "cash_div: divide by 0.0 error");
511 *result = rint(*c / *f);
514 } /* cash_div_flt4() */
518 * Multiply cash by int4.
521 cash_mul_int4(Cash *c, int4 i)
525 if (!PointerIsValid(c))
528 if (!PointerIsValid(result = palloc(sizeof(Cash))))
529 elog(ERROR, "Memory allocation failed, can't multiply cash");
531 *result = ((i) * (*c));
534 } /* cash_mul_int4() */
538 * Multiply int4 by cash.
541 int4_mul_cash(int4 i, Cash *c)
543 return cash_mul_int4(c, i);
544 } /* int4_mul_cash() */
548 * Divide cash by 4-byte integer.
550 * XXX Don't know if rounding or truncating is correct behavior.
551 * Round for now. - tgl 97/04/15
554 cash_div_int4(Cash *c, int4 i)
558 if (!PointerIsValid(c))
561 if (!PointerIsValid(result = palloc(sizeof(Cash))))
562 elog(ERROR, "Memory allocation failed, can't divide cash");
565 elog(ERROR, "cash_idiv: divide by 0 error");
567 *result = rint(*c / i);
570 } /* cash_div_int4() */
574 * Multiply cash by int2.
577 cash_mul_int2(Cash *c, int2 s)
581 if (!PointerIsValid(c))
584 if (!PointerIsValid(result = palloc(sizeof(Cash))))
585 elog(ERROR, "Memory allocation failed, can't multiply cash");
587 *result = ((s) * (*c));
590 } /* cash_mul_int2() */
594 * Multiply int2 by cash.
597 int2_mul_cash(int2 s, Cash *c)
599 return cash_mul_int2(c, s);
600 } /* int2_mul_cash() */
604 * Divide cash by int2.
606 * XXX Don't know if rounding or truncating is correct behavior.
607 * Round for now. - tgl 97/04/15
610 cash_div_int2(Cash *c, int2 s)
614 if (!PointerIsValid(c))
617 if (!PointerIsValid(result = palloc(sizeof(Cash))))
618 elog(ERROR, "Memory allocation failed, can't divide cash");
621 elog(ERROR, "cash_div: divide by 0 error");
623 *result = rint(*c / s);
626 } /* cash_div_int2() */
630 * Return larger of two cash values.
633 cashlarger(Cash *c1, Cash *c2)
637 if (!PointerIsValid(c1) || !PointerIsValid(c2))
640 if (!PointerIsValid(result = palloc(sizeof(Cash))))
641 elog(ERROR, "Memory allocation failed, can't return larger cash");
643 *result = ((*c1 > *c2) ? *c1 : *c2);
650 * Return smaller of two cash values.
653 cashsmaller(Cash *c1, Cash *c2)
657 if (!PointerIsValid(c1) || !PointerIsValid(c2))
660 if (!PointerIsValid(result = palloc(sizeof(Cash))))
661 elog(ERROR, "Memory allocation failed, can't return smaller cash");
663 *result = ((*c1 < *c2) ? *c1 : *c2);
666 } /* cashsmaller() */
670 * This converts a int4 as well but to a representation using words
671 * Obviously way North American centric - sorry
674 cash_words_out(Cash *value)
676 static char buf[128];
684 /* work with positive numbers */
688 strcpy(buf, "minus ");
694 m0 = *value % 100; /* cents */
695 m1 = (*value / 100) % 1000; /* hundreds */
696 m2 = (*value / 100000) % 1000; /* thousands */
697 m3 = *value / 100000000 % 1000; /* millions */
701 strcat(buf, num_word(m3));
702 strcat(buf, " million ");
707 strcat(buf, num_word(m2));
708 strcat(buf, " thousand ");
712 strcat(buf, num_word(m1));
717 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
718 strcat(buf, num_word(m0));
719 strcat(buf, m0 == 1 ? " cent" : " cents");
721 /* capitalize output */
722 *buf = toupper(*buf);
724 /* make a text type for output */
725 result = (text *) palloc(strlen(buf) + VARHDRSZ);
726 VARSIZE(result) = strlen(buf) + VARHDRSZ;
727 memcpy(VARDATA(result), buf, strlen(buf));
730 } /* cash_words_out() */
733 /*************************************************************************
735 ************************************************************************/
740 static char buf[128];
741 static const char *small[] = {
742 "zero", "one", "two", "three", "four", "five", "six", "seven",
743 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
744 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
745 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
747 const char **big = small + 18;
748 int tu = value % 100;
750 /* deal with the simple cases first */
754 /* is it an even multiple of 100? */
757 sprintf(buf, "%s hundred", small[value / 100]);
764 /* is it an even multiple of 10 other than 10? */
765 if (value % 10 == 0 && tu > 10)
766 sprintf(buf, "%s hundred %s",
767 small[value / 100], big[tu / 10]);
769 sprintf(buf, "%s hundred and %s",
770 small[value / 100], small[tu]);
772 sprintf(buf, "%s hundred %s %s",
773 small[value / 100], big[tu / 10], small[tu % 10]);
778 /* is it an even multiple of 10 other than 10? */
779 if (value % 10 == 0 && tu > 10)
780 sprintf(buf, "%s", big[tu / 10]);
782 sprintf(buf, "%s", small[tu]);
784 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);