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