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.40 2000/06/15 03:32:28 momjian Exp $
21 #include "miscadmin.h"
22 #include "utils/builtins.h"
23 #include "utils/cash.h"
25 static const char *num_word(Cash value);
27 /* when we go to 64 bit values we will have to modify this */
30 #define TERMINATOR (CASH_BUFSZ - 1)
31 #define LAST_PAREN (TERMINATOR - 1)
32 #define LAST_DIGIT (LAST_PAREN - 1)
35 static struct lconv *lconvert = NULL;
41 * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
42 * These macros and support routine hide the pass-by-refness.
44 #define PG_GETARG_CASH(n) (* ((Cash *) DatumGetPointer(fcinfo->arg[n])))
45 #define PG_RETURN_CASH(x) return CashGetDatum(x)
48 CashGetDatum(Cash value)
50 Cash *result = (Cash *) palloc(sizeof(Cash));
53 return PointerGetDatum(result);
58 * Convert a string to a cash data type.
59 * Format is [$]###[,]###[.##]
60 * Examples: 123.45 $123.45 $123,456.78
62 * This is currently implemented as a 32-bit integer.
63 * XXX HACK It looks as though some of the symbols for
64 * monetary values returned by localeconv() can be multiple
65 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
66 * XXX UNHACK Allow the currency symbol to be multi-byte.
70 cash_in(const char *str)
88 setlocale(LC_ALL, "");
89 lconvert = localeconv();
92 lconvert = localeconv();
94 /* frac_digits in the C locale seems to return CHAR_MAX */
95 /* best guess is 2 in this case I think */
96 fpoint = ((lconvert->frac_digits != (char)CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
98 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
99 ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
100 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
101 psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
102 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
113 printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
114 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
117 /* we need to add all sorts of checking here. For now just */
118 /* strip all leading whitespace and any leading currency symbol */
119 while (isspace((int) *s))
121 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
122 s += strlen(csymbol);
125 printf("cashin- string is '%s'\n", s);
128 /* a leading minus or paren signifies a negative number */
129 /* again, better heuristics needed */
130 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
133 s += strlen(nsymbol);
135 printf("cashin- negative symbol; string is '%s'\n", s);
144 else if (*s == psymbol)
148 printf("cashin- string is '%s'\n", s);
151 while (isspace((int) *s))
153 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
154 s += strlen(csymbol);
157 printf("cashin- string is '%s'\n", s);
162 /* we look for digits as int4 as we have less */
163 /* than the required number of decimal places */
164 if (isdigit((int) *s) && dec < fpoint)
166 value = (value * 10) + *s - '0';
171 /* decimal point? then start counting fractions... */
173 else if (*s == dsymbol && !seen_dot)
177 /* "thousands" separator? then skip... */
179 else if (*s == ssymbol)
186 if (isdigit((int) *s) && *s >= '5')
189 /* adjust for less than required decimal places */
190 for (; dec < fpoint; dec++)
197 while (isspace((int) *s) || *s == '0' || *s == ')')
201 elog(ERROR, "Bad money external representation %s", str);
203 if (!PointerIsValid(result = palloc(sizeof(Cash))))
204 elog(ERROR, "Memory allocation failed, can't input cash '%s'", str);
206 *result = (value * sgn);
209 printf("cashin- result is %d\n", *result);
217 * Function to convert cash to a dollars and cents representation.
218 * XXX HACK This code appears to assume US conventions for
219 * positive-valued amounts. - tgl 97/04/14
222 cash_out(Cash *in_value)
224 Cash value = *in_value;
226 char buf[CASH_BUFSZ];
228 int count = LAST_DIGIT;
230 int comma_position = 0;
240 if (lconvert == NULL)
241 lconvert = localeconv();
243 mon_group = *lconvert->mon_grouping;
244 comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
245 /* frac_digits in the C locale seems to return CHAR_MAX */
246 /* best guess is 2 in this case I think */
247 points = ((lconvert->frac_digits != (char)CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
248 convention = lconvert->n_sign_posn;
249 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
250 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
251 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
262 point_pos = LAST_DIGIT - points;
264 /* We're playing a little fast and loose with this. Shoot me. */
265 /* Not me, that was the other guy. Haven't fixed it yet - thomas */
266 if (!mon_group || mon_group == (char)CHAR_MAX)
269 /* allow more than three decimal points and separate them */
272 point_pos -= (points - 1) / mon_group;
273 comma_position = point_pos % (mon_group + 1);
276 /* we work with positive amounts and add the minus sign at the end */
283 /* allow for trailing negative strings */
284 MemSet(buf, ' ', CASH_BUFSZ);
285 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
287 while (value || count > (point_pos - 2))
289 if (points && count == point_pos)
290 buf[count--] = dsymbol;
291 else if (comma && count % (mon_group + 1) == comma_position)
292 buf[count--] = comma;
294 buf[count--] = (value % 10) + '0';
298 strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
299 count -= strlen(csymbol) - 1;
301 if (buf[LAST_DIGIT] == ',')
302 buf[LAST_DIGIT] = buf[LAST_PAREN];
304 /* see if we need to signify negative amount */
307 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
308 elog(ERROR, "Memory allocation failed, can't output cash");
310 /* Position code of 0 means use parens */
312 sprintf(result, "(%s)", buf + count);
313 else if (convention == 2)
314 sprintf(result, "%s%s", buf + count, nsymbol);
316 sprintf(result, "%s%s", nsymbol, buf + count);
320 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
321 elog(ERROR, "Memory allocation failed, can't output cash");
323 strcpy(result, buf + count);
331 cash_eq(Cash *c1, Cash *c2)
333 if (!PointerIsValid(c1) || !PointerIsValid(c2))
340 cash_ne(Cash *c1, Cash *c2)
342 if (!PointerIsValid(c1) || !PointerIsValid(c2))
349 cash_lt(Cash *c1, Cash *c2)
351 if (!PointerIsValid(c1) || !PointerIsValid(c2))
358 cash_le(Cash *c1, Cash *c2)
360 if (!PointerIsValid(c1) || !PointerIsValid(c2))
367 cash_gt(Cash *c1, Cash *c2)
369 if (!PointerIsValid(c1) || !PointerIsValid(c2))
376 cash_ge(Cash *c1, Cash *c2)
378 if (!PointerIsValid(c1) || !PointerIsValid(c2))
386 * Add two cash values.
389 cash_pl(Cash *c1, Cash *c2)
393 if (!PointerIsValid(c1) || !PointerIsValid(c2))
396 if (!PointerIsValid(result = palloc(sizeof(Cash))))
397 elog(ERROR, "Memory allocation failed, can't add cash");
399 *result = (*c1 + *c2);
406 * Subtract two cash values.
409 cash_mi(Cash *c1, Cash *c2)
413 if (!PointerIsValid(c1) || !PointerIsValid(c2))
416 if (!PointerIsValid(result = palloc(sizeof(Cash))))
417 elog(ERROR, "Memory allocation failed, can't subtract cash");
419 *result = (*c1 - *c2);
426 * Multiply cash by float8.
429 cash_mul_flt8(Cash *c, float8 *f)
433 if (!PointerIsValid(f) || !PointerIsValid(c))
436 if (!PointerIsValid(result = palloc(sizeof(Cash))))
437 elog(ERROR, "Memory allocation failed, can't multiply cash");
439 *result = ((*f) * (*c));
442 } /* cash_mul_flt8() */
446 * Multiply float8 by cash.
449 flt8_mul_cash(float8 *f, Cash *c)
451 return cash_mul_flt8(c, f);
452 } /* flt8_mul_cash() */
456 * Divide cash by float8.
458 * XXX Don't know if rounding or truncating is correct behavior.
459 * Round for now. - tgl 97/04/15
462 cash_div_flt8(Cash *c, float8 *f)
466 if (!PointerIsValid(f) || !PointerIsValid(c))
469 if (!PointerIsValid(result = palloc(sizeof(Cash))))
470 elog(ERROR, "Memory allocation failed, can't divide cash");
473 elog(ERROR, "cash_div: divide by 0.0 error");
475 *result = rint(*c / *f);
478 } /* cash_div_flt8() */
481 * Multiply cash by float4.
484 cash_mul_flt4(Cash *c, float4 *f)
488 if (!PointerIsValid(f) || !PointerIsValid(c))
491 if (!PointerIsValid(result = palloc(sizeof(Cash))))
492 elog(ERROR, "Memory allocation failed, can't multiply cash");
494 *result = ((*f) * (*c));
497 } /* cash_mul_flt4() */
501 * Multiply float4 by float4.
504 flt4_mul_cash(float4 *f, Cash *c)
506 return cash_mul_flt4(c, f);
507 } /* flt4_mul_cash() */
511 * Divide cash by float4.
513 * XXX Don't know if rounding or truncating is correct behavior.
514 * Round for now. - tgl 97/04/15
517 cash_div_flt4(Cash *c, float4 *f)
521 if (!PointerIsValid(f) || !PointerIsValid(c))
524 if (!PointerIsValid(result = palloc(sizeof(Cash))))
525 elog(ERROR, "Memory allocation failed, can't divide cash");
528 elog(ERROR, "cash_div: divide by 0.0 error");
530 *result = rint(*c / *f);
533 } /* cash_div_flt4() */
537 * Multiply cash by int4.
540 cash_mul_int4(PG_FUNCTION_ARGS)
542 Cash c = PG_GETARG_CASH(0);
543 int32 i = PG_GETARG_INT32(1);
547 PG_RETURN_CASH(result);
552 * Multiply int4 by cash.
555 int4_mul_cash(PG_FUNCTION_ARGS)
557 int32 i = PG_GETARG_INT32(0);
558 Cash c = PG_GETARG_CASH(1);
562 PG_RETURN_CASH(result);
567 * Divide cash by 4-byte integer.
569 * XXX Don't know if rounding or truncating is correct behavior.
570 * Round for now. - tgl 97/04/15
573 cash_div_int4(PG_FUNCTION_ARGS)
575 Cash c = PG_GETARG_CASH(0);
576 int32 i = PG_GETARG_INT32(1);
580 elog(ERROR, "cash_div_int4: divide by 0 error");
582 result = rint(c / i);
584 PG_RETURN_CASH(result);
589 * Multiply cash by int2.
592 cash_mul_int2(PG_FUNCTION_ARGS)
594 Cash c = PG_GETARG_CASH(0);
595 int16 s = PG_GETARG_INT16(1);
599 PG_RETURN_CASH(result);
603 * Multiply int2 by cash.
606 int2_mul_cash(PG_FUNCTION_ARGS)
608 int16 s = PG_GETARG_INT16(0);
609 Cash c = PG_GETARG_CASH(1);
613 PG_RETURN_CASH(result);
617 * Divide cash by int2.
619 * XXX Don't know if rounding or truncating is correct behavior.
620 * Round for now. - tgl 97/04/15
623 cash_div_int2(PG_FUNCTION_ARGS)
625 Cash c = PG_GETARG_CASH(0);
626 int16 s = PG_GETARG_INT16(1);
630 elog(ERROR, "cash_div: divide by 0 error");
632 result = rint(c / s);
633 PG_RETURN_CASH(result);
637 * Return larger of two cash values.
640 cashlarger(Cash *c1, Cash *c2)
644 if (!PointerIsValid(c1) || !PointerIsValid(c2))
647 if (!PointerIsValid(result = palloc(sizeof(Cash))))
648 elog(ERROR, "Memory allocation failed, can't return larger cash");
650 *result = ((*c1 > *c2) ? *c1 : *c2);
657 * Return smaller of two cash values.
660 cashsmaller(Cash *c1, Cash *c2)
664 if (!PointerIsValid(c1) || !PointerIsValid(c2))
667 if (!PointerIsValid(result = palloc(sizeof(Cash))))
668 elog(ERROR, "Memory allocation failed, can't return smaller cash");
670 *result = ((*c1 < *c2) ? *c1 : *c2);
673 } /* cashsmaller() */
677 * This converts a int4 as well but to a representation using words
678 * Obviously way North American centric - sorry
681 cash_words_out(Cash *value)
683 static char buf[128];
691 /* work with positive numbers */
695 strcpy(buf, "minus ");
701 m0 = *value % 100; /* cents */
702 m1 = (*value / 100) % 1000; /* hundreds */
703 m2 = (*value / 100000) % 1000; /* thousands */
704 m3 = *value / 100000000 % 1000; /* millions */
708 strcat(buf, num_word(m3));
709 strcat(buf, " million ");
714 strcat(buf, num_word(m2));
715 strcat(buf, " thousand ");
719 strcat(buf, num_word(m1));
724 strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
725 strcat(buf, num_word(m0));
726 strcat(buf, m0 == 1 ? " cent" : " cents");
728 /* capitalize output */
729 *buf = toupper(*buf);
731 /* make a text type for output */
732 result = (text *) palloc(strlen(buf) + VARHDRSZ);
733 VARSIZE(result) = strlen(buf) + VARHDRSZ;
734 memcpy(VARDATA(result), buf, strlen(buf));
737 } /* cash_words_out() */
740 /*************************************************************************
742 ************************************************************************/
747 static char buf[128];
748 static const char *small[] = {
749 "zero", "one", "two", "three", "four", "five", "six", "seven",
750 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
751 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
752 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
754 const char **big = small + 18;
755 int tu = value % 100;
757 /* deal with the simple cases first */
761 /* is it an even multiple of 100? */
764 sprintf(buf, "%s hundred", small[value / 100]);
771 /* is it an even multiple of 10 other than 10? */
772 if (value % 10 == 0 && tu > 10)
773 sprintf(buf, "%s hundred %s",
774 small[value / 100], big[tu / 10]);
776 sprintf(buf, "%s hundred and %s",
777 small[value / 100], small[tu]);
779 sprintf(buf, "%s hundred %s %s",
780 small[value / 100], big[tu / 10], small[tu % 10]);
785 /* is it an even multiple of 10 other than 10? */
786 if (value % 10 == 0 && tu > 10)
787 sprintf(buf, "%s", big[tu / 10]);
789 sprintf(buf, "%s", small[tu]);
791 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);