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 24 : 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 24 : const char *const *big = small + 18;
48 24 : int tu = value % 100;
49 :
50 : /* deal with the simple cases first */
51 24 : if (value <= 20)
52 : {
53 6 : appendStringInfoString(buf, small[value]);
54 6 : return;
55 : }
56 :
57 : /* is it an even multiple of 100? */
58 18 : if (!tu)
59 : {
60 0 : appendStringInfo(buf, "%s hundred", small[value / 100]);
61 0 : return;
62 : }
63 :
64 : /* more than 99? */
65 18 : if (value > 99)
66 : {
67 : /* is it an even multiple of 10 other than 10? */
68 12 : if (value % 10 == 0 && tu > 10)
69 0 : appendStringInfo(buf, "%s hundred %s",
70 0 : small[value / 100], big[tu / 10]);
71 12 : else if (tu < 20)
72 0 : appendStringInfo(buf, "%s hundred and %s",
73 0 : small[value / 100], small[tu]);
74 : else
75 12 : appendStringInfo(buf, "%s hundred %s %s",
76 12 : 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 6 : if (value % 10 == 0 && tu > 10)
82 0 : appendStringInfoString(buf, big[tu / 10]);
83 6 : else if (tu < 20)
84 0 : appendStringInfoString(buf, small[tu]);
85 : else
86 6 : appendStringInfo(buf, "%s %s", big[tu / 10], small[tu % 10]);
87 : }
88 : } /* num_word() */
89 :
90 : static inline Cash
91 30 : cash_pl_cash(Cash c1, Cash c2)
92 : {
93 : Cash res;
94 :
95 30 : if (unlikely(pg_add_s64_overflow(c1, c2, &res)))
96 6 : ereport(ERROR,
97 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
98 : errmsg("money out of range")));
99 :
100 24 : return res;
101 : }
102 :
103 : static inline Cash
104 18 : cash_mi_cash(Cash c1, Cash c2)
105 : {
106 : Cash res;
107 :
108 18 : if (unlikely(pg_sub_s64_overflow(c1, c2, &res)))
109 6 : ereport(ERROR,
110 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
111 : errmsg("money out of range")));
112 :
113 12 : return res;
114 : }
115 :
116 : static inline Cash
117 48 : cash_mul_float8(Cash c, float8 f)
118 : {
119 48 : float8 res = rint(float8_mul((float8) c, f));
120 :
121 48 : if (unlikely(isnan(res) || !FLOAT8_FITS_IN_INT64(res)))
122 24 : ereport(ERROR,
123 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
124 : errmsg("money out of range")));
125 :
126 24 : return (Cash) res;
127 : }
128 :
129 : static inline Cash
130 30 : cash_div_float8(Cash c, float8 f)
131 : {
132 30 : float8 res = rint(float8_div((float8) c, f));
133 :
134 30 : if (unlikely(isnan(res) || !FLOAT8_FITS_IN_INT64(res)))
135 6 : ereport(ERROR,
136 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
137 : errmsg("money out of range")));
138 :
139 24 : return (Cash) res;
140 : }
141 :
142 : static inline Cash
143 42 : cash_mul_int64(Cash c, int64 i)
144 : {
145 : Cash res;
146 :
147 42 : if (unlikely(pg_mul_s64_overflow(c, i, &res)))
148 6 : ereport(ERROR,
149 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
150 : errmsg("money out of range")));
151 :
152 36 : return res;
153 : }
154 :
155 : static inline Cash
156 60 : cash_div_int64(Cash c, int64 i)
157 : {
158 60 : if (unlikely(i == 0))
159 6 : ereport(ERROR,
160 : (errcode(ERRCODE_DIVISION_BY_ZERO),
161 : errmsg("division by zero")));
162 :
163 54 : 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 1606 : cash_in(PG_FUNCTION_ARGS)
174 : {
175 1606 : char *str = PG_GETARG_CSTRING(0);
176 1606 : Node *escontext = fcinfo->context;
177 : Cash result;
178 1606 : Cash value = 0;
179 1606 : Cash dec = 0;
180 1606 : Cash sgn = 1;
181 1606 : bool seen_dot = false;
182 1606 : const char *s = str;
183 : int fpoint;
184 : char dsymbol;
185 : const char *ssymbol,
186 : *psymbol,
187 : *nsymbol,
188 : *csymbol;
189 1606 : 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 1606 : fpoint = lconvert->frac_digits;
202 1606 : if (fpoint < 0 || fpoint > 10)
203 1606 : 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 1606 : if (*lconvert->mon_decimal_point != '\0' &&
207 0 : lconvert->mon_decimal_point[1] == '\0')
208 0 : dsymbol = *lconvert->mon_decimal_point;
209 : else
210 1606 : dsymbol = '.';
211 1606 : if (*lconvert->mon_thousands_sep != '\0')
212 0 : ssymbol = lconvert->mon_thousands_sep;
213 : else /* ssymbol should not equal dsymbol */
214 1606 : ssymbol = (dsymbol != ',') ? "," : ".";
215 1606 : csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
216 1606 : psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
217 1606 : 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 1606 : while (isspace((unsigned char) *s))
227 0 : s++;
228 1606 : if (strncmp(s, csymbol, strlen(csymbol)) == 0)
229 120 : s += strlen(csymbol);
230 1606 : 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 1606 : if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
241 : {
242 622 : sgn = -1;
243 622 : s += strlen(nsymbol);
244 : }
245 984 : else if (*s == '(')
246 : {
247 12 : sgn = -1;
248 12 : s++;
249 : }
250 972 : 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 1606 : while (isspace((unsigned char) *s))
259 0 : s++;
260 1606 : if (strncmp(s, csymbol, strlen(csymbol)) == 0)
261 6 : s += strlen(csymbol);
262 1606 : 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 15826 : 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 14304 : if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
285 12778 : {
286 12796 : int8 digit = *s - '0';
287 :
288 25580 : if (pg_mul_s64_overflow(value, 10, &value) ||
289 12784 : pg_sub_s64_overflow(value, digit, &value))
290 18 : 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 12778 : if (seen_dot)
296 2854 : dec++;
297 : }
298 : /* decimal point? then start counting fractions... */
299 1508 : else if (*s == dsymbol && !seen_dot)
300 : {
301 1436 : seen_dot = true;
302 : }
303 : /* ignore if "thousands" separator, else we're done */
304 72 : else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
305 6 : s += strlen(ssymbol) - 1;
306 : else
307 66 : break;
308 : }
309 :
310 : /* round off if there's another digit */
311 1588 : if (isdigit((unsigned char) *s) && *s >= '5')
312 : {
313 : /* remember we build the value in the negative */
314 30 : if (pg_sub_s64_overflow(value, 1, &value))
315 6 : 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 1886 : for (; dec < fpoint; dec++)
323 : {
324 328 : if (pg_mul_s64_overflow(value, 10, &value))
325 24 : 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 1594 : while (isdigit((unsigned char) *s))
336 36 : s++;
337 :
338 1570 : while (*s)
339 : {
340 24 : if (isspace((unsigned char) *s) || *s == ')')
341 12 : s++;
342 12 : else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
343 : {
344 0 : sgn = -1;
345 0 : s += strlen(nsymbol);
346 : }
347 12 : else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
348 0 : s += strlen(psymbol);
349 12 : else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
350 0 : s += strlen(csymbol);
351 : else
352 12 : 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 1546 : if (sgn > 0)
363 : {
364 936 : if (value == PG_INT64_MIN)
365 12 : 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 924 : result = -value;
370 : }
371 : else
372 610 : result = value;
373 :
374 : #ifdef CASHDEBUG
375 : printf("cashin- result is " INT64_FORMAT "\n", result);
376 : #endif
377 :
378 1534 : 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 416 : cash_out(PG_FUNCTION_ARGS)
388 : {
389 416 : 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 416 : struct lconv *lconvert = PGLC_localeconv();
405 :
406 : /* see comments about frac_digits in cash_in() */
407 416 : points = lconvert->frac_digits;
408 416 : if (points < 0 || points > 10)
409 416 : 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 416 : mon_group = *lconvert->mon_grouping;
416 416 : if (mon_group <= 0 || mon_group > 6)
417 416 : mon_group = 3;
418 :
419 : /* we restrict dsymbol to be a single byte, but not the other symbols */
420 416 : if (*lconvert->mon_decimal_point != '\0' &&
421 0 : lconvert->mon_decimal_point[1] == '\0')
422 0 : dsymbol = *lconvert->mon_decimal_point;
423 : else
424 416 : dsymbol = '.';
425 416 : if (*lconvert->mon_thousands_sep != '\0')
426 0 : ssymbol = lconvert->mon_thousands_sep;
427 : else /* ssymbol should not equal dsymbol */
428 416 : ssymbol = (dsymbol != ',') ? "," : ".";
429 416 : csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
430 :
431 416 : if (value < 0)
432 : {
433 : /* set up formatting data */
434 86 : signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
435 86 : sign_posn = lconvert->n_sign_posn;
436 86 : cs_precedes = lconvert->n_cs_precedes;
437 86 : sep_by_space = lconvert->n_sep_by_space;
438 : }
439 : else
440 : {
441 330 : signsymbol = lconvert->positive_sign;
442 330 : sign_posn = lconvert->p_sign_posn;
443 330 : cs_precedes = lconvert->p_cs_precedes;
444 330 : sep_by_space = lconvert->p_sep_by_space;
445 : }
446 :
447 : /* make the amount positive for digit-reconstruction loop */
448 416 : uvalue = pg_abs_s64(value);
449 :
450 : /* we build the digits+decimal-point+sep string right-to-left in buf[] */
451 416 : bufptr = buf + sizeof(buf) - 1;
452 416 : *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 416 : digit_pos = points;
461 : do
462 : {
463 3288 : if (points && digit_pos == 0)
464 : {
465 : /* insert decimal point, but not if value cannot be fractional */
466 416 : *(--bufptr) = dsymbol;
467 : }
468 2872 : else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
469 : {
470 : /* insert thousands sep, but only to left of radix point */
471 530 : bufptr -= strlen(ssymbol);
472 530 : memcpy(bufptr, ssymbol, strlen(ssymbol));
473 : }
474 :
475 3288 : *(--bufptr) = (uvalue % 10) + '0';
476 3288 : uvalue = uvalue / 10;
477 3288 : digit_pos--;
478 3288 : } 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 416 : 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 416 : case 1:
519 : default:
520 416 : if (cs_precedes)
521 416 : 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 416 : 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 416 : 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 1100 : cash_eq(PG_FUNCTION_ARGS)
619 : {
620 1100 : Cash c1 = PG_GETARG_CASH(0);
621 1100 : Cash c2 = PG_GETARG_CASH(1);
622 :
623 1100 : PG_RETURN_BOOL(c1 == c2);
624 : }
625 :
626 : Datum
627 12 : cash_ne(PG_FUNCTION_ARGS)
628 : {
629 12 : Cash c1 = PG_GETARG_CASH(0);
630 12 : Cash c2 = PG_GETARG_CASH(1);
631 :
632 12 : PG_RETURN_BOOL(c1 != c2);
633 : }
634 :
635 : Datum
636 1098 : cash_lt(PG_FUNCTION_ARGS)
637 : {
638 1098 : Cash c1 = PG_GETARG_CASH(0);
639 1098 : Cash c2 = PG_GETARG_CASH(1);
640 :
641 1098 : PG_RETURN_BOOL(c1 < c2);
642 : }
643 :
644 : Datum
645 1098 : cash_le(PG_FUNCTION_ARGS)
646 : {
647 1098 : Cash c1 = PG_GETARG_CASH(0);
648 1098 : Cash c2 = PG_GETARG_CASH(1);
649 :
650 1098 : PG_RETURN_BOOL(c1 <= c2);
651 : }
652 :
653 : Datum
654 1098 : cash_gt(PG_FUNCTION_ARGS)
655 : {
656 1098 : Cash c1 = PG_GETARG_CASH(0);
657 1098 : Cash c2 = PG_GETARG_CASH(1);
658 :
659 1098 : PG_RETURN_BOOL(c1 > c2);
660 : }
661 :
662 : Datum
663 1098 : cash_ge(PG_FUNCTION_ARGS)
664 : {
665 1098 : Cash c1 = PG_GETARG_CASH(0);
666 1098 : Cash c2 = PG_GETARG_CASH(1);
667 :
668 1098 : PG_RETURN_BOOL(c1 >= c2);
669 : }
670 :
671 : Datum
672 1318 : cash_cmp(PG_FUNCTION_ARGS)
673 : {
674 1318 : Cash c1 = PG_GETARG_CASH(0);
675 1318 : Cash c2 = PG_GETARG_CASH(1);
676 :
677 1318 : if (c1 > c2)
678 1122 : PG_RETURN_INT32(1);
679 196 : else if (c1 == c2)
680 14 : PG_RETURN_INT32(0);
681 : else
682 182 : PG_RETURN_INT32(-1);
683 : }
684 :
685 :
686 : /* cash_pl()
687 : * Add two cash values.
688 : */
689 : Datum
690 30 : cash_pl(PG_FUNCTION_ARGS)
691 : {
692 30 : Cash c1 = PG_GETARG_CASH(0);
693 30 : Cash c2 = PG_GETARG_CASH(1);
694 :
695 30 : PG_RETURN_CASH(cash_pl_cash(c1, c2));
696 : }
697 :
698 :
699 : /* cash_mi()
700 : * Subtract two cash values.
701 : */
702 : Datum
703 18 : cash_mi(PG_FUNCTION_ARGS)
704 : {
705 18 : Cash c1 = PG_GETARG_CASH(0);
706 18 : Cash c2 = PG_GETARG_CASH(1);
707 :
708 18 : 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 6 : cash_div_cash(PG_FUNCTION_ARGS)
717 : {
718 6 : Cash dividend = PG_GETARG_CASH(0);
719 6 : Cash divisor = PG_GETARG_CASH(1);
720 : float8 quotient;
721 :
722 6 : if (divisor == 0)
723 0 : ereport(ERROR,
724 : (errcode(ERRCODE_DIVISION_BY_ZERO),
725 : errmsg("division by zero")));
726 :
727 6 : quotient = (float8) dividend / (float8) divisor;
728 6 : PG_RETURN_FLOAT8(quotient);
729 : }
730 :
731 :
732 : /* cash_mul_flt8()
733 : * Multiply cash by float8.
734 : */
735 : Datum
736 24 : cash_mul_flt8(PG_FUNCTION_ARGS)
737 : {
738 24 : Cash c = PG_GETARG_CASH(0);
739 24 : float8 f = PG_GETARG_FLOAT8(1);
740 :
741 24 : PG_RETURN_CASH(cash_mul_float8(c, f));
742 : }
743 :
744 :
745 : /* flt8_mul_cash()
746 : * Multiply float8 by cash.
747 : */
748 : Datum
749 6 : flt8_mul_cash(PG_FUNCTION_ARGS)
750 : {
751 6 : float8 f = PG_GETARG_FLOAT8(0);
752 6 : Cash c = PG_GETARG_CASH(1);
753 :
754 6 : PG_RETURN_CASH(cash_mul_float8(c, f));
755 : }
756 :
757 :
758 : /* cash_div_flt8()
759 : * Divide cash by float8.
760 : */
761 : Datum
762 12 : cash_div_flt8(PG_FUNCTION_ARGS)
763 : {
764 12 : Cash c = PG_GETARG_CASH(0);
765 12 : float8 f = PG_GETARG_FLOAT8(1);
766 :
767 12 : PG_RETURN_CASH(cash_div_float8(c, f));
768 : }
769 :
770 :
771 : /* cash_mul_flt4()
772 : * Multiply cash by float4.
773 : */
774 : Datum
775 12 : cash_mul_flt4(PG_FUNCTION_ARGS)
776 : {
777 12 : Cash c = PG_GETARG_CASH(0);
778 12 : float4 f = PG_GETARG_FLOAT4(1);
779 :
780 12 : 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 6 : flt4_mul_cash(PG_FUNCTION_ARGS)
789 : {
790 6 : float4 f = PG_GETARG_FLOAT4(0);
791 6 : Cash c = PG_GETARG_CASH(1);
792 :
793 6 : 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 18 : cash_div_flt4(PG_FUNCTION_ARGS)
803 : {
804 18 : Cash c = PG_GETARG_CASH(0);
805 18 : float4 f = PG_GETARG_FLOAT4(1);
806 :
807 18 : 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 6 : cash_mul_int8(PG_FUNCTION_ARGS)
816 : {
817 6 : Cash c = PG_GETARG_CASH(0);
818 6 : int64 i = PG_GETARG_INT64(1);
819 :
820 6 : PG_RETURN_CASH(cash_mul_int64(c, i));
821 : }
822 :
823 :
824 : /* int8_mul_cash()
825 : * Multiply int8 by cash.
826 : */
827 : Datum
828 6 : int8_mul_cash(PG_FUNCTION_ARGS)
829 : {
830 6 : int64 i = PG_GETARG_INT64(0);
831 6 : Cash c = PG_GETARG_CASH(1);
832 :
833 6 : 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 18 : cash_div_int8(PG_FUNCTION_ARGS)
841 : {
842 18 : Cash c = PG_GETARG_CASH(0);
843 18 : int64 i = PG_GETARG_INT64(1);
844 :
845 18 : PG_RETURN_CASH(cash_div_int64(c, i));
846 : }
847 :
848 :
849 : /* cash_mul_int4()
850 : * Multiply cash by int4.
851 : */
852 : Datum
853 12 : cash_mul_int4(PG_FUNCTION_ARGS)
854 : {
855 12 : Cash c = PG_GETARG_CASH(0);
856 12 : int32 i = PG_GETARG_INT32(1);
857 :
858 12 : 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 6 : int4_mul_cash(PG_FUNCTION_ARGS)
867 : {
868 6 : int32 i = PG_GETARG_INT32(0);
869 6 : Cash c = PG_GETARG_CASH(1);
870 :
871 6 : 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 18 : cash_div_int4(PG_FUNCTION_ARGS)
881 : {
882 18 : Cash c = PG_GETARG_CASH(0);
883 18 : int32 i = PG_GETARG_INT32(1);
884 :
885 18 : 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 6 : cash_mul_int2(PG_FUNCTION_ARGS)
894 : {
895 6 : Cash c = PG_GETARG_CASH(0);
896 6 : int16 s = PG_GETARG_INT16(1);
897 :
898 6 : PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
899 : }
900 :
901 : /* int2_mul_cash()
902 : * Multiply int2 by cash.
903 : */
904 : Datum
905 6 : int2_mul_cash(PG_FUNCTION_ARGS)
906 : {
907 6 : int16 s = PG_GETARG_INT16(0);
908 6 : Cash c = PG_GETARG_CASH(1);
909 :
910 6 : 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 24 : cash_div_int2(PG_FUNCTION_ARGS)
919 : {
920 24 : Cash c = PG_GETARG_CASH(0);
921 24 : int16 s = PG_GETARG_INT16(1);
922 :
923 24 : PG_RETURN_CASH(cash_div_int64(c, (int64) s));
924 : }
925 :
926 : /* cashlarger()
927 : * Return larger of two cash values.
928 : */
929 : Datum
930 6 : cashlarger(PG_FUNCTION_ARGS)
931 : {
932 6 : Cash c1 = PG_GETARG_CASH(0);
933 6 : Cash c2 = PG_GETARG_CASH(1);
934 : Cash result;
935 :
936 6 : result = (c1 > c2) ? c1 : c2;
937 :
938 6 : PG_RETURN_CASH(result);
939 : }
940 :
941 : /* cashsmaller()
942 : * Return smaller of two cash values.
943 : */
944 : Datum
945 6 : cashsmaller(PG_FUNCTION_ARGS)
946 : {
947 6 : Cash c1 = PG_GETARG_CASH(0);
948 6 : Cash c2 = PG_GETARG_CASH(1);
949 : Cash result;
950 :
951 6 : result = (c1 < c2) ? c1 : c2;
952 :
953 6 : 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 12 : cash_words(PG_FUNCTION_ARGS)
962 : {
963 12 : 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 12 : initStringInfo(&buf);
977 :
978 : /* work with positive numbers */
979 12 : 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 12 : val = (uint64) value;
987 :
988 12 : dollars = val / INT64CONST(100);
989 12 : m0 = val % INT64CONST(100); /* cents */
990 12 : m1 = (val / INT64CONST(100)) % 1000; /* hundreds */
991 12 : m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
992 12 : m3 = (val / INT64CONST(100000000)) % 1000; /* millions */
993 12 : m4 = (val / INT64CONST(100000000000)) % 1000; /* billions */
994 12 : m5 = (val / INT64CONST(100000000000000)) % 1000; /* trillions */
995 12 : m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
996 :
997 12 : if (m6)
998 : {
999 0 : append_num_word(&buf, m6);
1000 0 : appendStringInfoString(&buf, " quadrillion ");
1001 : }
1002 :
1003 12 : if (m5)
1004 : {
1005 0 : append_num_word(&buf, m5);
1006 0 : appendStringInfoString(&buf, " trillion ");
1007 : }
1008 :
1009 12 : if (m4)
1010 : {
1011 0 : append_num_word(&buf, m4);
1012 0 : appendStringInfoString(&buf, " billion ");
1013 : }
1014 :
1015 12 : if (m3)
1016 : {
1017 0 : append_num_word(&buf, m3);
1018 0 : appendStringInfoString(&buf, " million ");
1019 : }
1020 :
1021 12 : if (m2)
1022 : {
1023 0 : append_num_word(&buf, m2);
1024 0 : appendStringInfoString(&buf, " thousand ");
1025 : }
1026 :
1027 12 : if (m1)
1028 12 : append_num_word(&buf, m1);
1029 :
1030 12 : if (dollars == 0)
1031 0 : appendStringInfoString(&buf, "zero");
1032 :
1033 12 : appendStringInfoString(&buf, dollars == 1 ? " dollar and " : " dollars and ");
1034 12 : append_num_word(&buf, m0);
1035 12 : appendStringInfoString(&buf, m0 == 1 ? " cent" : " cents");
1036 :
1037 : /* capitalize output */
1038 12 : buf.data[0] = pg_toupper((unsigned char) buf.data[0]);
1039 :
1040 : /* return as text datum */
1041 12 : res = cstring_to_text_with_len(buf.data, buf.len);
1042 12 : pfree(buf.data);
1043 12 : PG_RETURN_TEXT_P(res);
1044 : }
1045 :
1046 :
1047 : /* cash_numeric()
1048 : * Convert cash to numeric.
1049 : */
1050 : Datum
1051 24 : cash_numeric(PG_FUNCTION_ARGS)
1052 : {
1053 24 : Cash money = PG_GETARG_CASH(0);
1054 : Datum result;
1055 : int fpoint;
1056 24 : struct lconv *lconvert = PGLC_localeconv();
1057 :
1058 : /* see comments about frac_digits in cash_in() */
1059 24 : fpoint = lconvert->frac_digits;
1060 24 : if (fpoint < 0 || fpoint > 10)
1061 24 : fpoint = 2;
1062 :
1063 : /* convert the integral money value to numeric */
1064 24 : result = NumericGetDatum(int64_to_numeric(money));
1065 :
1066 : /* scale appropriately, if needed */
1067 24 : if (fpoint > 0)
1068 : {
1069 : int64 scale;
1070 : int i;
1071 : Datum numeric_scale;
1072 : Datum quotient;
1073 :
1074 : /* compute required scale factor */
1075 24 : scale = 1;
1076 72 : for (i = 0; i < fpoint; i++)
1077 48 : scale *= 10;
1078 24 : 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 24 : numeric_scale = DirectFunctionCall2(numeric_round,
1088 : numeric_scale,
1089 : Int32GetDatum(fpoint));
1090 :
1091 : /* Now we can safely divide ... */
1092 24 : quotient = DirectFunctionCall2(numeric_div, result, numeric_scale);
1093 :
1094 : /* ... and forcibly round to exactly the intended number of digits */
1095 24 : result = DirectFunctionCall2(numeric_round,
1096 : quotient,
1097 : Int32GetDatum(fpoint));
1098 : }
1099 :
1100 24 : PG_RETURN_DATUM(result);
1101 : }
1102 :
1103 : /* numeric_cash()
1104 : * Convert numeric to cash.
1105 : */
1106 : Datum
1107 12 : numeric_cash(PG_FUNCTION_ARGS)
1108 : {
1109 12 : Datum amount = PG_GETARG_DATUM(0);
1110 : Cash result;
1111 : int fpoint;
1112 : int64 scale;
1113 : int i;
1114 : Datum numeric_scale;
1115 12 : struct lconv *lconvert = PGLC_localeconv();
1116 :
1117 : /* see comments about frac_digits in cash_in() */
1118 12 : fpoint = lconvert->frac_digits;
1119 12 : if (fpoint < 0 || fpoint > 10)
1120 12 : fpoint = 2;
1121 :
1122 : /* compute required scale factor */
1123 12 : scale = 1;
1124 36 : for (i = 0; i < fpoint; i++)
1125 24 : scale *= 10;
1126 :
1127 : /* multiply the input amount by scale factor */
1128 12 : numeric_scale = NumericGetDatum(int64_to_numeric(scale));
1129 12 : amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
1130 :
1131 : /* note that numeric_int8 will round to nearest integer for us */
1132 12 : result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
1133 :
1134 12 : PG_RETURN_CASH(result);
1135 : }
1136 :
1137 : /* int4_cash()
1138 : * Convert int4 (int) to cash
1139 : */
1140 : Datum
1141 42 : int4_cash(PG_FUNCTION_ARGS)
1142 : {
1143 42 : int32 amount = PG_GETARG_INT32(0);
1144 : Cash result;
1145 : int fpoint;
1146 : int64 scale;
1147 : int i;
1148 42 : struct lconv *lconvert = PGLC_localeconv();
1149 :
1150 : /* see comments about frac_digits in cash_in() */
1151 42 : fpoint = lconvert->frac_digits;
1152 42 : if (fpoint < 0 || fpoint > 10)
1153 42 : fpoint = 2;
1154 :
1155 : /* compute required scale factor */
1156 42 : scale = 1;
1157 126 : for (i = 0; i < fpoint; i++)
1158 84 : scale *= 10;
1159 :
1160 : /* compute amount * scale, checking for overflow */
1161 42 : result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
1162 : Int64GetDatum(scale)));
1163 :
1164 42 : PG_RETURN_CASH(result);
1165 : }
1166 :
1167 : /* int8_cash()
1168 : * Convert int8 (bigint) to cash
1169 : */
1170 : Datum
1171 24 : int8_cash(PG_FUNCTION_ARGS)
1172 : {
1173 24 : int64 amount = PG_GETARG_INT64(0);
1174 : Cash result;
1175 : int fpoint;
1176 : int64 scale;
1177 : int i;
1178 24 : struct lconv *lconvert = PGLC_localeconv();
1179 :
1180 : /* see comments about frac_digits in cash_in() */
1181 24 : fpoint = lconvert->frac_digits;
1182 24 : if (fpoint < 0 || fpoint > 10)
1183 24 : fpoint = 2;
1184 :
1185 : /* compute required scale factor */
1186 24 : scale = 1;
1187 72 : for (i = 0; i < fpoint; i++)
1188 48 : scale *= 10;
1189 :
1190 : /* compute amount * scale, checking for overflow */
1191 24 : result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
1192 : Int64GetDatum(scale)));
1193 :
1194 24 : PG_RETURN_CASH(result);
1195 : }
|