1 /*
2 * cash.c
3 * Written by D'Arcy J.M. Cain
4 * darcy@druid.net
5 * http://www.druid.net/darcy/
6 *
7 * Functions to allow input and output of money normally but store
8 * and handle it as 64 bit ints
9 *
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.
15 *
16 * src/backend/utils/adt/cash.c
17 */
18
19 #include "postgres.h"
20
21 #include <limits.h>
22 #include <ctype.h>
23 #include <math.h>
24
25 #include "libpq/pqformat.h"
26 #include "utils/builtins.h"
27 #include "utils/cash.h"
28 #include "utils/int8.h"
29 #include "utils/numeric.h"
30 #include "utils/pg_locale.h"
31
32
33 /*************************************************************************
34 * Private routines
35 ************************************************************************/
36
37 static const char *
num_word(Cash value)38 num_word(Cash value)
39 {
40 static char buf[128];
41 static const char *small[] = {
42 "zero", "one", "two", "three", "four", "five", "six", "seven",
43 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
44 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
45 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
46 };
47 const char **big = small + 18;
48 int tu = value % 100;
49
50 /* deal with the simple cases first */
51 if (value <= 20)
52 return small[value];
53
54 /* is it an even multiple of 100? */
55 if (!tu)
56 {
57 sprintf(buf, "%s hundred", small[value / 100]);
58 return buf;
59 }
60
61 /* more than 99? */
62 if (value > 99)
63 {
64 /* is it an even multiple of 10 other than 10? */
65 if (value % 10 == 0 && tu > 10)
66 sprintf(buf, "%s hundred %s",
67 small[value / 100], big[tu / 10]);
68 else if (tu < 20)
69 sprintf(buf, "%s hundred and %s",
70 small[value / 100], small[tu]);
71 else
72 sprintf(buf, "%s hundred %s %s",
73 small[value / 100], big[tu / 10], small[tu % 10]);
74 }
75 else
76 {
77 /* is it an even multiple of 10 other than 10? */
78 if (value % 10 == 0 && tu > 10)
79 sprintf(buf, "%s", big[tu / 10]);
80 else if (tu < 20)
81 sprintf(buf, "%s", small[tu]);
82 else
83 sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
84 }
85
86 return buf;
87 } /* num_word() */
88
89 /* cash_in()
90 * Convert a string to a cash data type.
91 * Format is [$]###[,]###[.##]
92 * Examples: 123.45 $123.45 $123,456.78
93 *
94 */
95 Datum
cash_in(PG_FUNCTION_ARGS)96 cash_in(PG_FUNCTION_ARGS)
97 {
98 char *str = PG_GETARG_CSTRING(0);
99 Cash result;
100 Cash value = 0;
101 Cash dec = 0;
102 Cash sgn = 1;
103 bool seen_dot = false;
104 const char *s = str;
105 int fpoint;
106 char dsymbol;
107 const char *ssymbol,
108 *psymbol,
109 *nsymbol,
110 *csymbol;
111 struct lconv *lconvert = PGLC_localeconv();
112
113 /*
114 * frac_digits will be CHAR_MAX in some locales, notably C. However, just
115 * testing for == CHAR_MAX is risky, because of compilers like gcc that
116 * "helpfully" let you alter the platform-standard definition of whether
117 * char is signed or not. If we are so unfortunate as to get compiled
118 * with a nonstandard -fsigned-char or -funsigned-char switch, then our
119 * idea of CHAR_MAX will not agree with libc's. The safest course is not
120 * to test for CHAR_MAX at all, but to impose a range check for plausible
121 * frac_digits values.
122 */
123 fpoint = lconvert->frac_digits;
124 if (fpoint < 0 || fpoint > 10)
125 fpoint = 2; /* best guess in this case, I think */
126
127 /* we restrict dsymbol to be a single byte, but not the other symbols */
128 if (*lconvert->mon_decimal_point != '\0' &&
129 lconvert->mon_decimal_point[1] == '\0')
130 dsymbol = *lconvert->mon_decimal_point;
131 else
132 dsymbol = '.';
133 if (*lconvert->mon_thousands_sep != '\0')
134 ssymbol = lconvert->mon_thousands_sep;
135 else /* ssymbol should not equal dsymbol */
136 ssymbol = (dsymbol != ',') ? "," : ".";
137 csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
138 psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
139 nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
140
141 #ifdef CASHDEBUG
142 printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
143 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
144 #endif
145
146 /* we need to add all sorts of checking here. For now just */
147 /* strip all leading whitespace and any leading currency symbol */
148 while (isspace((unsigned char) *s))
149 s++;
150 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
151 s += strlen(csymbol);
152 while (isspace((unsigned char) *s))
153 s++;
154
155 #ifdef CASHDEBUG
156 printf("cashin- string is '%s'\n", s);
157 #endif
158
159 /* a leading minus or paren signifies a negative number */
160 /* again, better heuristics needed */
161 /* XXX - doesn't properly check for balanced parens - djmc */
162 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
163 {
164 sgn = -1;
165 s += strlen(nsymbol);
166 }
167 else if (*s == '(')
168 {
169 sgn = -1;
170 s++;
171 }
172 else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
173 s += strlen(psymbol);
174
175 #ifdef CASHDEBUG
176 printf("cashin- string is '%s'\n", s);
177 #endif
178
179 /* allow whitespace and currency symbol after the sign, too */
180 while (isspace((unsigned char) *s))
181 s++;
182 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
183 s += strlen(csymbol);
184 while (isspace((unsigned char) *s))
185 s++;
186
187 #ifdef CASHDEBUG
188 printf("cashin- string is '%s'\n", s);
189 #endif
190
191 /*
192 * We accumulate the absolute amount in "value" and then apply the sign at
193 * the end. (The sign can appear before or after the digits, so it would
194 * be more complicated to do otherwise.) Because of the larger range of
195 * negative signed integers, we build "value" in the negative and then
196 * flip the sign at the end, catching most-negative-number overflow if
197 * necessary.
198 */
199
200 for (; *s; s++)
201 {
202 /* we look for digits as long as we have found less */
203 /* than the required number of decimal places */
204 if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
205 {
206 Cash newvalue = (value * 10) - (*s - '0');
207
208 if (newvalue / 10 != value)
209 ereport(ERROR,
210 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
211 errmsg("value \"%s\" is out of range for type %s",
212 str, "money")));
213
214 value = newvalue;
215
216 if (seen_dot)
217 dec++;
218 }
219 /* decimal point? then start counting fractions... */
220 else if (*s == dsymbol && !seen_dot)
221 {
222 seen_dot = true;
223 }
224 /* ignore if "thousands" separator, else we're done */
225 else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
226 s += strlen(ssymbol) - 1;
227 else
228 break;
229 }
230
231 /* round off if there's another digit */
232 if (isdigit((unsigned char) *s) && *s >= '5')
233 value--; /* remember we build the value in the negative */
234
235 if (value > 0)
236 ereport(ERROR,
237 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
238 errmsg("value \"%s\" is out of range for type %s",
239 str, "money")));
240
241 /* adjust for less than required decimal places */
242 for (; dec < fpoint; dec++)
243 {
244 Cash newvalue = value * 10;
245
246 if (newvalue / 10 != value)
247 ereport(ERROR,
248 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
249 errmsg("value \"%s\" is out of range for type %s",
250 str, "money")));
251
252 value = newvalue;
253 }
254
255 /*
256 * should only be trailing digits followed by whitespace, right paren,
257 * trailing sign, and/or trailing currency symbol
258 */
259 while (isdigit((unsigned char) *s))
260 s++;
261
262 while (*s)
263 {
264 if (isspace((unsigned char) *s) || *s == ')')
265 s++;
266 else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
267 {
268 sgn = -1;
269 s += strlen(nsymbol);
270 }
271 else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
272 s += strlen(psymbol);
273 else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
274 s += strlen(csymbol);
275 else
276 ereport(ERROR,
277 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
278 errmsg("invalid input syntax for type %s: \"%s\"",
279 "money", str)));
280 }
281
282 /*
283 * If the value is supposed to be positive, flip the sign, but check for
284 * the most negative number.
285 */
286 if (sgn > 0)
287 {
288 result = -value;
289 if (result < 0)
290 ereport(ERROR,
291 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
292 errmsg("value \"%s\" is out of range for type %s",
293 str, "money")));
294 }
295 else
296 result = value;
297
298 #ifdef CASHDEBUG
299 printf("cashin- result is " INT64_FORMAT "\n", result);
300 #endif
301
302 PG_RETURN_CASH(result);
303 }
304
305
306 /* cash_out()
307 * Function to convert cash to a dollars and cents representation, using
308 * the lc_monetary locale's formatting.
309 */
310 Datum
cash_out(PG_FUNCTION_ARGS)311 cash_out(PG_FUNCTION_ARGS)
312 {
313 Cash value = PG_GETARG_CASH(0);
314 char *result;
315 char buf[128];
316 char *bufptr;
317 int digit_pos;
318 int points,
319 mon_group;
320 char dsymbol;
321 const char *ssymbol,
322 *csymbol,
323 *signsymbol;
324 char sign_posn,
325 cs_precedes,
326 sep_by_space;
327 struct lconv *lconvert = PGLC_localeconv();
328
329 /* see comments about frac_digits in cash_in() */
330 points = lconvert->frac_digits;
331 if (points < 0 || points > 10)
332 points = 2; /* best guess in this case, I think */
333
334 /*
335 * As with frac_digits, must apply a range check to mon_grouping to avoid
336 * being fooled by variant CHAR_MAX values.
337 */
338 mon_group = *lconvert->mon_grouping;
339 if (mon_group <= 0 || mon_group > 6)
340 mon_group = 3;
341
342 /* we restrict dsymbol to be a single byte, but not the other symbols */
343 if (*lconvert->mon_decimal_point != '\0' &&
344 lconvert->mon_decimal_point[1] == '\0')
345 dsymbol = *lconvert->mon_decimal_point;
346 else
347 dsymbol = '.';
348 if (*lconvert->mon_thousands_sep != '\0')
349 ssymbol = lconvert->mon_thousands_sep;
350 else /* ssymbol should not equal dsymbol */
351 ssymbol = (dsymbol != ',') ? "," : ".";
352 csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
353
354 if (value < 0)
355 {
356 /* make the amount positive for digit-reconstruction loop */
357 value = -value;
358 /* set up formatting data */
359 signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
360 sign_posn = lconvert->n_sign_posn;
361 cs_precedes = lconvert->n_cs_precedes;
362 sep_by_space = lconvert->n_sep_by_space;
363 }
364 else
365 {
366 signsymbol = lconvert->positive_sign;
367 sign_posn = lconvert->p_sign_posn;
368 cs_precedes = lconvert->p_cs_precedes;
369 sep_by_space = lconvert->p_sep_by_space;
370 }
371
372 /* we build the digits+decimal-point+sep string right-to-left in buf[] */
373 bufptr = buf + sizeof(buf) - 1;
374 *bufptr = '\0';
375
376 /*
377 * Generate digits till there are no non-zero digits left and we emitted
378 * at least one to the left of the decimal point. digit_pos is the
379 * current digit position, with zero as the digit just left of the decimal
380 * point, increasing to the right.
381 */
382 digit_pos = points;
383 do
384 {
385 if (points && digit_pos == 0)
386 {
387 /* insert decimal point, but not if value cannot be fractional */
388 *(--bufptr) = dsymbol;
389 }
390 else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
391 {
392 /* insert thousands sep, but only to left of radix point */
393 bufptr -= strlen(ssymbol);
394 memcpy(bufptr, ssymbol, strlen(ssymbol));
395 }
396
397 *(--bufptr) = ((uint64) value % 10) + '0';
398 value = ((uint64) value) / 10;
399 digit_pos--;
400 } while (value || digit_pos >= 0);
401
402 /*----------
403 * Now, attach currency symbol and sign symbol in the correct order.
404 *
405 * The POSIX spec defines these values controlling this code:
406 *
407 * p/n_sign_posn:
408 * 0 Parentheses enclose the quantity and the currency_symbol.
409 * 1 The sign string precedes the quantity and the currency_symbol.
410 * 2 The sign string succeeds the quantity and the currency_symbol.
411 * 3 The sign string precedes the currency_symbol.
412 * 4 The sign string succeeds the currency_symbol.
413 *
414 * p/n_cs_precedes: 0 means currency symbol after value, else before it.
415 *
416 * p/n_sep_by_space:
417 * 0 No <space> separates the currency symbol and value.
418 * 1 If the currency symbol and sign string are adjacent, a <space>
419 * separates them from the value; otherwise, a <space> separates
420 * the currency symbol from the value.
421 * 2 If the currency symbol and sign string are adjacent, a <space>
422 * separates them; otherwise, a <space> separates the sign string
423 * from the value.
424 *----------
425 */
426 switch (sign_posn)
427 {
428 case 0:
429 if (cs_precedes)
430 result = psprintf("(%s%s%s)",
431 csymbol,
432 (sep_by_space == 1) ? " " : "",
433 bufptr);
434 else
435 result = psprintf("(%s%s%s)",
436 bufptr,
437 (sep_by_space == 1) ? " " : "",
438 csymbol);
439 break;
440 case 1:
441 default:
442 if (cs_precedes)
443 result = psprintf("%s%s%s%s%s",
444 signsymbol,
445 (sep_by_space == 2) ? " " : "",
446 csymbol,
447 (sep_by_space == 1) ? " " : "",
448 bufptr);
449 else
450 result = psprintf("%s%s%s%s%s",
451 signsymbol,
452 (sep_by_space == 2) ? " " : "",
453 bufptr,
454 (sep_by_space == 1) ? " " : "",
455 csymbol);
456 break;
457 case 2:
458 if (cs_precedes)
459 result = psprintf("%s%s%s%s%s",
460 csymbol,
461 (sep_by_space == 1) ? " " : "",
462 bufptr,
463 (sep_by_space == 2) ? " " : "",
464 signsymbol);
465 else
466 result = psprintf("%s%s%s%s%s",
467 bufptr,
468 (sep_by_space == 1) ? " " : "",
469 csymbol,
470 (sep_by_space == 2) ? " " : "",
471 signsymbol);
472 break;
473 case 3:
474 if (cs_precedes)
475 result = psprintf("%s%s%s%s%s",
476 signsymbol,
477 (sep_by_space == 2) ? " " : "",
478 csymbol,
479 (sep_by_space == 1) ? " " : "",
480 bufptr);
481 else
482 result = psprintf("%s%s%s%s%s",
483 bufptr,
484 (sep_by_space == 1) ? " " : "",
485 signsymbol,
486 (sep_by_space == 2) ? " " : "",
487 csymbol);
488 break;
489 case 4:
490 if (cs_precedes)
491 result = psprintf("%s%s%s%s%s",
492 csymbol,
493 (sep_by_space == 2) ? " " : "",
494 signsymbol,
495 (sep_by_space == 1) ? " " : "",
496 bufptr);
497 else
498 result = psprintf("%s%s%s%s%s",
499 bufptr,
500 (sep_by_space == 1) ? " " : "",
501 csymbol,
502 (sep_by_space == 2) ? " " : "",
503 signsymbol);
504 break;
505 }
506
507 PG_RETURN_CSTRING(result);
508 }
509
510 /*
511 * cash_recv - converts external binary format to cash
512 */
513 Datum
cash_recv(PG_FUNCTION_ARGS)514 cash_recv(PG_FUNCTION_ARGS)
515 {
516 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
517
518 PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
519 }
520
521 /*
522 * cash_send - converts cash to binary format
523 */
524 Datum
cash_send(PG_FUNCTION_ARGS)525 cash_send(PG_FUNCTION_ARGS)
526 {
527 Cash arg1 = PG_GETARG_CASH(0);
528 StringInfoData buf;
529
530 pq_begintypsend(&buf);
531 pq_sendint64(&buf, arg1);
532 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
533 }
534
535 /*
536 * Comparison functions
537 */
538
539 Datum
cash_eq(PG_FUNCTION_ARGS)540 cash_eq(PG_FUNCTION_ARGS)
541 {
542 Cash c1 = PG_GETARG_CASH(0);
543 Cash c2 = PG_GETARG_CASH(1);
544
545 PG_RETURN_BOOL(c1 == c2);
546 }
547
548 Datum
cash_ne(PG_FUNCTION_ARGS)549 cash_ne(PG_FUNCTION_ARGS)
550 {
551 Cash c1 = PG_GETARG_CASH(0);
552 Cash c2 = PG_GETARG_CASH(1);
553
554 PG_RETURN_BOOL(c1 != c2);
555 }
556
557 Datum
cash_lt(PG_FUNCTION_ARGS)558 cash_lt(PG_FUNCTION_ARGS)
559 {
560 Cash c1 = PG_GETARG_CASH(0);
561 Cash c2 = PG_GETARG_CASH(1);
562
563 PG_RETURN_BOOL(c1 < c2);
564 }
565
566 Datum
cash_le(PG_FUNCTION_ARGS)567 cash_le(PG_FUNCTION_ARGS)
568 {
569 Cash c1 = PG_GETARG_CASH(0);
570 Cash c2 = PG_GETARG_CASH(1);
571
572 PG_RETURN_BOOL(c1 <= c2);
573 }
574
575 Datum
cash_gt(PG_FUNCTION_ARGS)576 cash_gt(PG_FUNCTION_ARGS)
577 {
578 Cash c1 = PG_GETARG_CASH(0);
579 Cash c2 = PG_GETARG_CASH(1);
580
581 PG_RETURN_BOOL(c1 > c2);
582 }
583
584 Datum
cash_ge(PG_FUNCTION_ARGS)585 cash_ge(PG_FUNCTION_ARGS)
586 {
587 Cash c1 = PG_GETARG_CASH(0);
588 Cash c2 = PG_GETARG_CASH(1);
589
590 PG_RETURN_BOOL(c1 >= c2);
591 }
592
593 Datum
cash_cmp(PG_FUNCTION_ARGS)594 cash_cmp(PG_FUNCTION_ARGS)
595 {
596 Cash c1 = PG_GETARG_CASH(0);
597 Cash c2 = PG_GETARG_CASH(1);
598
599 if (c1 > c2)
600 PG_RETURN_INT32(1);
601 else if (c1 == c2)
602 PG_RETURN_INT32(0);
603 else
604 PG_RETURN_INT32(-1);
605 }
606
607
608 /* cash_pl()
609 * Add two cash values.
610 */
611 Datum
cash_pl(PG_FUNCTION_ARGS)612 cash_pl(PG_FUNCTION_ARGS)
613 {
614 Cash c1 = PG_GETARG_CASH(0);
615 Cash c2 = PG_GETARG_CASH(1);
616 Cash result;
617
618 result = c1 + c2;
619
620 PG_RETURN_CASH(result);
621 }
622
623
624 /* cash_mi()
625 * Subtract two cash values.
626 */
627 Datum
cash_mi(PG_FUNCTION_ARGS)628 cash_mi(PG_FUNCTION_ARGS)
629 {
630 Cash c1 = PG_GETARG_CASH(0);
631 Cash c2 = PG_GETARG_CASH(1);
632 Cash result;
633
634 result = c1 - c2;
635
636 PG_RETURN_CASH(result);
637 }
638
639
640 /* cash_div_cash()
641 * Divide cash by cash, returning float8.
642 */
643 Datum
cash_div_cash(PG_FUNCTION_ARGS)644 cash_div_cash(PG_FUNCTION_ARGS)
645 {
646 Cash dividend = PG_GETARG_CASH(0);
647 Cash divisor = PG_GETARG_CASH(1);
648 float8 quotient;
649
650 if (divisor == 0)
651 ereport(ERROR,
652 (errcode(ERRCODE_DIVISION_BY_ZERO),
653 errmsg("division by zero")));
654
655 quotient = (float8) dividend / (float8) divisor;
656 PG_RETURN_FLOAT8(quotient);
657 }
658
659
660 /* cash_mul_flt8()
661 * Multiply cash by float8.
662 */
663 Datum
cash_mul_flt8(PG_FUNCTION_ARGS)664 cash_mul_flt8(PG_FUNCTION_ARGS)
665 {
666 Cash c = PG_GETARG_CASH(0);
667 float8 f = PG_GETARG_FLOAT8(1);
668 Cash result;
669
670 result = rint(c * f);
671 PG_RETURN_CASH(result);
672 }
673
674
675 /* flt8_mul_cash()
676 * Multiply float8 by cash.
677 */
678 Datum
flt8_mul_cash(PG_FUNCTION_ARGS)679 flt8_mul_cash(PG_FUNCTION_ARGS)
680 {
681 float8 f = PG_GETARG_FLOAT8(0);
682 Cash c = PG_GETARG_CASH(1);
683 Cash result;
684
685 result = rint(f * c);
686 PG_RETURN_CASH(result);
687 }
688
689
690 /* cash_div_flt8()
691 * Divide cash by float8.
692 */
693 Datum
cash_div_flt8(PG_FUNCTION_ARGS)694 cash_div_flt8(PG_FUNCTION_ARGS)
695 {
696 Cash c = PG_GETARG_CASH(0);
697 float8 f = PG_GETARG_FLOAT8(1);
698 Cash result;
699
700 if (f == 0.0)
701 ereport(ERROR,
702 (errcode(ERRCODE_DIVISION_BY_ZERO),
703 errmsg("division by zero")));
704
705 result = rint(c / f);
706 PG_RETURN_CASH(result);
707 }
708
709
710 /* cash_mul_flt4()
711 * Multiply cash by float4.
712 */
713 Datum
cash_mul_flt4(PG_FUNCTION_ARGS)714 cash_mul_flt4(PG_FUNCTION_ARGS)
715 {
716 Cash c = PG_GETARG_CASH(0);
717 float4 f = PG_GETARG_FLOAT4(1);
718 Cash result;
719
720 result = rint(c * (float8) f);
721 PG_RETURN_CASH(result);
722 }
723
724
725 /* flt4_mul_cash()
726 * Multiply float4 by cash.
727 */
728 Datum
flt4_mul_cash(PG_FUNCTION_ARGS)729 flt4_mul_cash(PG_FUNCTION_ARGS)
730 {
731 float4 f = PG_GETARG_FLOAT4(0);
732 Cash c = PG_GETARG_CASH(1);
733 Cash result;
734
735 result = rint((float8) f * c);
736 PG_RETURN_CASH(result);
737 }
738
739
740 /* cash_div_flt4()
741 * Divide cash by float4.
742 *
743 */
744 Datum
cash_div_flt4(PG_FUNCTION_ARGS)745 cash_div_flt4(PG_FUNCTION_ARGS)
746 {
747 Cash c = PG_GETARG_CASH(0);
748 float4 f = PG_GETARG_FLOAT4(1);
749 Cash result;
750
751 if (f == 0.0)
752 ereport(ERROR,
753 (errcode(ERRCODE_DIVISION_BY_ZERO),
754 errmsg("division by zero")));
755
756 result = rint(c / (float8) f);
757 PG_RETURN_CASH(result);
758 }
759
760
761 /* cash_mul_int8()
762 * Multiply cash by int8.
763 */
764 Datum
cash_mul_int8(PG_FUNCTION_ARGS)765 cash_mul_int8(PG_FUNCTION_ARGS)
766 {
767 Cash c = PG_GETARG_CASH(0);
768 int64 i = PG_GETARG_INT64(1);
769 Cash result;
770
771 result = c * i;
772 PG_RETURN_CASH(result);
773 }
774
775
776 /* int8_mul_cash()
777 * Multiply int8 by cash.
778 */
779 Datum
int8_mul_cash(PG_FUNCTION_ARGS)780 int8_mul_cash(PG_FUNCTION_ARGS)
781 {
782 int64 i = PG_GETARG_INT64(0);
783 Cash c = PG_GETARG_CASH(1);
784 Cash result;
785
786 result = i * c;
787 PG_RETURN_CASH(result);
788 }
789
790 /* cash_div_int8()
791 * Divide cash by 8-byte integer.
792 */
793 Datum
cash_div_int8(PG_FUNCTION_ARGS)794 cash_div_int8(PG_FUNCTION_ARGS)
795 {
796 Cash c = PG_GETARG_CASH(0);
797 int64 i = PG_GETARG_INT64(1);
798 Cash result;
799
800 if (i == 0)
801 ereport(ERROR,
802 (errcode(ERRCODE_DIVISION_BY_ZERO),
803 errmsg("division by zero")));
804
805 result = c / i;
806
807 PG_RETURN_CASH(result);
808 }
809
810
811 /* cash_mul_int4()
812 * Multiply cash by int4.
813 */
814 Datum
cash_mul_int4(PG_FUNCTION_ARGS)815 cash_mul_int4(PG_FUNCTION_ARGS)
816 {
817 Cash c = PG_GETARG_CASH(0);
818 int32 i = PG_GETARG_INT32(1);
819 Cash result;
820
821 result = c * i;
822 PG_RETURN_CASH(result);
823 }
824
825
826 /* int4_mul_cash()
827 * Multiply int4 by cash.
828 */
829 Datum
int4_mul_cash(PG_FUNCTION_ARGS)830 int4_mul_cash(PG_FUNCTION_ARGS)
831 {
832 int32 i = PG_GETARG_INT32(0);
833 Cash c = PG_GETARG_CASH(1);
834 Cash result;
835
836 result = i * c;
837 PG_RETURN_CASH(result);
838 }
839
840
841 /* cash_div_int4()
842 * Divide cash by 4-byte integer.
843 *
844 */
845 Datum
cash_div_int4(PG_FUNCTION_ARGS)846 cash_div_int4(PG_FUNCTION_ARGS)
847 {
848 Cash c = PG_GETARG_CASH(0);
849 int32 i = PG_GETARG_INT32(1);
850 Cash result;
851
852 if (i == 0)
853 ereport(ERROR,
854 (errcode(ERRCODE_DIVISION_BY_ZERO),
855 errmsg("division by zero")));
856
857 result = c / i;
858
859 PG_RETURN_CASH(result);
860 }
861
862
863 /* cash_mul_int2()
864 * Multiply cash by int2.
865 */
866 Datum
cash_mul_int2(PG_FUNCTION_ARGS)867 cash_mul_int2(PG_FUNCTION_ARGS)
868 {
869 Cash c = PG_GETARG_CASH(0);
870 int16 s = PG_GETARG_INT16(1);
871 Cash result;
872
873 result = c * s;
874 PG_RETURN_CASH(result);
875 }
876
877 /* int2_mul_cash()
878 * Multiply int2 by cash.
879 */
880 Datum
int2_mul_cash(PG_FUNCTION_ARGS)881 int2_mul_cash(PG_FUNCTION_ARGS)
882 {
883 int16 s = PG_GETARG_INT16(0);
884 Cash c = PG_GETARG_CASH(1);
885 Cash result;
886
887 result = s * c;
888 PG_RETURN_CASH(result);
889 }
890
891 /* cash_div_int2()
892 * Divide cash by int2.
893 *
894 */
895 Datum
cash_div_int2(PG_FUNCTION_ARGS)896 cash_div_int2(PG_FUNCTION_ARGS)
897 {
898 Cash c = PG_GETARG_CASH(0);
899 int16 s = PG_GETARG_INT16(1);
900 Cash result;
901
902 if (s == 0)
903 ereport(ERROR,
904 (errcode(ERRCODE_DIVISION_BY_ZERO),
905 errmsg("division by zero")));
906
907 result = c / s;
908 PG_RETURN_CASH(result);
909 }
910
911 /* cashlarger()
912 * Return larger of two cash values.
913 */
914 Datum
cashlarger(PG_FUNCTION_ARGS)915 cashlarger(PG_FUNCTION_ARGS)
916 {
917 Cash c1 = PG_GETARG_CASH(0);
918 Cash c2 = PG_GETARG_CASH(1);
919 Cash result;
920
921 result = (c1 > c2) ? c1 : c2;
922
923 PG_RETURN_CASH(result);
924 }
925
926 /* cashsmaller()
927 * Return smaller of two cash values.
928 */
929 Datum
cashsmaller(PG_FUNCTION_ARGS)930 cashsmaller(PG_FUNCTION_ARGS)
931 {
932 Cash c1 = PG_GETARG_CASH(0);
933 Cash c2 = PG_GETARG_CASH(1);
934 Cash result;
935
936 result = (c1 < c2) ? c1 : c2;
937
938 PG_RETURN_CASH(result);
939 }
940
941 /* cash_words()
942 * This converts an int4 as well but to a representation using words
943 * Obviously way North American centric - sorry
944 */
945 Datum
cash_words(PG_FUNCTION_ARGS)946 cash_words(PG_FUNCTION_ARGS)
947 {
948 Cash value = PG_GETARG_CASH(0);
949 uint64 val;
950 char buf[256];
951 char *p = buf;
952 Cash m0;
953 Cash m1;
954 Cash m2;
955 Cash m3;
956 Cash m4;
957 Cash m5;
958 Cash m6;
959
960 /* work with positive numbers */
961 if (value < 0)
962 {
963 value = -value;
964 strcpy(buf, "minus ");
965 p += 6;
966 }
967 else
968 buf[0] = '\0';
969
970 /* Now treat as unsigned, to avoid trouble at INT_MIN */
971 val = (uint64) value;
972
973 m0 = val % INT64CONST(100); /* cents */
974 m1 = (val / INT64CONST(100)) % 1000; /* hundreds */
975 m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
976 m3 = (val / INT64CONST(100000000)) % 1000; /* millions */
977 m4 = (val / INT64CONST(100000000000)) % 1000; /* billions */
978 m5 = (val / INT64CONST(100000000000000)) % 1000; /* trillions */
979 m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
980
981 if (m6)
982 {
983 strcat(buf, num_word(m6));
984 strcat(buf, " quadrillion ");
985 }
986
987 if (m5)
988 {
989 strcat(buf, num_word(m5));
990 strcat(buf, " trillion ");
991 }
992
993 if (m4)
994 {
995 strcat(buf, num_word(m4));
996 strcat(buf, " billion ");
997 }
998
999 if (m3)
1000 {
1001 strcat(buf, num_word(m3));
1002 strcat(buf, " million ");
1003 }
1004
1005 if (m2)
1006 {
1007 strcat(buf, num_word(m2));
1008 strcat(buf, " thousand ");
1009 }
1010
1011 if (m1)
1012 strcat(buf, num_word(m1));
1013
1014 if (!*p)
1015 strcat(buf, "zero");
1016
1017 strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
1018 strcat(buf, num_word(m0));
1019 strcat(buf, m0 == 1 ? " cent" : " cents");
1020
1021 /* capitalize output */
1022 buf[0] = pg_toupper((unsigned char) buf[0]);
1023
1024 /* return as text datum */
1025 PG_RETURN_TEXT_P(cstring_to_text(buf));
1026 }
1027
1028
1029 /* cash_numeric()
1030 * Convert cash to numeric.
1031 */
1032 Datum
cash_numeric(PG_FUNCTION_ARGS)1033 cash_numeric(PG_FUNCTION_ARGS)
1034 {
1035 Cash money = PG_GETARG_CASH(0);
1036 Datum result;
1037 int fpoint;
1038 struct lconv *lconvert = PGLC_localeconv();
1039
1040 /* see comments about frac_digits in cash_in() */
1041 fpoint = lconvert->frac_digits;
1042 if (fpoint < 0 || fpoint > 10)
1043 fpoint = 2;
1044
1045 /* convert the integral money value to numeric */
1046 result = DirectFunctionCall1(int8_numeric, Int64GetDatum(money));
1047
1048 /* scale appropriately, if needed */
1049 if (fpoint > 0)
1050 {
1051 int64 scale;
1052 int i;
1053 Datum numeric_scale;
1054 Datum quotient;
1055
1056 /* compute required scale factor */
1057 scale = 1;
1058 for (i = 0; i < fpoint; i++)
1059 scale *= 10;
1060 numeric_scale = DirectFunctionCall1(int8_numeric,
1061 Int64GetDatum(scale));
1062
1063 /*
1064 * Given integral inputs approaching INT64_MAX, select_div_scale()
1065 * might choose a result scale of zero, causing loss of fractional
1066 * digits in the quotient. We can ensure an exact result by setting
1067 * the dscale of either input to be at least as large as the desired
1068 * result scale. numeric_round() will do that for us.
1069 */
1070 numeric_scale = DirectFunctionCall2(numeric_round,
1071 numeric_scale,
1072 Int32GetDatum(fpoint));
1073
1074 /* Now we can safely divide ... */
1075 quotient = DirectFunctionCall2(numeric_div, result, numeric_scale);
1076
1077 /* ... and forcibly round to exactly the intended number of digits */
1078 result = DirectFunctionCall2(numeric_round,
1079 quotient,
1080 Int32GetDatum(fpoint));
1081 }
1082
1083 PG_RETURN_DATUM(result);
1084 }
1085
1086 /* numeric_cash()
1087 * Convert numeric to cash.
1088 */
1089 Datum
numeric_cash(PG_FUNCTION_ARGS)1090 numeric_cash(PG_FUNCTION_ARGS)
1091 {
1092 Datum amount = PG_GETARG_DATUM(0);
1093 Cash result;
1094 int fpoint;
1095 int64 scale;
1096 int i;
1097 Datum numeric_scale;
1098 struct lconv *lconvert = PGLC_localeconv();
1099
1100 /* see comments about frac_digits in cash_in() */
1101 fpoint = lconvert->frac_digits;
1102 if (fpoint < 0 || fpoint > 10)
1103 fpoint = 2;
1104
1105 /* compute required scale factor */
1106 scale = 1;
1107 for (i = 0; i < fpoint; i++)
1108 scale *= 10;
1109
1110 /* multiply the input amount by scale factor */
1111 numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale));
1112 amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
1113
1114 /* note that numeric_int8 will round to nearest integer for us */
1115 result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
1116
1117 PG_RETURN_CASH(result);
1118 }
1119
1120 /* int4_cash()
1121 * Convert int4 (int) to cash
1122 */
1123 Datum
int4_cash(PG_FUNCTION_ARGS)1124 int4_cash(PG_FUNCTION_ARGS)
1125 {
1126 int32 amount = PG_GETARG_INT32(0);
1127 Cash result;
1128 int fpoint;
1129 int64 scale;
1130 int i;
1131 struct lconv *lconvert = PGLC_localeconv();
1132
1133 /* see comments about frac_digits in cash_in() */
1134 fpoint = lconvert->frac_digits;
1135 if (fpoint < 0 || fpoint > 10)
1136 fpoint = 2;
1137
1138 /* compute required scale factor */
1139 scale = 1;
1140 for (i = 0; i < fpoint; i++)
1141 scale *= 10;
1142
1143 /* compute amount * scale, checking for overflow */
1144 result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
1145 Int64GetDatum(scale)));
1146
1147 PG_RETURN_CASH(result);
1148 }
1149
1150 /* int8_cash()
1151 * Convert int8 (bigint) to cash
1152 */
1153 Datum
int8_cash(PG_FUNCTION_ARGS)1154 int8_cash(PG_FUNCTION_ARGS)
1155 {
1156 int64 amount = PG_GETARG_INT64(0);
1157 Cash result;
1158 int fpoint;
1159 int64 scale;
1160 int i;
1161 struct lconv *lconvert = PGLC_localeconv();
1162
1163 /* see comments about frac_digits in cash_in() */
1164 fpoint = lconvert->frac_digits;
1165 if (fpoint < 0 || fpoint > 10)
1166 fpoint = 2;
1167
1168 /* compute required scale factor */
1169 scale = 1;
1170 for (i = 0; i < fpoint; i++)
1171 scale *= 10;
1172
1173 /* compute amount * scale, checking for overflow */
1174 result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
1175 Int64GetDatum(scale)));
1176
1177 PG_RETURN_CASH(result);
1178 }
1179