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 longs
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.4 1997/04/15 17:39:44 scrappy Exp $
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/cash.h"
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 *lconv = NULL;
38 * Convert a string to a cash data type.
39 * Format is [$]###[,]###[.##]
40 * Examples: 123.45 $123.45 $123,456.78
42 * This is currently implemented as a 32-bit integer.
43 * XXX HACK It looks as though some of the symbols for
44 * monetary values returned by localeconv() can be multiple
45 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
48 cash_in(const char *str)
58 char dsymbol, ssymbol, psymbol, nsymbol, csymbol;
61 if (lconv == NULL) *lconv = localeconv();
63 /* frac_digits in the C locale seems to return CHAR_MAX */
64 /* best guess is 2 in this case I think */
65 fpoint = ((lconv->frac_digits != CHAR_MAX)? lconv->frac_digits: 2); /* int_frac_digits? */
67 dsymbol = *lconv->mon_decimal_point;
68 ssymbol = *lconv->mon_thousands_sep;
69 csymbol = *lconv->currency_symbol;
70 psymbol = *lconv->positive_sign;
71 nsymbol = *lconv->negative_sign;
81 /* we need to add all sorts of checking here. For now just */
82 /* strip all leading whitespace and any leading dollar sign */
83 while (isspace(*s) || *s == csymbol) s++;
85 /* a leading minus or paren signifies a negative number */
86 /* again, better heuristics needed */
87 if (*s == nsymbol || *s == '(') {
91 } else if (*s == psymbol) {
95 while (isspace(*s) || *s == csymbol) s++;
98 /* we look for digits as long as we have less */
99 /* than the required number of decimal places */
100 if (isdigit(*s) && dec < fpoint) {
101 value = (value * 10) + *s - '0';
106 /* decimal point? then start counting fractions... */
107 } else if (*s == dsymbol && !seen_dot) {
110 /* "thousands" separator? then skip... */
111 } else if (*s == ssymbol) {
115 if (isdigit(*s) && *s >= '5')
118 /* adjust for less than required decimal places */
119 for (; dec < fpoint; dec++)
126 while (isspace(*s) || *s == ')') s++;
129 elog(WARN,"Bad money external representation %s",str);
131 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
132 elog(WARN,"Memory allocation failed, can't input cash '%s'",str);
134 *result = (value * sgn);
141 * Function to convert cash to a dollars and cents representation.
142 * XXX HACK This code appears to assume US conventions for
143 * positive-valued amounts. - tgl 97/04/14
146 cash_out(Cash *value)
149 char buf[CASH_BUFSZ];
151 int count = LAST_DIGIT;
153 int comma_position = 0;
154 char mon_group, comma, points;
155 char csymbol, dsymbol, *nsymbol;
159 if (lconv == NULL) *lconv = localeconv();
161 mon_group = *lconv->mon_grouping;
162 comma = *lconv->mon_thousands_sep;
163 csymbol = *lconv->currency_symbol;
164 dsymbol = *lconv->mon_decimal_point;
165 nsymbol = lconv->negative_sign;
166 /* frac_digits in the C locale seems to return CHAR_MAX */
167 /* best guess is 2 in this case I think */
168 points = ((lconv->frac_digits != CHAR_MAX)? lconv->frac_digits: 2); /* int_frac_digits? */
169 convention = lconv->n_sign_posn;
180 point_pos = LAST_DIGIT - points;
182 /* We're playing a little fast and loose with this. Shoot me. */
183 if (!mon_group || mon_group == CHAR_MAX)
186 /* allow more than three decimal points and separate them */
188 point_pos -= (points - 1)/mon_group;
189 comma_position = point_pos % (mon_group + 1);
192 /* we work with positive amounts and add the minus sign at the end */
198 /* allow for trailing negative strings */
199 memset(buf, ' ', CASH_BUFSZ);
200 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
202 while (*value || count > (point_pos - 2)) {
203 if (points && count == point_pos)
204 buf[count--] = dsymbol;
205 else if (comma && count % (mon_group + 1) == comma_position)
206 buf[count--] = comma;
208 buf[count--] = (*value % 10) + '0';
212 buf[count] = csymbol;
214 if (buf[LAST_DIGIT] == ',')
215 buf[LAST_DIGIT] = buf[LAST_PAREN];
217 /* see if we need to signify negative amount */
219 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
220 elog(WARN,"Memory allocation failed, can't output cash",NULL);
222 /* Position code of 0 means use parens */
224 sprintf(result, "(%s)", buf + count);
225 else if (convention == 2)
226 sprintf(result, "%s%s", buf + count, nsymbol);
228 sprintf(result, "%s%s", nsymbol, buf + count);
230 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
231 elog(WARN,"Memory allocation failed, can't output cash",NULL);
233 strcpy(result, buf + count);
241 cash_eq(Cash *c1, Cash *c2)
243 if (!PointerIsValid(c1) || !PointerIsValid(c2))
250 cash_ne(Cash *c1, Cash *c2)
252 if (!PointerIsValid(c1) || !PointerIsValid(c2))
259 cash_lt(Cash *c1, Cash *c2)
261 if (!PointerIsValid(c1) || !PointerIsValid(c2))
268 cash_le(Cash *c1, Cash *c2)
270 if (!PointerIsValid(c1) || !PointerIsValid(c2))
277 cash_gt(Cash *c1, Cash *c2)
279 if (!PointerIsValid(c1) || !PointerIsValid(c2))
286 cash_ge(Cash *c1, Cash *c2)
288 if (!PointerIsValid(c1) || !PointerIsValid(c2))
296 * Add two cash values.
299 cash_pl(Cash *c1, Cash *c2)
303 if (!PointerIsValid(c1) || !PointerIsValid(c2))
306 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
307 elog(WARN,"Memory allocation failed, can't add cash",NULL);
309 *result = (*c1 + *c2);
316 * Subtract two cash values.
319 cash_mi(Cash *c1, Cash *c2)
323 if (!PointerIsValid(c1) || !PointerIsValid(c2))
326 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
327 elog(WARN,"Memory allocation failed, can't subtract cash",NULL);
329 *result = (*c1 - *c2);
336 * Multiply cash by floating point number.
339 cash_mul(Cash *c, float8 *f)
343 if (!PointerIsValid(f) || !PointerIsValid(c))
346 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
347 elog(WARN,"Memory allocation failed, can't multiply cash",NULL);
349 *result = ((*f) * (*c));
356 * Divide cash by floating point number.
358 * XXX Don't know if rounding or truncating is correct behavior.
359 * Round for now. - tgl 97/04/15
362 cash_div(Cash *c, float8 *f)
366 if (!PointerIsValid(f) || !PointerIsValid(c))
369 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
370 elog(WARN,"Memory allocation failed, can't divide cash",NULL);
372 *result = rint(*c / *f);
379 * Return larger of two cash values.
382 cashlarger(Cash *c1, Cash *c2)
386 if (!PointerIsValid(c1) || !PointerIsValid(c2))
389 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
390 elog(WARN,"Memory allocation failed, can't return larger cash",NULL);
392 *result = ((*c1 > *c2)? *c1: *c2);
399 * Return smaller of two cash values.
402 cashsmaller(Cash *c1, Cash *c2)
406 if (!PointerIsValid(c1) || !PointerIsValid(c2))
409 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
410 elog(WARN,"Memory allocation failed, can't return smaller cash",NULL);
412 *result = ((*c1 < *c2)? *c1: *c2);
415 } /* cashsmaller() */
419 * This converts a long as well but to a representation using words
420 * Obviously way North American centric - sorry
423 cash_words_out(Cash *value)
425 static char buf[128];
432 /* work with positive numbers */
435 strcpy(buf, "minus ");
441 m0 = *value % 100; /* cents */
442 m1 = (*value/100) % 1000; /* hundreds */
443 m2 = (*value/100000) % 1000; /* thousands */
444 m3 = *value/100000000 % 1000; /* millions */
447 strcat(buf, num_word(m3));
448 strcat(buf, " million ");
452 strcat(buf, num_word(m2));
453 strcat(buf, " thousand ");
457 strcat(buf, num_word(m1));
462 strcat(buf, (int)(*value/100) == 1 ? " dollar and " : " dollars and ");
463 strcat(buf, num_word(m0));
464 strcat(buf, m0 == 1 ? " cent" : " cents");
465 *buf = toupper(*buf);
467 } /* cash_words_out() */
470 /*************************************************************************
472 ************************************************************************/
477 static char buf[128];
478 static const char *small[] = {
479 "zero", "one", "two", "three", "four", "five", "six", "seven",
480 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
481 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
482 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
484 const char **big = small + 18;
485 int tu = value % 100;
487 /* deal with the simple cases first */
489 return(small[value]);
491 /* is it an even multiple of 100? */
493 sprintf(buf, "%s hundred", small[value/100]);
499 /* is it an even multiple of 10 other than 10? */
500 if (value % 10 == 0 && tu > 10)
501 sprintf(buf, "%s hundred %s",
502 small[value/100], big[tu/10]);
504 sprintf(buf, "%s hundred and %s",
505 small[value/100], small[tu]);
507 sprintf(buf, "%s hundred %s %s",
508 small[value/100], big[tu/10], small[tu % 10]);
511 /* is it an even multiple of 10 other than 10? */
512 if (value % 10 == 0 && tu > 10)
513 sprintf(buf, "%s", big[tu/10]);
515 sprintf(buf, "%s", small[tu]);
517 sprintf(buf, "%s %s", big[tu/10], small[tu % 10]);