Properly access a buffer's LSN using existing access macros instead of abusing
[PostgreSQL.git] / src / backend / utils / adt / cash.c
blobb5896e16f6f790481821ea1202fb5a479dded061
1 /*
2 * cash.c
3 * Written by D'Arcy J.M. Cain
4 * darcy@druid.net
5 * http://www.druid.net/darcy/
7 * Functions to allow input and output of money normally but store
8 * and handle it as 64 bit ints
10 * A slightly modified version of this file and a discussion of the
11 * workings can be found in the book "Software Solutions in C" by
12 * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
13 * this version handles 64 bit numbers and so can hold values up to
14 * $92,233,720,368,547,758.07.
16 * $PostgreSQL$
19 #include "postgres.h"
21 #include <limits.h>
22 #include <ctype.h>
23 #include <math.h>
24 #include <locale.h>
26 #include "libpq/pqformat.h"
27 #include "utils/builtins.h"
28 #include "utils/cash.h"
29 #include "utils/pg_locale.h"
31 #define CASH_BUFSZ 36
33 #define TERMINATOR (CASH_BUFSZ - 1)
34 #define LAST_PAREN (TERMINATOR - 1)
35 #define LAST_DIGIT (LAST_PAREN - 1)
38 /*************************************************************************
39 * Private routines
40 ************************************************************************/
42 static const char *
43 num_word(Cash value)
45 static char buf[128];
46 static const char *small[] = {
47 "zero", "one", "two", "three", "four", "five", "six", "seven",
48 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
49 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
50 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
52 const char **big = small + 18;
53 int tu = value % 100;
55 /* deal with the simple cases first */
56 if (value <= 20)
57 return small[value];
59 /* is it an even multiple of 100? */
60 if (!tu)
62 sprintf(buf, "%s hundred", small[value / 100]);
63 return buf;
66 /* more than 99? */
67 if (value > 99)
69 /* is it an even multiple of 10 other than 10? */
70 if (value % 10 == 0 && tu > 10)
71 sprintf(buf, "%s hundred %s",
72 small[value / 100], big[tu / 10]);
73 else if (tu < 20)
74 sprintf(buf, "%s hundred and %s",
75 small[value / 100], small[tu]);
76 else
77 sprintf(buf, "%s hundred %s %s",
78 small[value / 100], big[tu / 10], small[tu % 10]);
80 else
82 /* is it an even multiple of 10 other than 10? */
83 if (value % 10 == 0 && tu > 10)
84 sprintf(buf, "%s", big[tu / 10]);
85 else if (tu < 20)
86 sprintf(buf, "%s", small[tu]);
87 else
88 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
91 return buf;
92 } /* num_word() */
95 /* cash_in()
96 * Convert a string to a cash data type.
97 * Format is [$]###[,]###[.##]
98 * Examples: 123.45 $123.45 $123,456.78
101 Datum
102 cash_in(PG_FUNCTION_ARGS)
104 char *str = PG_GETARG_CSTRING(0);
105 Cash result;
106 Cash value = 0;
107 Cash dec = 0;
108 Cash sgn = 1;
109 int seen_dot = 0;
110 const char *s = str;
111 int fpoint;
112 char dsymbol,
113 ssymbol,
114 psymbol;
115 const char *nsymbol,
116 *csymbol;
118 struct lconv *lconvert = PGLC_localeconv();
121 * frac_digits will be CHAR_MAX in some locales, notably C. However, just
122 * testing for == CHAR_MAX is risky, because of compilers like gcc that
123 * "helpfully" let you alter the platform-standard definition of whether
124 * char is signed or not. If we are so unfortunate as to get compiled
125 * with a nonstandard -fsigned-char or -funsigned-char switch, then our
126 * idea of CHAR_MAX will not agree with libc's. The safest course is not
127 * to test for CHAR_MAX at all, but to impose a range check for plausible
128 * frac_digits values.
130 fpoint = lconvert->frac_digits;
131 if (fpoint < 0 || fpoint > 10)
132 fpoint = 2; /* best guess in this case, I think */
134 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
135 if (*lconvert->mon_thousands_sep != '\0')
136 ssymbol = *lconvert->mon_thousands_sep;
137 else
138 /* ssymbol should not equal dsymbol */
139 ssymbol = (dsymbol != ',') ? ',' : '.';
140 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
141 psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
142 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
144 #ifdef CASHDEBUG
145 printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
146 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
147 #endif
149 /* we need to add all sorts of checking here. For now just */
150 /* strip all leading whitespace and any leading currency symbol */
151 while (isspace((unsigned char) *s))
152 s++;
153 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
154 s += strlen(csymbol);
156 #ifdef CASHDEBUG
157 printf("cashin- string is '%s'\n", s);
158 #endif
160 /* a leading minus or paren signifies a negative number */
161 /* again, better heuristics needed */
162 /* XXX - doesn't properly check for balanced parens - djmc */
163 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
165 sgn = -1;
166 s += strlen(nsymbol);
167 #ifdef CASHDEBUG
168 printf("cashin- negative symbol; string is '%s'\n", s);
169 #endif
171 else if (*s == '(')
173 sgn = -1;
174 s++;
176 else if (*s == psymbol)
177 s++;
179 #ifdef CASHDEBUG
180 printf("cashin- string is '%s'\n", s);
181 #endif
183 while (isspace((unsigned char) *s))
184 s++;
185 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
186 s += strlen(csymbol);
188 #ifdef CASHDEBUG
189 printf("cashin- string is '%s'\n", s);
190 #endif
192 for (;; s++)
194 /* we look for digits as int8 as we have less */
195 /* than the required number of decimal places */
196 if (isdigit((unsigned char) *s) && dec < fpoint)
198 value = (value * 10) + *s - '0';
200 if (seen_dot)
201 dec++;
204 /* decimal point? then start counting fractions... */
205 else if (*s == dsymbol && !seen_dot)
207 seen_dot = 1;
210 /* not "thousands" separator? */
211 else if (*s != ssymbol)
213 /* round off */
214 if (isdigit((unsigned char) *s) && *s >= '5')
215 value++;
217 /* adjust for less than required decimal places */
218 for (; dec < fpoint; dec++)
219 value *= 10;
221 break;
225 /* should only be trailing digits followed by whitespace or right paren */
226 while (isdigit((unsigned char) *s))
227 s++;
228 while (isspace((unsigned char) *s) || *s == ')')
229 s++;
231 if (*s != '\0')
232 ereport(ERROR,
233 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
234 errmsg("invalid input syntax for type money: \"%s\"", str)));
236 result = value * sgn;
238 #ifdef CASHDEBUG
239 printf("cashin- result is %d\n", result);
240 #endif
242 PG_RETURN_CASH(result);
246 /* cash_out()
247 * Function to convert cash to a dollars and cents representation.
248 * XXX HACK This code appears to assume US conventions for
249 * positive-valued amounts. - tgl 97/04/14
251 Datum
252 cash_out(PG_FUNCTION_ARGS)
254 Cash value = PG_GETARG_CASH(0);
255 char *result;
256 char buf[CASH_BUFSZ];
257 int minus = 0;
258 int count = LAST_DIGIT;
259 int point_pos;
260 int ssymbol_position = 0;
261 int points,
262 mon_group;
263 char ssymbol;
264 const char *csymbol,
265 *nsymbol;
266 char dsymbol;
267 char convention;
269 struct lconv *lconvert = PGLC_localeconv();
271 /* see comments about frac_digits in cash_in() */
272 points = lconvert->frac_digits;
273 if (points < 0 || points > 10)
274 points = 2; /* best guess in this case, I think */
277 * As with frac_digits, must apply a range check to mon_grouping to avoid
278 * being fooled by variant CHAR_MAX values.
280 mon_group = *lconvert->mon_grouping;
281 if (mon_group <= 0 || mon_group > 6)
282 mon_group = 3;
284 convention = lconvert->n_sign_posn;
285 dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
286 if (*lconvert->mon_thousands_sep != '\0')
287 ssymbol = *lconvert->mon_thousands_sep;
288 else
289 /* ssymbol should not equal dsymbol */
290 ssymbol = (dsymbol != ',') ? ',' : '.';
291 csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
292 nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
294 point_pos = LAST_DIGIT - points;
296 point_pos -= (points - 1) / mon_group;
297 ssymbol_position = point_pos % (mon_group + 1);
299 /* we work with positive amounts and add the minus sign at the end */
300 if (value < 0)
302 minus = 1;
303 value = -value;
306 /* allow for trailing negative strings */
307 MemSet(buf, ' ', CASH_BUFSZ);
308 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
310 while (value || count > (point_pos - 2))
312 if (points && count == point_pos)
313 buf[count--] = dsymbol;
314 else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
315 buf[count--] = ssymbol;
317 buf[count--] = ((uint64) value % 10) + '0';
318 value = ((uint64) value) / 10;
321 strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
322 count -= strlen(csymbol) - 1;
325 * If points == 0 and the number of digits % mon_group == 0,
326 * the code above adds a trailing ssymbol on the far right,
327 * so remove it.
329 if (buf[LAST_DIGIT] == ssymbol)
330 buf[LAST_DIGIT] = '\0';
332 /* see if we need to signify negative amount */
333 if (minus)
335 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
337 /* Position code of 0 means use parens */
338 if (convention == 0)
339 sprintf(result, "(%s)", buf + count);
340 else if (convention == 2)
341 sprintf(result, "%s%s", buf + count, nsymbol);
342 else
343 sprintf(result, "%s%s", nsymbol, buf + count);
345 else
347 result = palloc(CASH_BUFSZ + 2 - count);
348 strcpy(result, buf + count);
351 PG_RETURN_CSTRING(result);
355 * cash_recv - converts external binary format to cash
357 Datum
358 cash_recv(PG_FUNCTION_ARGS)
360 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
362 PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
366 * cash_send - converts cash to binary format
368 Datum
369 cash_send(PG_FUNCTION_ARGS)
371 Cash arg1 = PG_GETARG_CASH(0);
372 StringInfoData buf;
374 pq_begintypsend(&buf);
375 pq_sendint64(&buf, arg1);
376 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
380 * Comparison functions
383 Datum
384 cash_eq(PG_FUNCTION_ARGS)
386 Cash c1 = PG_GETARG_CASH(0);
387 Cash c2 = PG_GETARG_CASH(1);
389 PG_RETURN_BOOL(c1 == c2);
392 Datum
393 cash_ne(PG_FUNCTION_ARGS)
395 Cash c1 = PG_GETARG_CASH(0);
396 Cash c2 = PG_GETARG_CASH(1);
398 PG_RETURN_BOOL(c1 != c2);
401 Datum
402 cash_lt(PG_FUNCTION_ARGS)
404 Cash c1 = PG_GETARG_CASH(0);
405 Cash c2 = PG_GETARG_CASH(1);
407 PG_RETURN_BOOL(c1 < c2);
410 Datum
411 cash_le(PG_FUNCTION_ARGS)
413 Cash c1 = PG_GETARG_CASH(0);
414 Cash c2 = PG_GETARG_CASH(1);
416 PG_RETURN_BOOL(c1 <= c2);
419 Datum
420 cash_gt(PG_FUNCTION_ARGS)
422 Cash c1 = PG_GETARG_CASH(0);
423 Cash c2 = PG_GETARG_CASH(1);
425 PG_RETURN_BOOL(c1 > c2);
428 Datum
429 cash_ge(PG_FUNCTION_ARGS)
431 Cash c1 = PG_GETARG_CASH(0);
432 Cash c2 = PG_GETARG_CASH(1);
434 PG_RETURN_BOOL(c1 >= c2);
437 Datum
438 cash_cmp(PG_FUNCTION_ARGS)
440 Cash c1 = PG_GETARG_CASH(0);
441 Cash c2 = PG_GETARG_CASH(1);
443 if (c1 > c2)
444 PG_RETURN_INT32(1);
445 else if (c1 == c2)
446 PG_RETURN_INT32(0);
447 else
448 PG_RETURN_INT32(-1);
452 /* cash_pl()
453 * Add two cash values.
455 Datum
456 cash_pl(PG_FUNCTION_ARGS)
458 Cash c1 = PG_GETARG_CASH(0);
459 Cash c2 = PG_GETARG_CASH(1);
460 Cash result;
462 result = c1 + c2;
464 PG_RETURN_CASH(result);
468 /* cash_mi()
469 * Subtract two cash values.
471 Datum
472 cash_mi(PG_FUNCTION_ARGS)
474 Cash c1 = PG_GETARG_CASH(0);
475 Cash c2 = PG_GETARG_CASH(1);
476 Cash result;
478 result = c1 - c2;
480 PG_RETURN_CASH(result);
484 /* cash_mul_flt8()
485 * Multiply cash by float8.
487 Datum
488 cash_mul_flt8(PG_FUNCTION_ARGS)
490 Cash c = PG_GETARG_CASH(0);
491 float8 f = PG_GETARG_FLOAT8(1);
492 Cash result;
494 result = c * f;
495 PG_RETURN_CASH(result);
499 /* flt8_mul_cash()
500 * Multiply float8 by cash.
502 Datum
503 flt8_mul_cash(PG_FUNCTION_ARGS)
505 float8 f = PG_GETARG_FLOAT8(0);
506 Cash c = PG_GETARG_CASH(1);
507 Cash result;
509 result = f * c;
510 PG_RETURN_CASH(result);
514 /* cash_div_flt8()
515 * Divide cash by float8.
517 Datum
518 cash_div_flt8(PG_FUNCTION_ARGS)
520 Cash c = PG_GETARG_CASH(0);
521 float8 f = PG_GETARG_FLOAT8(1);
522 Cash result;
524 if (f == 0.0)
525 ereport(ERROR,
526 (errcode(ERRCODE_DIVISION_BY_ZERO),
527 errmsg("division by zero")));
529 result = rint(c / f);
530 PG_RETURN_CASH(result);
534 /* cash_mul_flt4()
535 * Multiply cash by float4.
537 Datum
538 cash_mul_flt4(PG_FUNCTION_ARGS)
540 Cash c = PG_GETARG_CASH(0);
541 float4 f = PG_GETARG_FLOAT4(1);
542 Cash result;
544 result = c * f;
545 PG_RETURN_CASH(result);
549 /* flt4_mul_cash()
550 * Multiply float4 by cash.
552 Datum
553 flt4_mul_cash(PG_FUNCTION_ARGS)
555 float4 f = PG_GETARG_FLOAT4(0);
556 Cash c = PG_GETARG_CASH(1);
557 Cash result;
559 result = f * c;
560 PG_RETURN_CASH(result);
564 /* cash_div_flt4()
565 * Divide cash by float4.
568 Datum
569 cash_div_flt4(PG_FUNCTION_ARGS)
571 Cash c = PG_GETARG_CASH(0);
572 float4 f = PG_GETARG_FLOAT4(1);
573 Cash result;
575 if (f == 0.0)
576 ereport(ERROR,
577 (errcode(ERRCODE_DIVISION_BY_ZERO),
578 errmsg("division by zero")));
580 result = rint(c / f);
581 PG_RETURN_CASH(result);
585 /* cash_mul_int8()
586 * Multiply cash by int8.
588 Datum
589 cash_mul_int8(PG_FUNCTION_ARGS)
591 Cash c = PG_GETARG_CASH(0);
592 int64 i = PG_GETARG_INT64(1);
593 Cash result;
595 result = c * i;
596 PG_RETURN_CASH(result);
600 /* int8_mul_cash()
601 * Multiply int8 by cash.
603 Datum
604 int8_mul_cash(PG_FUNCTION_ARGS)
606 int64 i = PG_GETARG_INT64(0);
607 Cash c = PG_GETARG_CASH(1);
608 Cash result;
610 result = i * c;
611 PG_RETURN_CASH(result);
614 /* cash_div_int8()
615 * Divide cash by 8-byte integer.
617 Datum
618 cash_div_int8(PG_FUNCTION_ARGS)
620 Cash c = PG_GETARG_CASH(0);
621 int64 i = PG_GETARG_INT64(1);
622 Cash result;
624 if (i == 0)
625 ereport(ERROR,
626 (errcode(ERRCODE_DIVISION_BY_ZERO),
627 errmsg("division by zero")));
629 result = rint(c / i);
631 PG_RETURN_CASH(result);
635 /* cash_mul_int4()
636 * Multiply cash by int4.
638 Datum
639 cash_mul_int4(PG_FUNCTION_ARGS)
641 Cash c = PG_GETARG_CASH(0);
642 int32 i = PG_GETARG_INT32(1);
643 Cash result;
645 result = c * i;
646 PG_RETURN_CASH(result);
650 /* int4_mul_cash()
651 * Multiply int4 by cash.
653 Datum
654 int4_mul_cash(PG_FUNCTION_ARGS)
656 int32 i = PG_GETARG_INT32(0);
657 Cash c = PG_GETARG_CASH(1);
658 Cash result;
660 result = i * c;
661 PG_RETURN_CASH(result);
665 /* cash_div_int4()
666 * Divide cash by 4-byte integer.
669 Datum
670 cash_div_int4(PG_FUNCTION_ARGS)
672 Cash c = PG_GETARG_CASH(0);
673 int32 i = PG_GETARG_INT32(1);
674 Cash result;
676 if (i == 0)
677 ereport(ERROR,
678 (errcode(ERRCODE_DIVISION_BY_ZERO),
679 errmsg("division by zero")));
681 result = rint(c / i);
683 PG_RETURN_CASH(result);
687 /* cash_mul_int2()
688 * Multiply cash by int2.
690 Datum
691 cash_mul_int2(PG_FUNCTION_ARGS)
693 Cash c = PG_GETARG_CASH(0);
694 int16 s = PG_GETARG_INT16(1);
695 Cash result;
697 result = c * s;
698 PG_RETURN_CASH(result);
701 /* int2_mul_cash()
702 * Multiply int2 by cash.
704 Datum
705 int2_mul_cash(PG_FUNCTION_ARGS)
707 int16 s = PG_GETARG_INT16(0);
708 Cash c = PG_GETARG_CASH(1);
709 Cash result;
711 result = s * c;
712 PG_RETURN_CASH(result);
715 /* cash_div_int2()
716 * Divide cash by int2.
719 Datum
720 cash_div_int2(PG_FUNCTION_ARGS)
722 Cash c = PG_GETARG_CASH(0);
723 int16 s = PG_GETARG_INT16(1);
724 Cash result;
726 if (s == 0)
727 ereport(ERROR,
728 (errcode(ERRCODE_DIVISION_BY_ZERO),
729 errmsg("division by zero")));
731 result = rint(c / s);
732 PG_RETURN_CASH(result);
735 /* cashlarger()
736 * Return larger of two cash values.
738 Datum
739 cashlarger(PG_FUNCTION_ARGS)
741 Cash c1 = PG_GETARG_CASH(0);
742 Cash c2 = PG_GETARG_CASH(1);
743 Cash result;
745 result = (c1 > c2) ? c1 : c2;
747 PG_RETURN_CASH(result);
750 /* cashsmaller()
751 * Return smaller of two cash values.
753 Datum
754 cashsmaller(PG_FUNCTION_ARGS)
756 Cash c1 = PG_GETARG_CASH(0);
757 Cash c2 = PG_GETARG_CASH(1);
758 Cash result;
760 result = (c1 < c2) ? c1 : c2;
762 PG_RETURN_CASH(result);
765 /* cash_words()
766 * This converts a int4 as well but to a representation using words
767 * Obviously way North American centric - sorry
769 Datum
770 cash_words(PG_FUNCTION_ARGS)
772 Cash value = PG_GETARG_CASH(0);
773 uint64 val;
774 char buf[256];
775 char *p = buf;
776 Cash m0;
777 Cash m1;
778 Cash m2;
779 Cash m3;
780 Cash m4;
781 Cash m5;
782 Cash m6;
784 /* work with positive numbers */
785 if (value < 0)
787 value = -value;
788 strcpy(buf, "minus ");
789 p += 6;
791 else
792 buf[0] = '\0';
794 /* Now treat as unsigned, to avoid trouble at INT_MIN */
795 val = (uint64) value;
797 m0 = val % INT64CONST(100); /* cents */
798 m1 = (val / INT64CONST(100)) % 1000; /* hundreds */
799 m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
800 m3 = (val / INT64CONST(100000000)) % 1000; /* millions */
801 m4 = (val / INT64CONST(100000000000)) % 1000; /* billions */
802 m5 = (val / INT64CONST(100000000000000)) % 1000; /* trillions */
803 m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
805 if (m6)
807 strcat(buf, num_word(m6));
808 strcat(buf, " quadrillion ");
811 if (m5)
813 strcat(buf, num_word(m5));
814 strcat(buf, " trillion ");
817 if (m4)
819 strcat(buf, num_word(m4));
820 strcat(buf, " billion ");
823 if (m3)
825 strcat(buf, num_word(m3));
826 strcat(buf, " million ");
829 if (m2)
831 strcat(buf, num_word(m2));
832 strcat(buf, " thousand ");
835 if (m1)
836 strcat(buf, num_word(m1));
838 if (!*p)
839 strcat(buf, "zero");
841 strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
842 strcat(buf, num_word(m0));
843 strcat(buf, m0 == 1 ? " cent" : " cents");
845 /* capitalize output */
846 buf[0] = pg_toupper((unsigned char) buf[0]);
848 /* return as text datum */
849 PG_RETURN_TEXT_P(cstring_to_text(buf));