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.14 1997/09/13 12:05:32 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 *in_value)
170 Cash value = *in_value;
172 char buf[CASH_BUFSZ];
174 int count = LAST_DIGIT;
176 int comma_position = 0;
187 lconv = localeconv();
189 mon_group = *lconv->mon_grouping;
190 comma = *lconv->mon_thousands_sep;
191 csymbol = *lconv->currency_symbol;
192 dsymbol = *lconv->mon_decimal_point;
193 nsymbol = lconv->negative_sign;
194 /* frac_digits in the C locale seems to return CHAR_MAX */
195 /* best guess is 2 in this case I think */
196 points = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2); /* int_frac_digits? */
197 convention = lconv->n_sign_posn;
208 point_pos = LAST_DIGIT - points;
210 /* We're playing a little fast and loose with this. Shoot me. */
211 if (!mon_group || mon_group == CHAR_MAX)
214 /* allow more than three decimal points and separate them */
217 point_pos -= (points - 1) / mon_group;
218 comma_position = point_pos % (mon_group + 1);
221 /* we work with positive amounts and add the minus sign at the end */
228 /* allow for trailing negative strings */
229 memset(buf, ' ', CASH_BUFSZ);
230 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
232 while (value || count > (point_pos - 2))
234 if (points && count == point_pos)
235 buf[count--] = dsymbol;
236 else if (comma && count % (mon_group + 1) == comma_position)
237 buf[count--] = comma;
239 buf[count--] = (value % 10) + '0';
243 buf[count] = csymbol;
245 if (buf[LAST_DIGIT] == ',')
246 buf[LAST_DIGIT] = buf[LAST_PAREN];
248 /* see if we need to signify negative amount */
251 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
252 elog(WARN, "Memory allocation failed, can't output cash", NULL);
254 /* Position code of 0 means use parens */
256 sprintf(result, "(%s)", buf + count);
257 else if (convention == 2)
258 sprintf(result, "%s%s", buf + count, nsymbol);
260 sprintf(result, "%s%s", nsymbol, buf + count);
264 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
265 elog(WARN, "Memory allocation failed, can't output cash", NULL);
267 strcpy(result, buf + count);
275 cash_eq(Cash *c1, Cash *c2)
277 if (!PointerIsValid(c1) || !PointerIsValid(c2))
284 cash_ne(Cash *c1, Cash *c2)
286 if (!PointerIsValid(c1) || !PointerIsValid(c2))
293 cash_lt(Cash *c1, Cash *c2)
295 if (!PointerIsValid(c1) || !PointerIsValid(c2))
302 cash_le(Cash *c1, Cash *c2)
304 if (!PointerIsValid(c1) || !PointerIsValid(c2))
311 cash_gt(Cash *c1, Cash *c2)
313 if (!PointerIsValid(c1) || !PointerIsValid(c2))
320 cash_ge(Cash *c1, Cash *c2)
322 if (!PointerIsValid(c1) || !PointerIsValid(c2))
330 * Add two cash values.
333 cash_pl(Cash *c1, Cash *c2)
337 if (!PointerIsValid(c1) || !PointerIsValid(c2))
340 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
341 elog(WARN, "Memory allocation failed, can't add cash", NULL);
343 *result = (*c1 + *c2);
350 * Subtract two cash values.
353 cash_mi(Cash *c1, Cash *c2)
357 if (!PointerIsValid(c1) || !PointerIsValid(c2))
360 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
361 elog(WARN, "Memory allocation failed, can't subtract cash", NULL);
363 *result = (*c1 - *c2);
370 * Multiply cash by floating point number.
373 cash_mul(Cash *c, float8 *f)
377 if (!PointerIsValid(f) || !PointerIsValid(c))
380 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
381 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
383 *result = ((*f) * (*c));
390 * Divide cash by floating point number.
392 * XXX Don't know if rounding or truncating is correct behavior.
393 * Round for now. - tgl 97/04/15
396 cash_div(Cash *c, float8 *f)
400 if (!PointerIsValid(f) || !PointerIsValid(c))
403 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
404 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
407 elog(WARN, "cash_div: divide by 0.0 error");
409 *result = rint(*c / *f);
416 * Return larger of two cash values.
419 cashlarger(Cash *c1, Cash *c2)
423 if (!PointerIsValid(c1) || !PointerIsValid(c2))
426 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
427 elog(WARN, "Memory allocation failed, can't return larger cash", NULL);
429 *result = ((*c1 > *c2) ? *c1 : *c2);
436 * Return smaller of two cash values.
439 cashsmaller(Cash *c1, Cash *c2)
443 if (!PointerIsValid(c1) || !PointerIsValid(c2))
446 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
447 elog(WARN, "Memory allocation failed, can't return smaller cash", NULL);
449 *result = ((*c1 < *c2) ? *c1 : *c2);
452 } /* cashsmaller() */
456 * This converts a int4 as well but to a representation using words
457 * Obviously way North American centric - sorry
460 cash_words_out(Cash *value)
462 static char buf[128];
469 /* work with positive numbers */
473 strcpy(buf, "minus ");
481 m0 = *value % 100; /* cents */
482 m1 = (*value / 100) % 1000; /* hundreds */
483 m2 = (*value / 100000) % 1000; /* thousands */
484 m3 = *value / 100000000 % 1000; /* millions */
488 strcat(buf, num_word(m3));
489 strcat(buf, " million ");
494 strcat(buf, num_word(m2));
495 strcat(buf, " thousand ");
499 strcat(buf, num_word(m1));
504 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
505 strcat(buf, num_word(m0));
506 strcat(buf, m0 == 1 ? " cent" : " cents");
507 *buf = toupper(*buf);
509 } /* cash_words_out() */
512 /*************************************************************************
514 ************************************************************************/
519 static char buf[128];
520 static const char *small[] = {
521 "zero", "one", "two", "three", "four", "five", "six", "seven",
522 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
523 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
524 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
526 const char **big = small + 18;
527 int tu = value % 100;
529 /* deal with the simple cases first */
531 return (small[value]);
533 /* is it an even multiple of 100? */
536 sprintf(buf, "%s hundred", small[value / 100]);
543 /* is it an even multiple of 10 other than 10? */
544 if (value % 10 == 0 && tu > 10)
545 sprintf(buf, "%s hundred %s",
546 small[value / 100], big[tu / 10]);
548 sprintf(buf, "%s hundred and %s",
549 small[value / 100], small[tu]);
551 sprintf(buf, "%s hundred %s %s",
552 small[value / 100], big[tu / 10], small[tu % 10]);
557 /* is it an even multiple of 10 other than 10? */
558 if (value % 10 == 0 && tu > 10)
559 sprintf(buf, "%s", big[tu / 10]);
561 sprintf(buf, "%s", small[tu]);
563 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);