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