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