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