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.12 1997/09/08 21:48:13 momjian Exp $
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/cash.h"
26 static const char *num_word(Cash value);
28 /* when we go to 64 bit values we will have to modify this */
31 #define TERMINATOR (CASH_BUFSZ - 1)
32 #define LAST_PAREN (TERMINATOR - 1)
33 #define LAST_DIGIT (LAST_PAREN - 1)
36 static struct lconv *lconv = NULL;
41 * Convert a string to a cash data type.
42 * Format is [$]###[,]###[.##]
43 * Examples: 123.45 $123.45 $123,456.78
45 * This is currently implemented as a 32-bit integer.
46 * XXX HACK It looks as though some of the symbols for
47 * monetary values returned by localeconv() can be multiple
48 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
51 cash_in(const char *str)
71 /* frac_digits in the C locale seems to return CHAR_MAX */
72 /* best guess is 2 in this case I think */
73 fpoint = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2); /* int_frac_digits? */
75 dsymbol = *lconv->mon_decimal_point;
76 ssymbol = *lconv->mon_thousands_sep;
77 csymbol = *lconv->currency_symbol;
78 psymbol = *lconv->positive_sign;
79 nsymbol = *lconv->negative_sign;
89 /* we need to add all sorts of checking here. For now just */
90 /* strip all leading whitespace and any leading dollar sign */
91 while (isspace(*s) || *s == csymbol)
94 /* a leading minus or paren signifies a negative number */
95 /* again, better heuristics needed */
96 if (*s == nsymbol || *s == '(')
102 else if (*s == psymbol)
107 while (isspace(*s) || *s == csymbol)
112 /* we look for digits as int4 as we have less */
113 /* than the required number of decimal places */
114 if (isdigit(*s) && dec < fpoint)
116 value = (value * 10) + *s - '0';
121 /* decimal point? then start counting fractions... */
123 else if (*s == dsymbol && !seen_dot)
127 /* "thousands" separator? then skip... */
129 else if (*s == ssymbol)
136 if (isdigit(*s) && *s >= '5')
139 /* adjust for less than required decimal places */
140 for (; dec < fpoint; dec++)
147 while (isspace(*s) || *s == '0' || *s == ')')
151 elog(WARN, "Bad money external representation %s", str);
153 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
154 elog(WARN, "Memory allocation failed, can't input cash '%s'", str);
156 *result = (value * sgn);
163 * Function to convert cash to a dollars and cents representation.
164 * XXX HACK This code appears to assume US conventions for
165 * positive-valued amounts. - tgl 97/04/14
168 cash_out(Cash *value)
171 char buf[CASH_BUFSZ];
173 int count = LAST_DIGIT;
175 int comma_position = 0;
186 lconv = localeconv();
188 mon_group = *lconv->mon_grouping;
189 comma = *lconv->mon_thousands_sep;
190 csymbol = *lconv->currency_symbol;
191 dsymbol = *lconv->mon_decimal_point;
192 nsymbol = lconv->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 = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2); /* int_frac_digits? */
196 convention = lconv->n_sign_posn;
207 point_pos = LAST_DIGIT - points;
209 /* We're playing a little fast and loose with this. Shoot me. */
210 if (!mon_group || mon_group == CHAR_MAX)
213 /* allow more than three decimal points and separate them */
216 point_pos -= (points - 1) / mon_group;
217 comma_position = point_pos % (mon_group + 1);
220 /* we work with positive amounts and add the minus sign at the end */
227 /* allow for trailing negative strings */
228 memset(buf, ' ', CASH_BUFSZ);
229 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
231 while (*value || count > (point_pos - 2))
233 if (points && count == point_pos)
234 buf[count--] = dsymbol;
235 else if (comma && count % (mon_group + 1) == comma_position)
236 buf[count--] = comma;
238 buf[count--] = (*value % 10) + '0';
242 buf[count] = csymbol;
244 if (buf[LAST_DIGIT] == ',')
245 buf[LAST_DIGIT] = buf[LAST_PAREN];
247 /* see if we need to signify negative amount */
250 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
251 elog(WARN, "Memory allocation failed, can't output cash", NULL);
253 /* Position code of 0 means use parens */
255 sprintf(result, "(%s)", buf + count);
256 else if (convention == 2)
257 sprintf(result, "%s%s", buf + count, nsymbol);
259 sprintf(result, "%s%s", nsymbol, buf + count);
263 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
264 elog(WARN, "Memory allocation failed, can't output cash", NULL);
266 strcpy(result, buf + count);
274 cash_eq(Cash *c1, Cash *c2)
276 if (!PointerIsValid(c1) || !PointerIsValid(c2))
283 cash_ne(Cash *c1, Cash *c2)
285 if (!PointerIsValid(c1) || !PointerIsValid(c2))
292 cash_lt(Cash *c1, Cash *c2)
294 if (!PointerIsValid(c1) || !PointerIsValid(c2))
301 cash_le(Cash *c1, Cash *c2)
303 if (!PointerIsValid(c1) || !PointerIsValid(c2))
310 cash_gt(Cash *c1, Cash *c2)
312 if (!PointerIsValid(c1) || !PointerIsValid(c2))
319 cash_ge(Cash *c1, Cash *c2)
321 if (!PointerIsValid(c1) || !PointerIsValid(c2))
329 * Add two cash values.
332 cash_pl(Cash *c1, Cash *c2)
336 if (!PointerIsValid(c1) || !PointerIsValid(c2))
339 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
340 elog(WARN, "Memory allocation failed, can't add cash", NULL);
342 *result = (*c1 + *c2);
349 * Subtract two cash values.
352 cash_mi(Cash *c1, Cash *c2)
356 if (!PointerIsValid(c1) || !PointerIsValid(c2))
359 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
360 elog(WARN, "Memory allocation failed, can't subtract cash", NULL);
362 *result = (*c1 - *c2);
369 * Multiply cash by floating point number.
372 cash_mul(Cash *c, float8 *f)
376 if (!PointerIsValid(f) || !PointerIsValid(c))
379 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
380 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
382 *result = ((*f) * (*c));
389 * Divide cash by floating point number.
391 * XXX Don't know if rounding or truncating is correct behavior.
392 * Round for now. - tgl 97/04/15
395 cash_div(Cash *c, float8 *f)
399 if (!PointerIsValid(f) || !PointerIsValid(c))
402 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
403 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
406 elog(WARN, "cash_div: divide by 0.0 error");
408 *result = rint(*c / *f);
415 * Return larger of two cash values.
418 cashlarger(Cash *c1, Cash *c2)
422 if (!PointerIsValid(c1) || !PointerIsValid(c2))
425 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
426 elog(WARN, "Memory allocation failed, can't return larger cash", NULL);
428 *result = ((*c1 > *c2) ? *c1 : *c2);
435 * Return smaller of two cash values.
438 cashsmaller(Cash *c1, Cash *c2)
442 if (!PointerIsValid(c1) || !PointerIsValid(c2))
445 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
446 elog(WARN, "Memory allocation failed, can't return smaller cash", NULL);
448 *result = ((*c1 < *c2) ? *c1 : *c2);
451 } /* cashsmaller() */
455 * This converts a int4 as well but to a representation using words
456 * Obviously way North American centric - sorry
459 cash_words_out(Cash *value)
461 static char buf[128];
468 /* work with positive numbers */
472 strcpy(buf, "minus ");
480 m0 = *value % 100; /* cents */
481 m1 = (*value / 100) % 1000; /* hundreds */
482 m2 = (*value / 100000) % 1000; /* thousands */
483 m3 = *value / 100000000 % 1000; /* millions */
487 strcat(buf, num_word(m3));
488 strcat(buf, " million ");
493 strcat(buf, num_word(m2));
494 strcat(buf, " thousand ");
498 strcat(buf, num_word(m1));
503 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
504 strcat(buf, num_word(m0));
505 strcat(buf, m0 == 1 ? " cent" : " cents");
506 *buf = toupper(*buf);
508 } /* cash_words_out() */
511 /*************************************************************************
513 ************************************************************************/
518 static char buf[128];
519 static const char *small[] = {
520 "zero", "one", "two", "three", "four", "five", "six", "seven",
521 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
522 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
523 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
525 const char **big = small + 18;
526 int tu = value % 100;
528 /* deal with the simple cases first */
530 return (small[value]);
532 /* is it an even multiple of 100? */
535 sprintf(buf, "%s hundred", small[value / 100]);
542 /* is it an even multiple of 10 other than 10? */
543 if (value % 10 == 0 && tu > 10)
544 sprintf(buf, "%s hundred %s",
545 small[value / 100], big[tu / 10]);
547 sprintf(buf, "%s hundred and %s",
548 small[value / 100], small[tu]);
550 sprintf(buf, "%s hundred %s %s",
551 small[value / 100], big[tu / 10], small[tu % 10]);
556 /* is it an even multiple of 10 other than 10? */
557 if (value % 10 == 0 && tu > 10)
558 sprintf(buf, "%s", big[tu / 10]);
560 sprintf(buf, "%s", small[tu]);
562 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);