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.28 1999/05/25 16:11:52 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 *lconvert = 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
49 * XXX UNHACK Allow the currency symbol to be multi-byte.
53 cash_in(const char *str)
71 setlocale(LC_ALL, "");
72 lconvert = localeconv();
75 lconvert = localeconv();
77 /* frac_digits in the C locale seems to return CHAR_MAX */
78 /* best guess is 2 in this case I think */
79 fpoint = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
81 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
82 ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
83 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
84 psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
85 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
96 printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
97 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
100 /* we need to add all sorts of checking here. For now just */
101 /* strip all leading whitespace and any leading currency symbol */
104 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
105 s += strlen(csymbol);
108 printf("cashin- string is '%s'\n", s);
111 /* a leading minus or paren signifies a negative number */
112 /* again, better heuristics needed */
113 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
116 s += strlen(nsymbol);
118 printf("cashin- negative symbol; string is '%s'\n", s);
127 else if (*s == psymbol)
131 printf("cashin- string is '%s'\n", s);
136 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
137 s += strlen(csymbol);
140 printf("cashin- string is '%s'\n", s);
145 /* we look for digits as int4 as we have less */
146 /* than the required number of decimal places */
147 if (isdigit(*s) && dec < fpoint)
149 value = (value * 10) + *s - '0';
154 /* decimal point? then start counting fractions... */
156 else if (*s == dsymbol && !seen_dot)
160 /* "thousands" separator? then skip... */
162 else if (*s == ssymbol)
169 if (isdigit(*s) && *s >= '5')
172 /* adjust for less than required decimal places */
173 for (; dec < fpoint; dec++)
180 while (isspace(*s) || *s == '0' || *s == ')')
184 elog(ERROR, "Bad money external representation %s", str);
186 if (!PointerIsValid(result = palloc(sizeof(Cash))))
187 elog(ERROR, "Memory allocation failed, can't input cash '%s'", str);
189 *result = (value * sgn);
192 printf("cashin- result is %d\n", *result);
200 * Function to convert cash to a dollars and cents representation.
201 * XXX HACK This code appears to assume US conventions for
202 * positive-valued amounts. - tgl 97/04/14
205 cash_out(Cash *in_value)
207 Cash value = *in_value;
209 char buf[CASH_BUFSZ];
211 int count = LAST_DIGIT;
213 int comma_position = 0;
223 if (lconvert == NULL)
224 lconvert = localeconv();
226 mon_group = *lconvert->mon_grouping;
227 comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
228 /* frac_digits in the C locale seems to return CHAR_MAX */
229 /* best guess is 2 in this case I think */
230 points = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
231 convention = lconvert->n_sign_posn;
232 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
233 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
234 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
245 point_pos = LAST_DIGIT - points;
247 /* We're playing a little fast and loose with this. Shoot me. */
248 /* Not me, that was the other guy. Haven't fixed it yet - thomas */
249 if (!mon_group || mon_group == CHAR_MAX)
252 /* allow more than three decimal points and separate them */
255 point_pos -= (points - 1) / mon_group;
256 comma_position = point_pos % (mon_group + 1);
259 /* we work with positive amounts and add the minus sign at the end */
266 /* allow for trailing negative strings */
267 MemSet(buf, ' ', CASH_BUFSZ);
268 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
270 while (value || count > (point_pos - 2))
272 if (points && count == point_pos)
273 buf[count--] = dsymbol;
274 else if (comma && count % (mon_group + 1) == comma_position)
275 buf[count--] = comma;
277 buf[count--] = (value % 10) + '0';
281 strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
282 count -= strlen(csymbol) - 1;
284 if (buf[LAST_DIGIT] == ',')
285 buf[LAST_DIGIT] = buf[LAST_PAREN];
287 /* see if we need to signify negative amount */
290 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
291 elog(ERROR, "Memory allocation failed, can't output cash", NULL);
293 /* Position code of 0 means use parens */
295 sprintf(result, "(%s)", buf + count);
296 else if (convention == 2)
297 sprintf(result, "%s%s", buf + count, nsymbol);
299 sprintf(result, "%s%s", nsymbol, buf + count);
303 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
304 elog(ERROR, "Memory allocation failed, can't output cash", NULL);
306 strcpy(result, buf + count);
314 cash_eq(Cash *c1, Cash *c2)
316 if (!PointerIsValid(c1) || !PointerIsValid(c2))
323 cash_ne(Cash *c1, Cash *c2)
325 if (!PointerIsValid(c1) || !PointerIsValid(c2))
332 cash_lt(Cash *c1, Cash *c2)
334 if (!PointerIsValid(c1) || !PointerIsValid(c2))
341 cash_le(Cash *c1, Cash *c2)
343 if (!PointerIsValid(c1) || !PointerIsValid(c2))
350 cash_gt(Cash *c1, Cash *c2)
352 if (!PointerIsValid(c1) || !PointerIsValid(c2))
359 cash_ge(Cash *c1, Cash *c2)
361 if (!PointerIsValid(c1) || !PointerIsValid(c2))
369 * Add two cash values.
372 cash_pl(Cash *c1, Cash *c2)
376 if (!PointerIsValid(c1) || !PointerIsValid(c2))
379 if (!PointerIsValid(result = palloc(sizeof(Cash))))
380 elog(ERROR, "Memory allocation failed, can't add cash", NULL);
382 *result = (*c1 + *c2);
389 * Subtract two cash values.
392 cash_mi(Cash *c1, Cash *c2)
396 if (!PointerIsValid(c1) || !PointerIsValid(c2))
399 if (!PointerIsValid(result = palloc(sizeof(Cash))))
400 elog(ERROR, "Memory allocation failed, can't subtract cash", NULL);
402 *result = (*c1 - *c2);
409 * Multiply cash by float8.
412 cash_mul_flt8(Cash *c, float8 *f)
416 if (!PointerIsValid(f) || !PointerIsValid(c))
419 if (!PointerIsValid(result = palloc(sizeof(Cash))))
420 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
422 *result = ((*f) * (*c));
425 } /* cash_mul_flt8() */
429 * Multiply float8 by cash.
432 flt8_mul_cash(float8 *f, Cash *c)
434 return cash_mul_flt8(c, f);
435 } /* flt8_mul_cash() */
439 * Divide cash by float8.
441 * XXX Don't know if rounding or truncating is correct behavior.
442 * Round for now. - tgl 97/04/15
445 cash_div_flt8(Cash *c, float8 *f)
449 if (!PointerIsValid(f) || !PointerIsValid(c))
452 if (!PointerIsValid(result = palloc(sizeof(Cash))))
453 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
456 elog(ERROR, "cash_div: divide by 0.0 error");
458 *result = rint(*c / *f);
461 } /* cash_div_flt8() */
464 * Multiply cash by float4.
467 cash_mul_flt4(Cash *c, float4 *f)
471 if (!PointerIsValid(f) || !PointerIsValid(c))
474 if (!PointerIsValid(result = palloc(sizeof(Cash))))
475 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
477 *result = ((*f) * (*c));
480 } /* cash_mul_flt4() */
484 * Multiply float4 by float4.
487 flt4_mul_cash(float4 *f, Cash *c)
489 return cash_mul_flt4(c, f);
490 } /* flt4_mul_cash() */
494 * Divide cash by float4.
496 * XXX Don't know if rounding or truncating is correct behavior.
497 * Round for now. - tgl 97/04/15
500 cash_div_flt4(Cash *c, float4 *f)
504 if (!PointerIsValid(f) || !PointerIsValid(c))
507 if (!PointerIsValid(result = palloc(sizeof(Cash))))
508 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
511 elog(ERROR, "cash_div: divide by 0.0 error");
513 *result = rint(*c / *f);
516 } /* cash_div_flt4() */
520 * Multiply cash by int4.
523 cash_mul_int4(Cash *c, int4 i)
527 if (!PointerIsValid(c))
530 if (!PointerIsValid(result = palloc(sizeof(Cash))))
531 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
533 *result = ((i) * (*c));
536 } /* cash_mul_int4() */
540 * Multiply int4 by cash.
543 int4_mul_cash(int4 i, Cash *c)
545 return cash_mul_int4(c, i);
546 } /* int4_mul_cash() */
550 * Divide cash by 4-byte integer.
552 * XXX Don't know if rounding or truncating is correct behavior.
553 * Round for now. - tgl 97/04/15
556 cash_div_int4(Cash *c, int4 i)
560 if (!PointerIsValid(c))
563 if (!PointerIsValid(result = palloc(sizeof(Cash))))
564 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
567 elog(ERROR, "cash_idiv: divide by 0 error");
569 *result = rint(*c / i);
572 } /* cash_div_int4() */
576 * Multiply cash by int2.
579 cash_mul_int2(Cash *c, int2 s)
583 if (!PointerIsValid(c))
586 if (!PointerIsValid(result = palloc(sizeof(Cash))))
587 elog(ERROR, "Memory allocation failed, can't multiply cash", NULL);
589 *result = ((s) * (*c));
592 } /* cash_mul_int2() */
596 * Multiply int2 by cash.
599 int2_mul_cash(int2 s, Cash *c)
601 return cash_mul_int2(c, s);
602 } /* int2_mul_cash() */
606 * Divide cash by int2.
608 * XXX Don't know if rounding or truncating is correct behavior.
609 * Round for now. - tgl 97/04/15
612 cash_div_int2(Cash *c, int2 s)
616 if (!PointerIsValid(c))
619 if (!PointerIsValid(result = palloc(sizeof(Cash))))
620 elog(ERROR, "Memory allocation failed, can't divide cash", NULL);
623 elog(ERROR, "cash_div: divide by 0 error");
625 *result = rint(*c / s);
628 } /* cash_div_int2() */
632 * Return larger of two cash values.
635 cashlarger(Cash *c1, Cash *c2)
639 if (!PointerIsValid(c1) || !PointerIsValid(c2))
642 if (!PointerIsValid(result = palloc(sizeof(Cash))))
643 elog(ERROR, "Memory allocation failed, can't return larger cash", NULL);
645 *result = ((*c1 > *c2) ? *c1 : *c2);
652 * Return smaller of two cash values.
655 cashsmaller(Cash *c1, Cash *c2)
659 if (!PointerIsValid(c1) || !PointerIsValid(c2))
662 if (!PointerIsValid(result = palloc(sizeof(Cash))))
663 elog(ERROR, "Memory allocation failed, can't return smaller cash", NULL);
665 *result = ((*c1 < *c2) ? *c1 : *c2);
668 } /* cashsmaller() */
672 * This converts a int4 as well but to a representation using words
673 * Obviously way North American centric - sorry
676 cash_words_out(Cash *value)
678 static char buf[128];
686 /* work with positive numbers */
690 strcpy(buf, "minus ");
696 m0 = *value % 100; /* cents */
697 m1 = (*value / 100) % 1000; /* hundreds */
698 m2 = (*value / 100000) % 1000; /* thousands */
699 m3 = *value / 100000000 % 1000; /* millions */
703 strcat(buf, num_word(m3));
704 strcat(buf, " million ");
709 strcat(buf, num_word(m2));
710 strcat(buf, " thousand ");
714 strcat(buf, num_word(m1));
719 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
720 strcat(buf, num_word(m0));
721 strcat(buf, m0 == 1 ? " cent" : " cents");
723 /* capitalize output */
724 *buf = toupper(*buf);
726 /* make a text type for output */
727 result = (text *) palloc(strlen(buf) + VARHDRSZ);
728 VARSIZE(result) = strlen(buf) + VARHDRSZ;
729 StrNCpy(VARDATA(result), buf, strlen(buf));
732 } /* cash_words_out() */
735 /*************************************************************************
737 ************************************************************************/
742 static char buf[128];
743 static const char *small[] = {
744 "zero", "one", "two", "three", "four", "five", "six", "seven",
745 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
746 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
747 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
749 const char **big = small + 18;
750 int tu = value % 100;
752 /* deal with the simple cases first */
756 /* is it an even multiple of 100? */
759 sprintf(buf, "%s hundred", small[value / 100]);
766 /* is it an even multiple of 10 other than 10? */
767 if (value % 10 == 0 && tu > 10)
768 sprintf(buf, "%s hundred %s",
769 small[value / 100], big[tu / 10]);
771 sprintf(buf, "%s hundred and %s",
772 small[value / 100], small[tu]);
774 sprintf(buf, "%s hundred %s %s",
775 small[value / 100], big[tu / 10], small[tu % 10]);
780 /* is it an even multiple of 10 other than 10? */
781 if (value % 10 == 0 && tu > 10)
782 sprintf(buf, "%s", big[tu / 10]);
784 sprintf(buf, "%s", small[tu]);
786 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);