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.17 1997/10/03 13:10:06 thomas 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 *lconvert = NULL;
40 * Convert a string to a cash data type.
41 * Format is [$]###[,]###[.##]
42 * Examples: 123.45 $123.45 $123,456.78
44 * This is currently implemented as a 32-bit integer.
45 * XXX HACK It looks as though some of the symbols for
46 * monetary values returned by localeconv() can be multiple
47 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
50 cash_in(const char *str)
68 lconvert = localeconv();
70 /* frac_digits in the C locale seems to return CHAR_MAX */
71 /* best guess is 2 in this case I think */
72 fpoint = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
74 dsymbol = *lconvert->mon_decimal_point;
75 ssymbol = *lconvert->mon_thousands_sep;
76 csymbol = *lconvert->currency_symbol;
77 psymbol = *lconvert->positive_sign;
78 nsymbol = *lconvert->negative_sign;
88 /* we need to add all sorts of checking here. For now just */
89 /* strip all leading whitespace and any leading dollar sign */
90 while (isspace(*s) || *s == csymbol)
93 /* a leading minus or paren signifies a negative number */
94 /* again, better heuristics needed */
95 if (*s == nsymbol || *s == '(')
101 else if (*s == psymbol)
106 while (isspace(*s) || *s == csymbol)
111 /* we look for digits as int4 as we have less */
112 /* than the required number of decimal places */
113 if (isdigit(*s) && dec < fpoint)
115 value = (value * 10) + *s - '0';
120 /* decimal point? then start counting fractions... */
122 else if (*s == dsymbol && !seen_dot)
126 /* "thousands" separator? then skip... */
128 else if (*s == ssymbol)
135 if (isdigit(*s) && *s >= '5')
138 /* adjust for less than required decimal places */
139 for (; dec < fpoint; dec++)
146 while (isspace(*s) || *s == '0' || *s == ')')
150 elog(WARN, "Bad money external representation %s", str);
152 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
153 elog(WARN, "Memory allocation failed, can't input cash '%s'", str);
155 *result = (value * sgn);
162 * Function to convert cash to a dollars and cents representation.
163 * XXX HACK This code appears to assume US conventions for
164 * positive-valued amounts. - tgl 97/04/14
167 cash_out(Cash *in_value)
169 Cash value = *in_value;
171 char buf[CASH_BUFSZ];
173 int count = LAST_DIGIT;
175 int comma_position = 0;
185 if (lconvert == NULL)
186 lconvert = localeconv();
188 mon_group = *lconvert->mon_grouping;
189 comma = *lconvert->mon_thousands_sep;
190 csymbol = *lconvert->currency_symbol;
191 dsymbol = *lconvert->mon_decimal_point;
192 nsymbol = lconvert->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 = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
196 convention = lconvert->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 float8.
372 cash_mul_flt8(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));
385 } /* cash_mul_flt8() */
389 * Multiply float8 by cash.
392 flt8_mul_cash(float8 *f, Cash *c)
394 return (cash_mul_flt8(c, f));
395 } /* flt8_mul_cash() */
399 * Divide cash by float8.
401 * XXX Don't know if rounding or truncating is correct behavior.
402 * Round for now. - tgl 97/04/15
405 cash_div_flt8(Cash *c, float8 *f)
409 if (!PointerIsValid(f) || !PointerIsValid(c))
412 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
413 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
416 elog(WARN, "cash_div: divide by 0.0 error");
418 *result = rint(*c / *f);
421 } /* cash_div_flt8() */
424 * Multiply cash by float4.
427 cash_mul_flt4(Cash *c, float4 *f)
431 if (!PointerIsValid(f) || !PointerIsValid(c))
434 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
435 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
437 *result = ((*f) * (*c));
440 } /* cash_mul_flt4() */
444 * Multiply float4 by float4.
447 flt4_mul_cash(float4 *f, Cash *c)
449 return (cash_mul_flt4(c, f));
450 } /* flt4_mul_cash() */
454 * Divide cash by float4.
456 * XXX Don't know if rounding or truncating is correct behavior.
457 * Round for now. - tgl 97/04/15
460 cash_div_flt4(Cash *c, float4 *f)
464 if (!PointerIsValid(f) || !PointerIsValid(c))
467 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
468 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
471 elog(WARN, "cash_div: divide by 0.0 error");
473 *result = rint(*c / *f);
476 } /* cash_div_flt4() */
480 * Multiply cash by int4.
483 cash_mul_int4(Cash *c, int4 i)
487 if (!PointerIsValid(c))
490 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
491 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
493 *result = ((i) * (*c));
496 } /* cash_mul_int4() */
500 * Multiply int4 by cash.
503 int4_mul_cash(int4 i, Cash *c)
505 return (cash_mul_int4(c, i));
506 } /* int4_mul_cash() */
510 * Divide cash by 4-byte integer.
512 * XXX Don't know if rounding or truncating is correct behavior.
513 * Round for now. - tgl 97/04/15
516 cash_div_int4(Cash *c, int4 i)
520 if (!PointerIsValid(c))
523 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
524 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
527 elog(WARN, "cash_idiv: divide by 0 error");
529 *result = rint(*c / i);
532 } /* cash_div_int4() */
536 * Multiply cash by int2.
539 cash_mul_int2(Cash *c, int2 s)
543 if (!PointerIsValid(c))
546 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
547 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
549 *result = ((s) * (*c));
552 } /* cash_mul_int2() */
556 * Multiply int2 by cash.
559 int2_mul_cash(int2 s, Cash *c)
561 return (cash_mul_int2(c, s));
562 } /* int2_mul_cash() */
566 * Divide cash by int2.
568 * XXX Don't know if rounding or truncating is correct behavior.
569 * Round for now. - tgl 97/04/15
572 cash_div_int2(Cash *c, int2 s)
576 if (!PointerIsValid(c))
579 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
580 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
583 elog(WARN, "cash_div: divide by 0 error");
585 *result = rint(*c / s);
588 } /* cash_div_int2() */
592 * Return larger of two cash values.
595 cashlarger(Cash *c1, Cash *c2)
599 if (!PointerIsValid(c1) || !PointerIsValid(c2))
602 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
603 elog(WARN, "Memory allocation failed, can't return larger cash", NULL);
605 *result = ((*c1 > *c2) ? *c1 : *c2);
612 * Return smaller of two cash values.
615 cashsmaller(Cash *c1, Cash *c2)
619 if (!PointerIsValid(c1) || !PointerIsValid(c2))
622 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
623 elog(WARN, "Memory allocation failed, can't return smaller cash", NULL);
625 *result = ((*c1 < *c2) ? *c1 : *c2);
628 } /* cashsmaller() */
632 * This converts a int4 as well but to a representation using words
633 * Obviously way North American centric - sorry
636 cash_words_out(Cash *value)
638 static char buf[128];
645 /* work with positive numbers */
649 strcpy(buf, "minus ");
657 m0 = *value % 100; /* cents */
658 m1 = (*value / 100) % 1000; /* hundreds */
659 m2 = (*value / 100000) % 1000; /* thousands */
660 m3 = *value / 100000000 % 1000; /* millions */
664 strcat(buf, num_word(m3));
665 strcat(buf, " million ");
670 strcat(buf, num_word(m2));
671 strcat(buf, " thousand ");
675 strcat(buf, num_word(m1));
680 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
681 strcat(buf, num_word(m0));
682 strcat(buf, m0 == 1 ? " cent" : " cents");
683 *buf = toupper(*buf);
685 } /* cash_words_out() */
688 /*************************************************************************
690 ************************************************************************/
695 static char buf[128];
696 static const char *small[] = {
697 "zero", "one", "two", "three", "four", "five", "six", "seven",
698 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
699 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
700 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
702 const char **big = small + 18;
703 int tu = value % 100;
705 /* deal with the simple cases first */
707 return (small[value]);
709 /* is it an even multiple of 100? */
712 sprintf(buf, "%s hundred", small[value / 100]);
719 /* is it an even multiple of 10 other than 10? */
720 if (value % 10 == 0 && tu > 10)
721 sprintf(buf, "%s hundred %s",
722 small[value / 100], big[tu / 10]);
724 sprintf(buf, "%s hundred and %s",
725 small[value / 100], small[tu]);
727 sprintf(buf, "%s hundred %s %s",
728 small[value / 100], big[tu / 10], small[tu % 10]);
733 /* is it an even multiple of 10 other than 10? */
734 if (value % 10 == 0 && tu > 10)
735 sprintf(buf, "%s", big[tu / 10]);
737 sprintf(buf, "%s", small[tu]);
739 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);