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