Coverage Report

Created: 2025-10-09 06:07

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