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 "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
669 heikki.linnakangas@i 40 :CBC 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 : : };
2784 andres@anarazel.de 48 : 16 : const char *const *big = small + 18;
7087 darcy@druid.net 49 : 16 : int tu = value % 100;
50 : :
51 : : /* deal with the simple cases first */
52 [ + + ]: 16 : if (value <= 20)
53 : : {
669 heikki.linnakangas@i 54 : 4 : appendStringInfoString(buf, small[value]);
55 : 4 : return;
56 : : }
57 : :
58 : : /* is it an even multiple of 100? */
7087 darcy@druid.net 59 [ - + ]: 12 : if (!tu)
60 : : {
669 heikki.linnakangas@i 61 :UBC 0 : appendStringInfo(buf, "%s hundred", small[value / 100]);
62 : 0 : return;
63 : : }
64 : :
65 : : /* more than 99? */
7087 darcy@druid.net 66 [ + + ]:CBC 12 : if (value > 99)
67 : : {
68 : : /* is it an even multiple of 10 other than 10? */
69 [ - + - - ]: 8 : if (value % 10 == 0 && tu > 10)
669 heikki.linnakangas@i 70 :UBC 0 : appendStringInfo(buf, "%s hundred %s",
71 : 0 : small[value / 100], big[tu / 10]);
7087 darcy@druid.net 72 [ - + ]:CBC 8 : else if (tu < 20)
669 heikki.linnakangas@i 73 :UBC 0 : appendStringInfo(buf, "%s hundred and %s",
74 : 0 : small[value / 100], small[tu]);
75 : : else
669 heikki.linnakangas@i 76 :CBC 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? */
7087 darcy@druid.net 82 [ - + - - ]: 4 : if (value % 10 == 0 && tu > 10)
669 heikki.linnakangas@i 83 :UBC 0 : appendStringInfoString(buf, big[tu / 10]);
7087 darcy@druid.net 84 [ - + ]:CBC 4 : else if (tu < 20)
669 heikki.linnakangas@i 85 :UBC 0 : appendStringInfoString(buf, small[tu]);
86 : : else
669 heikki.linnakangas@i 87 :CBC 4 : appendStringInfo(buf, "%s %s", big[tu / 10], small[tu % 10]);
88 : : }
89 : : }
90 : :
91 : : static inline Cash
680 nathan@postgresql.or 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
9431 tgl@sss.pgh.pa.us 175 : 879 : cash_in(PG_FUNCTION_ARGS)
176 : : {
177 : 879 : char *str = PG_GETARG_CSTRING(0);
1263 178 : 879 : Node *escontext = fcinfo->context;
179 : : Cash result;
10491 bruce@momjian.us 180 : 879 : Cash value = 0;
181 : 879 : Cash dec = 0;
182 : 879 : Cash sgn = 1;
5327 tgl@sss.pgh.pa.us 183 : 879 : bool seen_dot = false;
10491 bruce@momjian.us 184 : 879 : const char *s = str;
185 : : int fpoint;
186 : : char dsymbol;
187 : : const char *ssymbol,
188 : : *psymbol,
189 : : *nsymbol,
190 : : *csymbol;
9317 tgl@sss.pgh.pa.us 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 : : */
9324 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 */
5327 208 [ - + ]: 879 : if (*lconvert->mon_decimal_point != '\0' &&
5327 tgl@sss.pgh.pa.us 209 [ # # ]:UBC 0 : lconvert->mon_decimal_point[1] == '\0')
210 : 0 : dsymbol = *lconvert->mon_decimal_point;
211 : : else
5327 tgl@sss.pgh.pa.us 212 :CBC 879 : dsymbol = '.';
213 [ - + ]: 879 : if (*lconvert->mon_thousands_sep != '\0')
5327 tgl@sss.pgh.pa.us 214 :UBC 0 : ssymbol = lconvert->mon_thousands_sep;
215 : : else /* ssymbol should not equal dsymbol */
5327 tgl@sss.pgh.pa.us 216 [ + - ]:CBC 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 */
9309 228 [ - + ]: 879 : while (isspace((unsigned char) *s))
10133 bruce@momjian.us 229 :UBC 0 : s++;
10133 bruce@momjian.us 230 [ + + ]:CBC 879 : if (strncmp(s, csymbol, strlen(csymbol)) == 0)
231 : 80 : s += strlen(csymbol);
5326 tgl@sss.pgh.pa.us 232 [ - + ]: 879 : while (isspace((unsigned char) *s))
5326 tgl@sss.pgh.pa.us 233 :UBC 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 */
10133 bruce@momjian.us 242 [ + + ]:CBC 879 : if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
243 : : {
10316 lockhart@fourpalms.o 244 : 323 : sgn = -1;
245 : 323 : s += strlen(nsymbol);
246 : : }
247 [ + + ]: 556 : else if (*s == '(')
248 : : {
10492 bruce@momjian.us 249 : 8 : sgn = -1;
250 : 8 : s++;
251 : : }
5327 tgl@sss.pgh.pa.us 252 [ - + ]: 548 : else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
5327 tgl@sss.pgh.pa.us 253 :UBC 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 */
9309 tgl@sss.pgh.pa.us 260 [ - + ]:CBC 879 : while (isspace((unsigned char) *s))
10133 bruce@momjian.us 261 :UBC 0 : s++;
10133 bruce@momjian.us 262 [ + + ]:CBC 879 : if (strncmp(s, csymbol, strlen(csymbol)) == 0)
263 : 4 : s += strlen(csymbol);
5326 tgl@sss.pgh.pa.us 264 [ - + ]: 879 : while (isspace((unsigned char) *s))
5326 tgl@sss.pgh.pa.us 265 :UBC 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 : :
5327 tgl@sss.pgh.pa.us 280 [ + + ]:CBC 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 : : */
6198 286 [ + + + + : 7895 : if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
+ + ]
10492 bruce@momjian.us 287 : 7064 : {
3091 andres@anarazel.de 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))
1263 tgl@sss.pgh.pa.us 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 : :
10492 bruce@momjian.us 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 : : {
5327 tgl@sss.pgh.pa.us 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
10492 bruce@momjian.us 309 : 44 : break;
310 : : }
311 : :
312 : : /* round off if there's another digit */
5327 tgl@sss.pgh.pa.us 313 [ + + + + ]: 867 : if (isdigit((unsigned char) *s) && *s >= '5')
314 : : {
315 : : /* remember we build the value in the negative */
3091 andres@anarazel.de 316 [ + + ]: 20 : if (pg_sub_s64_overflow(value, 1, &value))
1263 tgl@sss.pgh.pa.us 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 */
5327 324 [ + + ]: 1055 : for (; dec < fpoint; dec++)
325 : : {
3091 andres@anarazel.de 326 [ + + ]: 208 : if (pg_mul_s64_overflow(value, 10, &value))
1263 tgl@sss.pgh.pa.us 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 : : */
6897 337 [ + + ]: 871 : while (isdigit((unsigned char) *s))
338 : 24 : s++;
339 : :
5327 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 : : {
5327 tgl@sss.pgh.pa.us 346 :UBC 0 : sgn = -1;
347 : 0 : s += strlen(nsymbol);
348 : : }
5326 tgl@sss.pgh.pa.us 349 [ - + ]:CBC 8 : else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
5326 tgl@sss.pgh.pa.us 350 :UBC 0 : s += strlen(psymbol);
5326 tgl@sss.pgh.pa.us 351 [ - + ]:CBC 8 : else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
5326 tgl@sss.pgh.pa.us 352 :UBC 0 : s += strlen(csymbol);
353 : : else
1263 tgl@sss.pgh.pa.us 354 [ + + ]:CBC 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 : : */
3551 peter_e@gmx.net 364 [ + + ]: 839 : if (sgn > 0)
365 : : {
3091 andres@anarazel.de 366 [ + + ]: 524 : if (value == PG_INT64_MIN)
1263 tgl@sss.pgh.pa.us 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")));
3091 andres@anarazel.de 371 : 516 : result = -value;
372 : : }
373 : : else
3551 peter_e@gmx.net 374 : 315 : result = value;
375 : :
376 : : #ifdef CASHDEBUG
377 : : printf("cashin- result is " INT64_FORMAT "\n", result);
378 : : #endif
379 : :
9431 tgl@sss.pgh.pa.us 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;
9317 407 : 265 : struct lconv *lconvert = PGLC_localeconv();
408 : :
409 : : /* see comments about frac_digits in cash_in() */
9324 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 : : */
10466 lockhart@fourpalms.o 418 : 265 : mon_group = *lconvert->mon_grouping;
9324 tgl@sss.pgh.pa.us 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 */
5327 423 [ - + ]: 265 : if (*lconvert->mon_decimal_point != '\0' &&
5327 tgl@sss.pgh.pa.us 424 [ # # ]:UBC 0 : lconvert->mon_decimal_point[1] == '\0')
425 : 0 : dsymbol = *lconvert->mon_decimal_point;
426 : : else
5327 tgl@sss.pgh.pa.us 427 :CBC 265 : dsymbol = '.';
428 [ - + ]: 265 : if (*lconvert->mon_thousands_sep != '\0')
5327 tgl@sss.pgh.pa.us 429 :UBC 0 : ssymbol = lconvert->mon_thousands_sep;
430 : : else /* ssymbol should not equal dsymbol */
5327 tgl@sss.pgh.pa.us 431 [ + - ]:CBC 265 : ssymbol = (dsymbol != ',') ? "," : ".";
432 [ - + ]: 265 : csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
433 : :
10486 bruce@momjian.us 434 [ + + ]: 265 : if (value < 0)
435 : : {
436 : : /* set up formatting data */
5326 tgl@sss.pgh.pa.us 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 */
653 nathan@postgresql.or 451 : 265 : uvalue = pg_abs_s64(value);
452 : :
453 : : /* we build the digits+decimal-point+sep string right-to-left in buf[] */
5327 tgl@sss.pgh.pa.us 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 : : }
5326 471 [ + + + + ]: 1874 : else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
472 : : {
473 : : /* insert thousands sep, but only to left of radix point */
5327 474 : 351 : bufptr -= strlen(ssymbol);
475 : 351 : memcpy(bufptr, ssymbol, strlen(ssymbol));
476 : : }
477 : :
653 nathan@postgresql.or 478 : 2139 : *(--bufptr) = (uvalue % 10) + '0';
479 : 2139 : uvalue = uvalue / 10;
5327 tgl@sss.pgh.pa.us 480 : 2139 : digit_pos--;
653 nathan@postgresql.or 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 : : */
5326 tgl@sss.pgh.pa.us 507 [ - + - - : 265 : switch (sign_posn)
- ]
508 : : {
5326 tgl@sss.pgh.pa.us 509 :UBC 0 : case 0:
510 [ # # ]: 0 : if (cs_precedes)
4612 peter_e@gmx.net 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);
5326 tgl@sss.pgh.pa.us 520 : 0 : break;
5326 tgl@sss.pgh.pa.us 521 :CBC 265 : case 1:
522 : : default:
523 [ + - ]: 265 : if (cs_precedes)
4612 peter_e@gmx.net 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
4612 peter_e@gmx.net 531 [ # # # # ]:UBC 0 : result = psprintf("%s%s%s%s%s",
532 : : signsymbol,
533 : : (sep_by_space == 2) ? " " : "",
534 : : bufptr,
535 : : (sep_by_space == 1) ? " " : "",
536 : : csymbol);
5326 tgl@sss.pgh.pa.us 537 :CBC 265 : break;
5326 tgl@sss.pgh.pa.us 538 :UBC 0 : case 2:
539 [ # # ]: 0 : if (cs_precedes)
4612 peter_e@gmx.net 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);
5326 tgl@sss.pgh.pa.us 553 : 0 : break;
554 : 0 : case 3:
555 [ # # ]: 0 : if (cs_precedes)
4612 peter_e@gmx.net 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);
5326 tgl@sss.pgh.pa.us 569 : 0 : break;
570 : 0 : case 4:
571 [ # # ]: 0 : if (cs_precedes)
4612 peter_e@gmx.net 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);
5326 tgl@sss.pgh.pa.us 585 : 0 : break;
586 : : }
587 : :
9431 tgl@sss.pgh.pa.us 588 :CBC 265 : PG_RETURN_CSTRING(result);
589 : : }
590 : :
591 : : /*
592 : : * cash_recv - converts external binary format to cash
593 : : */
594 : : Datum
8418 tgl@sss.pgh.pa.us 595 :UBC 0 : cash_recv(PG_FUNCTION_ARGS)
596 : : {
597 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
598 : :
6857 599 : 0 : PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
600 : : }
601 : :
602 : : /*
603 : : * cash_send - converts cash to binary format
604 : : */
605 : : Datum
8418 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);
6857 612 : 0 : pq_sendint64(&buf, arg1);
8418 613 : 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
614 : : }
615 : :
616 : : /*
617 : : * Comparison functions
618 : : */
619 : :
620 : : Datum
9431 tgl@sss.pgh.pa.us 621 :CBC 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
8322 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
9431 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 : :
680 nathan@postgresql.or 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
9431 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
5797 tgl@sss.pgh.pa.us 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)
5797 tgl@sss.pgh.pa.us 729 [ # # ]:UBC 0 : ereport(ERROR,
730 : : (errcode(ERRCODE_DIVISION_BY_ZERO),
731 : : errmsg("division by zero")));
732 : :
5797 tgl@sss.pgh.pa.us 733 :CBC 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
9433 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 : :
680 nathan@postgresql.or 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
9433 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9433 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9433 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9433 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9433 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
7087 darcy@druid.net 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 : :
680 nathan@postgresql.or 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
7087 darcy@druid.net 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 : :
680 nathan@postgresql.or 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
7087 darcy@druid.net 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 : :
680 nathan@postgresql.or 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
9482 tgl@sss.pgh.pa.us 869 : 8 : cash_mul_int4(PG_FUNCTION_ARGS)
870 : : {
871 : 8 : Cash c = PG_GETARG_CASH(0);
6857 872 : 8 : int32 i = PG_GETARG_INT32(1);
873 : :
680 nathan@postgresql.or 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
9482 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9482 tgl@sss.pgh.pa.us 898 : 14 : cash_div_int4(PG_FUNCTION_ARGS)
899 : : {
900 : 14 : Cash c = PG_GETARG_CASH(0);
6857 901 : 14 : int32 i = PG_GETARG_INT32(1);
902 : :
680 nathan@postgresql.or 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
9490 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9490 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9490 tgl@sss.pgh.pa.us 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 : :
680 nathan@postgresql.or 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
9431 tgl@sss.pgh.pa.us 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
9458 bruce@momjian.us 985 : 8 : cash_words(PG_FUNCTION_ARGS)
986 : : {
9459 tgl@sss.pgh.pa.us 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 : :
669 heikki.linnakangas@i 1000 : 8 : initStringInfo(&buf);
1001 : :
1002 : : /* work with positive numbers */
9459 tgl@sss.pgh.pa.us 1003 [ - + ]: 8 : if (value < 0)
1004 : : {
9459 tgl@sss.pgh.pa.us 1005 :UBC 0 : value = -value;
669 heikki.linnakangas@i 1006 : 0 : appendStringInfoString(&buf, "minus ");
1007 : : }
1008 : :
1009 : : /* Now treat as unsigned, to avoid trouble at INT_MIN */
7087 darcy@druid.net 1010 :CBC 8 : val = (uint64) value;
1011 : :
669 heikki.linnakangas@i 1012 : 8 : dollars = val / INT64CONST(100);
6197 bruce@momjian.us 1013 : 8 : m0 = val % INT64CONST(100); /* cents */
3265 tgl@sss.pgh.pa.us 1014 : 8 : m1 = (val / INT64CONST(100)) % 1000; /* hundreds */
1015 : 8 : m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
6197 bruce@momjian.us 1016 : 8 : m3 = (val / INT64CONST(100000000)) % 1000; /* millions */
3265 tgl@sss.pgh.pa.us 1017 : 8 : m4 = (val / INT64CONST(100000000000)) % 1000; /* billions */
6564 1018 : 8 : m5 = (val / INT64CONST(100000000000000)) % 1000; /* trillions */
6197 bruce@momjian.us 1019 : 8 : m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
1020 : :
7087 darcy@druid.net 1021 [ - + ]: 8 : if (m6)
1022 : : {
669 heikki.linnakangas@i 1023 :UBC 0 : append_num_word(&buf, m6);
1024 : 0 : appendStringInfoString(&buf, " quadrillion ");
1025 : : }
1026 : :
7087 darcy@druid.net 1027 [ - + ]:CBC 8 : if (m5)
1028 : : {
669 heikki.linnakangas@i 1029 :UBC 0 : append_num_word(&buf, m5);
1030 : 0 : appendStringInfoString(&buf, " trillion ");
1031 : : }
1032 : :
7087 darcy@druid.net 1033 [ - + ]:CBC 8 : if (m4)
1034 : : {
669 heikki.linnakangas@i 1035 :UBC 0 : append_num_word(&buf, m4);
1036 : 0 : appendStringInfoString(&buf, " billion ");
1037 : : }
1038 : :
10492 bruce@momjian.us 1039 [ - + ]:CBC 8 : if (m3)
1040 : : {
669 heikki.linnakangas@i 1041 :UBC 0 : append_num_word(&buf, m3);
1042 : 0 : appendStringInfoString(&buf, " million ");
1043 : : }
1044 : :
10492 bruce@momjian.us 1045 [ - + ]:CBC 8 : if (m2)
1046 : : {
669 heikki.linnakangas@i 1047 :UBC 0 : append_num_word(&buf, m2);
1048 : 0 : appendStringInfoString(&buf, " thousand ");
1049 : : }
1050 : :
10492 bruce@momjian.us 1051 [ + - ]:CBC 8 : if (m1)
669 heikki.linnakangas@i 1052 : 8 : append_num_word(&buf, m1);
1053 : :
1054 [ - + ]: 8 : if (dollars == 0)
669 heikki.linnakangas@i 1055 :UBC 0 : appendStringInfoString(&buf, "zero");
1056 : :
669 heikki.linnakangas@i 1057 [ - + ]:CBC 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 */
180 jdavis@postgresql.or 1062 :GNC 8 : buf.data[0] = pg_ascii_toupper((unsigned char) buf.data[0]);
1063 : :
1064 : : /* return as text datum */
669 heikki.linnakangas@i 1065 :CBC 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
5797 tgl@sss.pgh.pa.us 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 */
2089 peter@eisentraut.org 1089 : 16 : result = NumericGetDatum(int64_to_numeric(money));
1090 : :
1091 : : /* scale appropriately, if needed */
2500 tgl@sss.pgh.pa.us 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;
2089 peter@eisentraut.org 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 : : */
2500 tgl@sss.pgh.pa.us 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
5797 1133 : 8 : numeric_cash(PG_FUNCTION_ARGS)
1134 : : {
61 peter@eisentraut.org 1135 :GNC 8 : Numeric amount = PG_GETARG_NUMERIC(0);
1136 : : Cash result;
1137 : : int fpoint;
1138 : : int64 scale;
1139 : : int i;
1140 : : Numeric numeric_scale;
5797 tgl@sss.pgh.pa.us 1141 :CBC 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 */
61 peter@eisentraut.org 1154 :GNC 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)))
- - - + ]
61 peter@eisentraut.org 1158 :UNC 0 : PG_RETURN_NULL();
1159 : :
1160 : : /* note that numeric_int8 will round to nearest integer for us */
61 peter@eisentraut.org 1161 :GNC 8 : result = numeric_int8_safe(amount, fcinfo->context);
1162 [ - + - - : 8 : if (unlikely(SOFT_ERROR_OCCURRED(fcinfo->context)))
- - - + ]
61 peter@eisentraut.org 1163 :UNC 0 : PG_RETURN_NULL();
1164 : :
5797 tgl@sss.pgh.pa.us 1165 :CBC 8 : PG_RETURN_CASH(result);
1166 : : }
1167 : :
1168 : : /*
1169 : : * int4_cash()
1170 : : * Convert int4 (int) to cash
1171 : : */
1172 : : Datum
5534 rhaas@postgresql.org 1173 : 28 : int4_cash(PG_FUNCTION_ARGS)
1174 : : {
5529 bruce@momjian.us 1175 : 28 : int32 amount = PG_GETARG_INT32(0);
1176 : : Cash result;
1177 : : int fpoint;
1178 : : int64 scale;
1179 : : int i;
5534 rhaas@postgresql.org 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 */
61 peter@eisentraut.org 1193 [ - + ]:GNC 28 : if (unlikely(pg_mul_s64_overflow(amount, scale, &result)))
61 peter@eisentraut.org 1194 [ # # ]:UNC 0 : ereturn(fcinfo->context, (Datum) 0,
1195 : : errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
1196 : : errmsg("bigint out of range"));
1197 : :
5534 rhaas@postgresql.org 1198 :CBC 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 : : {
5529 bruce@momjian.us 1208 : 16 : int64 amount = PG_GETARG_INT64(0);
1209 : : Cash result;
1210 : : int fpoint;
1211 : : int64 scale;
1212 : : int i;
5534 rhaas@postgresql.org 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 */
61 peter@eisentraut.org 1226 [ - + ]:GNC 16 : if (unlikely(pg_mul_s64_overflow(amount, scale, &result)))
61 peter@eisentraut.org 1227 [ # # ]:UNC 0 : ereturn(fcinfo->context, (Datum) 0,
1228 : : errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
1229 : : errmsg("bigint out of range"));
1230 : :
5534 rhaas@postgresql.org 1231 :CBC 16 : PG_RETURN_CASH(result);
1232 : : }
|