mirror of
https://github.com/postgres/postgres.git
synced 2025-08-09 17:03:00 +03:00
None of the arithmetic functions for the the money type handle overflow. This commit introduces several helper functions with overflow checking and makes use of them in the money type's arithmetic functions. Fixes bug #18240. Reported-by: Alexander Lakhin Author: Joseph Koshakow Discussion: https://postgr.es/m/18240-c5da758d7dc1ecf0%40postgresql.org Discussion: https://postgr.es/m/CAAvxfHdBPOyEGS7s%2Bxf4iaW0-cgiq25jpYdWBqQqvLtLe_t6tw%40mail.gmail.com Backpatch-through: 12
1191 lines
26 KiB
C
1191 lines
26 KiB
C
/*
|
|
* cash.c
|
|
* Written by D'Arcy J.M. Cain
|
|
* darcy@druid.net
|
|
* http://www.druid.net/darcy/
|
|
*
|
|
* Functions to allow input and output of money normally but store
|
|
* and handle it as 64 bit ints
|
|
*
|
|
* A slightly modified version of this file and a discussion of the
|
|
* workings can be found in the book "Software Solutions in C" by
|
|
* Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
|
|
* this version handles 64 bit numbers and so can hold values up to
|
|
* $92,233,720,368,547,758.07.
|
|
*
|
|
* src/backend/utils/adt/cash.c
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
|
|
#include "common/int.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/cash.h"
|
|
#include "utils/float.h"
|
|
#include "utils/numeric.h"
|
|
#include "utils/pg_locale.h"
|
|
|
|
|
|
/*************************************************************************
|
|
* Private routines
|
|
************************************************************************/
|
|
|
|
static const char *
|
|
num_word(Cash value)
|
|
{
|
|
static char buf[128];
|
|
static const char *const small[] = {
|
|
"zero", "one", "two", "three", "four", "five", "six", "seven",
|
|
"eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
|
|
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
|
|
"thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
|
|
};
|
|
const char *const *big = small + 18;
|
|
int tu = value % 100;
|
|
|
|
/* deal with the simple cases first */
|
|
if (value <= 20)
|
|
return small[value];
|
|
|
|
/* is it an even multiple of 100? */
|
|
if (!tu)
|
|
{
|
|
sprintf(buf, "%s hundred", small[value / 100]);
|
|
return buf;
|
|
}
|
|
|
|
/* more than 99? */
|
|
if (value > 99)
|
|
{
|
|
/* is it an even multiple of 10 other than 10? */
|
|
if (value % 10 == 0 && tu > 10)
|
|
sprintf(buf, "%s hundred %s",
|
|
small[value / 100], big[tu / 10]);
|
|
else if (tu < 20)
|
|
sprintf(buf, "%s hundred and %s",
|
|
small[value / 100], small[tu]);
|
|
else
|
|
sprintf(buf, "%s hundred %s %s",
|
|
small[value / 100], big[tu / 10], small[tu % 10]);
|
|
}
|
|
else
|
|
{
|
|
/* is it an even multiple of 10 other than 10? */
|
|
if (value % 10 == 0 && tu > 10)
|
|
sprintf(buf, "%s", big[tu / 10]);
|
|
else if (tu < 20)
|
|
sprintf(buf, "%s", small[tu]);
|
|
else
|
|
sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
|
|
}
|
|
|
|
return buf;
|
|
} /* num_word() */
|
|
|
|
static inline Cash
|
|
cash_pl_cash(Cash c1, Cash c2)
|
|
{
|
|
Cash res;
|
|
|
|
if (unlikely(pg_add_s64_overflow(c1, c2, &res)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("money out of range")));
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline Cash
|
|
cash_mi_cash(Cash c1, Cash c2)
|
|
{
|
|
Cash res;
|
|
|
|
if (unlikely(pg_sub_s64_overflow(c1, c2, &res)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("money out of range")));
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline Cash
|
|
cash_mul_float8(Cash c, float8 f)
|
|
{
|
|
float8 res = rint(float8_mul((float8) c, f));
|
|
|
|
if (unlikely(isnan(res) || !FLOAT8_FITS_IN_INT64(res)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("money out of range")));
|
|
|
|
return (Cash) res;
|
|
}
|
|
|
|
static inline Cash
|
|
cash_div_float8(Cash c, float8 f)
|
|
{
|
|
float8 res = rint(float8_div((float8) c, f));
|
|
|
|
if (unlikely(isnan(res) || !FLOAT8_FITS_IN_INT64(res)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("money out of range")));
|
|
|
|
return (Cash) res;
|
|
}
|
|
|
|
static inline Cash
|
|
cash_mul_int64(Cash c, int64 i)
|
|
{
|
|
Cash res;
|
|
|
|
if (unlikely(pg_mul_s64_overflow(c, i, &res)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("money out of range")));
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline Cash
|
|
cash_div_int64(Cash c, int64 i)
|
|
{
|
|
if (unlikely(i == 0))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DIVISION_BY_ZERO),
|
|
errmsg("division by zero")));
|
|
|
|
return c / i;
|
|
}
|
|
|
|
/* cash_in()
|
|
* Convert a string to a cash data type.
|
|
* Format is [$]###[,]###[.##]
|
|
* Examples: 123.45 $123.45 $123,456.78
|
|
*
|
|
*/
|
|
Datum
|
|
cash_in(PG_FUNCTION_ARGS)
|
|
{
|
|
char *str = PG_GETARG_CSTRING(0);
|
|
Node *escontext = fcinfo->context;
|
|
Cash result;
|
|
Cash value = 0;
|
|
Cash dec = 0;
|
|
Cash sgn = 1;
|
|
bool seen_dot = false;
|
|
const char *s = str;
|
|
int fpoint;
|
|
char dsymbol;
|
|
const char *ssymbol,
|
|
*psymbol,
|
|
*nsymbol,
|
|
*csymbol;
|
|
struct lconv *lconvert = PGLC_localeconv();
|
|
|
|
/*
|
|
* frac_digits will be CHAR_MAX in some locales, notably C. However, just
|
|
* testing for == CHAR_MAX is risky, because of compilers like gcc that
|
|
* "helpfully" let you alter the platform-standard definition of whether
|
|
* char is signed or not. If we are so unfortunate as to get compiled
|
|
* with a nonstandard -fsigned-char or -funsigned-char switch, then our
|
|
* idea of CHAR_MAX will not agree with libc's. The safest course is not
|
|
* to test for CHAR_MAX at all, but to impose a range check for plausible
|
|
* frac_digits values.
|
|
*/
|
|
fpoint = lconvert->frac_digits;
|
|
if (fpoint < 0 || fpoint > 10)
|
|
fpoint = 2; /* best guess in this case, I think */
|
|
|
|
/* we restrict dsymbol to be a single byte, but not the other symbols */
|
|
if (*lconvert->mon_decimal_point != '\0' &&
|
|
lconvert->mon_decimal_point[1] == '\0')
|
|
dsymbol = *lconvert->mon_decimal_point;
|
|
else
|
|
dsymbol = '.';
|
|
if (*lconvert->mon_thousands_sep != '\0')
|
|
ssymbol = lconvert->mon_thousands_sep;
|
|
else /* ssymbol should not equal dsymbol */
|
|
ssymbol = (dsymbol != ',') ? "," : ".";
|
|
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
|
|
psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
|
|
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
|
|
|
|
#ifdef CASHDEBUG
|
|
printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
|
|
fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
|
|
#endif
|
|
|
|
/* we need to add all sorts of checking here. For now just */
|
|
/* strip all leading whitespace and any leading currency symbol */
|
|
while (isspace((unsigned char) *s))
|
|
s++;
|
|
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
|
|
s += strlen(csymbol);
|
|
while (isspace((unsigned char) *s))
|
|
s++;
|
|
|
|
#ifdef CASHDEBUG
|
|
printf("cashin- string is '%s'\n", s);
|
|
#endif
|
|
|
|
/* a leading minus or paren signifies a negative number */
|
|
/* again, better heuristics needed */
|
|
/* XXX - doesn't properly check for balanced parens - djmc */
|
|
if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
|
|
{
|
|
sgn = -1;
|
|
s += strlen(nsymbol);
|
|
}
|
|
else if (*s == '(')
|
|
{
|
|
sgn = -1;
|
|
s++;
|
|
}
|
|
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
|
|
s += strlen(psymbol);
|
|
|
|
#ifdef CASHDEBUG
|
|
printf("cashin- string is '%s'\n", s);
|
|
#endif
|
|
|
|
/* allow whitespace and currency symbol after the sign, too */
|
|
while (isspace((unsigned char) *s))
|
|
s++;
|
|
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
|
|
s += strlen(csymbol);
|
|
while (isspace((unsigned char) *s))
|
|
s++;
|
|
|
|
#ifdef CASHDEBUG
|
|
printf("cashin- string is '%s'\n", s);
|
|
#endif
|
|
|
|
/*
|
|
* We accumulate the absolute amount in "value" and then apply the sign at
|
|
* the end. (The sign can appear before or after the digits, so it would
|
|
* be more complicated to do otherwise.) Because of the larger range of
|
|
* negative signed integers, we build "value" in the negative and then
|
|
* flip the sign at the end, catching most-negative-number overflow if
|
|
* necessary.
|
|
*/
|
|
|
|
for (; *s; s++)
|
|
{
|
|
/*
|
|
* We look for digits as long as we have found less than the required
|
|
* number of decimal places.
|
|
*/
|
|
if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
|
|
{
|
|
int8 digit = *s - '0';
|
|
|
|
if (pg_mul_s64_overflow(value, 10, &value) ||
|
|
pg_sub_s64_overflow(value, digit, &value))
|
|
ereturn(escontext, (Datum) 0,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value \"%s\" is out of range for type %s",
|
|
str, "money")));
|
|
|
|
if (seen_dot)
|
|
dec++;
|
|
}
|
|
/* decimal point? then start counting fractions... */
|
|
else if (*s == dsymbol && !seen_dot)
|
|
{
|
|
seen_dot = true;
|
|
}
|
|
/* ignore if "thousands" separator, else we're done */
|
|
else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
|
|
s += strlen(ssymbol) - 1;
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* round off if there's another digit */
|
|
if (isdigit((unsigned char) *s) && *s >= '5')
|
|
{
|
|
/* remember we build the value in the negative */
|
|
if (pg_sub_s64_overflow(value, 1, &value))
|
|
ereturn(escontext, (Datum) 0,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value \"%s\" is out of range for type %s",
|
|
str, "money")));
|
|
}
|
|
|
|
/* adjust for less than required decimal places */
|
|
for (; dec < fpoint; dec++)
|
|
{
|
|
if (pg_mul_s64_overflow(value, 10, &value))
|
|
ereturn(escontext, (Datum) 0,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value \"%s\" is out of range for type %s",
|
|
str, "money")));
|
|
}
|
|
|
|
/*
|
|
* should only be trailing digits followed by whitespace, right paren,
|
|
* trailing sign, and/or trailing currency symbol
|
|
*/
|
|
while (isdigit((unsigned char) *s))
|
|
s++;
|
|
|
|
while (*s)
|
|
{
|
|
if (isspace((unsigned char) *s) || *s == ')')
|
|
s++;
|
|
else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
|
|
{
|
|
sgn = -1;
|
|
s += strlen(nsymbol);
|
|
}
|
|
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
|
|
s += strlen(psymbol);
|
|
else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
|
|
s += strlen(csymbol);
|
|
else
|
|
ereturn(escontext, (Datum) 0,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"money", str)));
|
|
}
|
|
|
|
/*
|
|
* If the value is supposed to be positive, flip the sign, but check for
|
|
* the most negative number.
|
|
*/
|
|
if (sgn > 0)
|
|
{
|
|
if (value == PG_INT64_MIN)
|
|
ereturn(escontext, (Datum) 0,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value \"%s\" is out of range for type %s",
|
|
str, "money")));
|
|
result = -value;
|
|
}
|
|
else
|
|
result = value;
|
|
|
|
#ifdef CASHDEBUG
|
|
printf("cashin- result is " INT64_FORMAT "\n", result);
|
|
#endif
|
|
|
|
PG_RETURN_CASH(result);
|
|
}
|
|
|
|
|
|
/* cash_out()
|
|
* Function to convert cash to a dollars and cents representation, using
|
|
* the lc_monetary locale's formatting.
|
|
*/
|
|
Datum
|
|
cash_out(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash value = PG_GETARG_CASH(0);
|
|
char *result;
|
|
char buf[128];
|
|
char *bufptr;
|
|
int digit_pos;
|
|
int points,
|
|
mon_group;
|
|
char dsymbol;
|
|
const char *ssymbol,
|
|
*csymbol,
|
|
*signsymbol;
|
|
char sign_posn,
|
|
cs_precedes,
|
|
sep_by_space;
|
|
struct lconv *lconvert = PGLC_localeconv();
|
|
|
|
/* see comments about frac_digits in cash_in() */
|
|
points = lconvert->frac_digits;
|
|
if (points < 0 || points > 10)
|
|
points = 2; /* best guess in this case, I think */
|
|
|
|
/*
|
|
* As with frac_digits, must apply a range check to mon_grouping to avoid
|
|
* being fooled by variant CHAR_MAX values.
|
|
*/
|
|
mon_group = *lconvert->mon_grouping;
|
|
if (mon_group <= 0 || mon_group > 6)
|
|
mon_group = 3;
|
|
|
|
/* we restrict dsymbol to be a single byte, but not the other symbols */
|
|
if (*lconvert->mon_decimal_point != '\0' &&
|
|
lconvert->mon_decimal_point[1] == '\0')
|
|
dsymbol = *lconvert->mon_decimal_point;
|
|
else
|
|
dsymbol = '.';
|
|
if (*lconvert->mon_thousands_sep != '\0')
|
|
ssymbol = lconvert->mon_thousands_sep;
|
|
else /* ssymbol should not equal dsymbol */
|
|
ssymbol = (dsymbol != ',') ? "," : ".";
|
|
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
|
|
|
|
if (value < 0)
|
|
{
|
|
/* make the amount positive for digit-reconstruction loop */
|
|
value = -value;
|
|
/* set up formatting data */
|
|
signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
|
|
sign_posn = lconvert->n_sign_posn;
|
|
cs_precedes = lconvert->n_cs_precedes;
|
|
sep_by_space = lconvert->n_sep_by_space;
|
|
}
|
|
else
|
|
{
|
|
signsymbol = lconvert->positive_sign;
|
|
sign_posn = lconvert->p_sign_posn;
|
|
cs_precedes = lconvert->p_cs_precedes;
|
|
sep_by_space = lconvert->p_sep_by_space;
|
|
}
|
|
|
|
/* we build the digits+decimal-point+sep string right-to-left in buf[] */
|
|
bufptr = buf + sizeof(buf) - 1;
|
|
*bufptr = '\0';
|
|
|
|
/*
|
|
* Generate digits till there are no non-zero digits left and we emitted
|
|
* at least one to the left of the decimal point. digit_pos is the
|
|
* current digit position, with zero as the digit just left of the decimal
|
|
* point, increasing to the right.
|
|
*/
|
|
digit_pos = points;
|
|
do
|
|
{
|
|
if (points && digit_pos == 0)
|
|
{
|
|
/* insert decimal point, but not if value cannot be fractional */
|
|
*(--bufptr) = dsymbol;
|
|
}
|
|
else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
|
|
{
|
|
/* insert thousands sep, but only to left of radix point */
|
|
bufptr -= strlen(ssymbol);
|
|
memcpy(bufptr, ssymbol, strlen(ssymbol));
|
|
}
|
|
|
|
*(--bufptr) = ((uint64) value % 10) + '0';
|
|
value = ((uint64) value) / 10;
|
|
digit_pos--;
|
|
} while (value || digit_pos >= 0);
|
|
|
|
/*----------
|
|
* Now, attach currency symbol and sign symbol in the correct order.
|
|
*
|
|
* The POSIX spec defines these values controlling this code:
|
|
*
|
|
* p/n_sign_posn:
|
|
* 0 Parentheses enclose the quantity and the currency_symbol.
|
|
* 1 The sign string precedes the quantity and the currency_symbol.
|
|
* 2 The sign string succeeds the quantity and the currency_symbol.
|
|
* 3 The sign string precedes the currency_symbol.
|
|
* 4 The sign string succeeds the currency_symbol.
|
|
*
|
|
* p/n_cs_precedes: 0 means currency symbol after value, else before it.
|
|
*
|
|
* p/n_sep_by_space:
|
|
* 0 No <space> separates the currency symbol and value.
|
|
* 1 If the currency symbol and sign string are adjacent, a <space>
|
|
* separates them from the value; otherwise, a <space> separates
|
|
* the currency symbol from the value.
|
|
* 2 If the currency symbol and sign string are adjacent, a <space>
|
|
* separates them; otherwise, a <space> separates the sign string
|
|
* from the value.
|
|
*----------
|
|
*/
|
|
switch (sign_posn)
|
|
{
|
|
case 0:
|
|
if (cs_precedes)
|
|
result = psprintf("(%s%s%s)",
|
|
csymbol,
|
|
(sep_by_space == 1) ? " " : "",
|
|
bufptr);
|
|
else
|
|
result = psprintf("(%s%s%s)",
|
|
bufptr,
|
|
(sep_by_space == 1) ? " " : "",
|
|
csymbol);
|
|
break;
|
|
case 1:
|
|
default:
|
|
if (cs_precedes)
|
|
result = psprintf("%s%s%s%s%s",
|
|
signsymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
csymbol,
|
|
(sep_by_space == 1) ? " " : "",
|
|
bufptr);
|
|
else
|
|
result = psprintf("%s%s%s%s%s",
|
|
signsymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
bufptr,
|
|
(sep_by_space == 1) ? " " : "",
|
|
csymbol);
|
|
break;
|
|
case 2:
|
|
if (cs_precedes)
|
|
result = psprintf("%s%s%s%s%s",
|
|
csymbol,
|
|
(sep_by_space == 1) ? " " : "",
|
|
bufptr,
|
|
(sep_by_space == 2) ? " " : "",
|
|
signsymbol);
|
|
else
|
|
result = psprintf("%s%s%s%s%s",
|
|
bufptr,
|
|
(sep_by_space == 1) ? " " : "",
|
|
csymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
signsymbol);
|
|
break;
|
|
case 3:
|
|
if (cs_precedes)
|
|
result = psprintf("%s%s%s%s%s",
|
|
signsymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
csymbol,
|
|
(sep_by_space == 1) ? " " : "",
|
|
bufptr);
|
|
else
|
|
result = psprintf("%s%s%s%s%s",
|
|
bufptr,
|
|
(sep_by_space == 1) ? " " : "",
|
|
signsymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
csymbol);
|
|
break;
|
|
case 4:
|
|
if (cs_precedes)
|
|
result = psprintf("%s%s%s%s%s",
|
|
csymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
signsymbol,
|
|
(sep_by_space == 1) ? " " : "",
|
|
bufptr);
|
|
else
|
|
result = psprintf("%s%s%s%s%s",
|
|
bufptr,
|
|
(sep_by_space == 1) ? " " : "",
|
|
csymbol,
|
|
(sep_by_space == 2) ? " " : "",
|
|
signsymbol);
|
|
break;
|
|
}
|
|
|
|
PG_RETURN_CSTRING(result);
|
|
}
|
|
|
|
/*
|
|
* cash_recv - converts external binary format to cash
|
|
*/
|
|
Datum
|
|
cash_recv(PG_FUNCTION_ARGS)
|
|
{
|
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
|
|
|
PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
|
|
}
|
|
|
|
/*
|
|
* cash_send - converts cash to binary format
|
|
*/
|
|
Datum
|
|
cash_send(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash arg1 = PG_GETARG_CASH(0);
|
|
StringInfoData buf;
|
|
|
|
pq_begintypsend(&buf);
|
|
pq_sendint64(&buf, arg1);
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
}
|
|
|
|
/*
|
|
* Comparison functions
|
|
*/
|
|
|
|
Datum
|
|
cash_eq(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_BOOL(c1 == c2);
|
|
}
|
|
|
|
Datum
|
|
cash_ne(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_BOOL(c1 != c2);
|
|
}
|
|
|
|
Datum
|
|
cash_lt(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_BOOL(c1 < c2);
|
|
}
|
|
|
|
Datum
|
|
cash_le(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_BOOL(c1 <= c2);
|
|
}
|
|
|
|
Datum
|
|
cash_gt(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_BOOL(c1 > c2);
|
|
}
|
|
|
|
Datum
|
|
cash_ge(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_BOOL(c1 >= c2);
|
|
}
|
|
|
|
Datum
|
|
cash_cmp(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
if (c1 > c2)
|
|
PG_RETURN_INT32(1);
|
|
else if (c1 == c2)
|
|
PG_RETURN_INT32(0);
|
|
else
|
|
PG_RETURN_INT32(-1);
|
|
}
|
|
|
|
|
|
/* cash_pl()
|
|
* Add two cash values.
|
|
*/
|
|
Datum
|
|
cash_pl(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_pl_cash(c1, c2));
|
|
}
|
|
|
|
|
|
/* cash_mi()
|
|
* Subtract two cash values.
|
|
*/
|
|
Datum
|
|
cash_mi(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_mi_cash(c1, c2));
|
|
}
|
|
|
|
|
|
/* cash_div_cash()
|
|
* Divide cash by cash, returning float8.
|
|
*/
|
|
Datum
|
|
cash_div_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash dividend = PG_GETARG_CASH(0);
|
|
Cash divisor = PG_GETARG_CASH(1);
|
|
float8 quotient;
|
|
|
|
if (divisor == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DIVISION_BY_ZERO),
|
|
errmsg("division by zero")));
|
|
|
|
quotient = (float8) dividend / (float8) divisor;
|
|
PG_RETURN_FLOAT8(quotient);
|
|
}
|
|
|
|
|
|
/* cash_mul_flt8()
|
|
* Multiply cash by float8.
|
|
*/
|
|
Datum
|
|
cash_mul_flt8(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
float8 f = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_float8(c, f));
|
|
}
|
|
|
|
|
|
/* flt8_mul_cash()
|
|
* Multiply float8 by cash.
|
|
*/
|
|
Datum
|
|
flt8_mul_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 f = PG_GETARG_FLOAT8(0);
|
|
Cash c = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_float8(c, f));
|
|
}
|
|
|
|
|
|
/* cash_div_flt8()
|
|
* Divide cash by float8.
|
|
*/
|
|
Datum
|
|
cash_div_flt8(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
float8 f = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_CASH(cash_div_float8(c, f));
|
|
}
|
|
|
|
|
|
/* cash_mul_flt4()
|
|
* Multiply cash by float4.
|
|
*/
|
|
Datum
|
|
cash_mul_flt4(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
float4 f = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_float8(c, (float8) f));
|
|
}
|
|
|
|
|
|
/* flt4_mul_cash()
|
|
* Multiply float4 by cash.
|
|
*/
|
|
Datum
|
|
flt4_mul_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 f = PG_GETARG_FLOAT4(0);
|
|
Cash c = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_float8(c, (float8) f));
|
|
}
|
|
|
|
|
|
/* cash_div_flt4()
|
|
* Divide cash by float4.
|
|
*
|
|
*/
|
|
Datum
|
|
cash_div_flt4(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
float4 f = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_CASH(cash_div_float8(c, (float8) f));
|
|
}
|
|
|
|
|
|
/* cash_mul_int8()
|
|
* Multiply cash by int8.
|
|
*/
|
|
Datum
|
|
cash_mul_int8(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int64 i = PG_GETARG_INT64(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_int64(c, i));
|
|
}
|
|
|
|
|
|
/* int8_mul_cash()
|
|
* Multiply int8 by cash.
|
|
*/
|
|
Datum
|
|
int8_mul_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
int64 i = PG_GETARG_INT64(0);
|
|
Cash c = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_int64(c, i));
|
|
}
|
|
|
|
/* cash_div_int8()
|
|
* Divide cash by 8-byte integer.
|
|
*/
|
|
Datum
|
|
cash_div_int8(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int64 i = PG_GETARG_INT64(1);
|
|
|
|
PG_RETURN_CASH(cash_div_int64(c, i));
|
|
}
|
|
|
|
|
|
/* cash_mul_int4()
|
|
* Multiply cash by int4.
|
|
*/
|
|
Datum
|
|
cash_mul_int4(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int32 i = PG_GETARG_INT32(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_int64(c, (int64) i));
|
|
}
|
|
|
|
|
|
/* int4_mul_cash()
|
|
* Multiply int4 by cash.
|
|
*/
|
|
Datum
|
|
int4_mul_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 i = PG_GETARG_INT32(0);
|
|
Cash c = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_int64(c, (int64) i));
|
|
}
|
|
|
|
|
|
/* cash_div_int4()
|
|
* Divide cash by 4-byte integer.
|
|
*
|
|
*/
|
|
Datum
|
|
cash_div_int4(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int32 i = PG_GETARG_INT32(1);
|
|
|
|
PG_RETURN_CASH(cash_div_int64(c, (int64) i));
|
|
}
|
|
|
|
|
|
/* cash_mul_int2()
|
|
* Multiply cash by int2.
|
|
*/
|
|
Datum
|
|
cash_mul_int2(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int16 s = PG_GETARG_INT16(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
|
|
}
|
|
|
|
/* int2_mul_cash()
|
|
* Multiply int2 by cash.
|
|
*/
|
|
Datum
|
|
int2_mul_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
int16 s = PG_GETARG_INT16(0);
|
|
Cash c = PG_GETARG_CASH(1);
|
|
|
|
PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
|
|
}
|
|
|
|
/* cash_div_int2()
|
|
* Divide cash by int2.
|
|
*
|
|
*/
|
|
Datum
|
|
cash_div_int2(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int16 s = PG_GETARG_INT16(1);
|
|
|
|
PG_RETURN_CASH(cash_div_int64(c, (int64) s));
|
|
}
|
|
|
|
/* cashlarger()
|
|
* Return larger of two cash values.
|
|
*/
|
|
Datum
|
|
cashlarger(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
Cash result;
|
|
|
|
result = (c1 > c2) ? c1 : c2;
|
|
|
|
PG_RETURN_CASH(result);
|
|
}
|
|
|
|
/* cashsmaller()
|
|
* Return smaller of two cash values.
|
|
*/
|
|
Datum
|
|
cashsmaller(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c1 = PG_GETARG_CASH(0);
|
|
Cash c2 = PG_GETARG_CASH(1);
|
|
Cash result;
|
|
|
|
result = (c1 < c2) ? c1 : c2;
|
|
|
|
PG_RETURN_CASH(result);
|
|
}
|
|
|
|
/* cash_words()
|
|
* This converts an int4 as well but to a representation using words
|
|
* Obviously way North American centric - sorry
|
|
*/
|
|
Datum
|
|
cash_words(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash value = PG_GETARG_CASH(0);
|
|
uint64 val;
|
|
char buf[256];
|
|
char *p = buf;
|
|
Cash m0;
|
|
Cash m1;
|
|
Cash m2;
|
|
Cash m3;
|
|
Cash m4;
|
|
Cash m5;
|
|
Cash m6;
|
|
|
|
/* work with positive numbers */
|
|
if (value < 0)
|
|
{
|
|
value = -value;
|
|
strcpy(buf, "minus ");
|
|
p += 6;
|
|
}
|
|
else
|
|
buf[0] = '\0';
|
|
|
|
/* Now treat as unsigned, to avoid trouble at INT_MIN */
|
|
val = (uint64) value;
|
|
|
|
m0 = val % INT64CONST(100); /* cents */
|
|
m1 = (val / INT64CONST(100)) % 1000; /* hundreds */
|
|
m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
|
|
m3 = (val / INT64CONST(100000000)) % 1000; /* millions */
|
|
m4 = (val / INT64CONST(100000000000)) % 1000; /* billions */
|
|
m5 = (val / INT64CONST(100000000000000)) % 1000; /* trillions */
|
|
m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
|
|
|
|
if (m6)
|
|
{
|
|
strcat(buf, num_word(m6));
|
|
strcat(buf, " quadrillion ");
|
|
}
|
|
|
|
if (m5)
|
|
{
|
|
strcat(buf, num_word(m5));
|
|
strcat(buf, " trillion ");
|
|
}
|
|
|
|
if (m4)
|
|
{
|
|
strcat(buf, num_word(m4));
|
|
strcat(buf, " billion ");
|
|
}
|
|
|
|
if (m3)
|
|
{
|
|
strcat(buf, num_word(m3));
|
|
strcat(buf, " million ");
|
|
}
|
|
|
|
if (m2)
|
|
{
|
|
strcat(buf, num_word(m2));
|
|
strcat(buf, " thousand ");
|
|
}
|
|
|
|
if (m1)
|
|
strcat(buf, num_word(m1));
|
|
|
|
if (!*p)
|
|
strcat(buf, "zero");
|
|
|
|
strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
|
|
strcat(buf, num_word(m0));
|
|
strcat(buf, m0 == 1 ? " cent" : " cents");
|
|
|
|
/* capitalize output */
|
|
buf[0] = pg_toupper((unsigned char) buf[0]);
|
|
|
|
/* return as text datum */
|
|
PG_RETURN_TEXT_P(cstring_to_text(buf));
|
|
}
|
|
|
|
|
|
/* cash_numeric()
|
|
* Convert cash to numeric.
|
|
*/
|
|
Datum
|
|
cash_numeric(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash money = PG_GETARG_CASH(0);
|
|
Datum result;
|
|
int fpoint;
|
|
struct lconv *lconvert = PGLC_localeconv();
|
|
|
|
/* see comments about frac_digits in cash_in() */
|
|
fpoint = lconvert->frac_digits;
|
|
if (fpoint < 0 || fpoint > 10)
|
|
fpoint = 2;
|
|
|
|
/* convert the integral money value to numeric */
|
|
result = NumericGetDatum(int64_to_numeric(money));
|
|
|
|
/* scale appropriately, if needed */
|
|
if (fpoint > 0)
|
|
{
|
|
int64 scale;
|
|
int i;
|
|
Datum numeric_scale;
|
|
Datum quotient;
|
|
|
|
/* compute required scale factor */
|
|
scale = 1;
|
|
for (i = 0; i < fpoint; i++)
|
|
scale *= 10;
|
|
numeric_scale = NumericGetDatum(int64_to_numeric(scale));
|
|
|
|
/*
|
|
* Given integral inputs approaching INT64_MAX, select_div_scale()
|
|
* might choose a result scale of zero, causing loss of fractional
|
|
* digits in the quotient. We can ensure an exact result by setting
|
|
* the dscale of either input to be at least as large as the desired
|
|
* result scale. numeric_round() will do that for us.
|
|
*/
|
|
numeric_scale = DirectFunctionCall2(numeric_round,
|
|
numeric_scale,
|
|
Int32GetDatum(fpoint));
|
|
|
|
/* Now we can safely divide ... */
|
|
quotient = DirectFunctionCall2(numeric_div, result, numeric_scale);
|
|
|
|
/* ... and forcibly round to exactly the intended number of digits */
|
|
result = DirectFunctionCall2(numeric_round,
|
|
quotient,
|
|
Int32GetDatum(fpoint));
|
|
}
|
|
|
|
PG_RETURN_DATUM(result);
|
|
}
|
|
|
|
/* numeric_cash()
|
|
* Convert numeric to cash.
|
|
*/
|
|
Datum
|
|
numeric_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
Datum amount = PG_GETARG_DATUM(0);
|
|
Cash result;
|
|
int fpoint;
|
|
int64 scale;
|
|
int i;
|
|
Datum numeric_scale;
|
|
struct lconv *lconvert = PGLC_localeconv();
|
|
|
|
/* see comments about frac_digits in cash_in() */
|
|
fpoint = lconvert->frac_digits;
|
|
if (fpoint < 0 || fpoint > 10)
|
|
fpoint = 2;
|
|
|
|
/* compute required scale factor */
|
|
scale = 1;
|
|
for (i = 0; i < fpoint; i++)
|
|
scale *= 10;
|
|
|
|
/* multiply the input amount by scale factor */
|
|
numeric_scale = NumericGetDatum(int64_to_numeric(scale));
|
|
amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
|
|
|
|
/* note that numeric_int8 will round to nearest integer for us */
|
|
result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
|
|
|
|
PG_RETURN_CASH(result);
|
|
}
|
|
|
|
/* int4_cash()
|
|
* Convert int4 (int) to cash
|
|
*/
|
|
Datum
|
|
int4_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 amount = PG_GETARG_INT32(0);
|
|
Cash result;
|
|
int fpoint;
|
|
int64 scale;
|
|
int i;
|
|
struct lconv *lconvert = PGLC_localeconv();
|
|
|
|
/* see comments about frac_digits in cash_in() */
|
|
fpoint = lconvert->frac_digits;
|
|
if (fpoint < 0 || fpoint > 10)
|
|
fpoint = 2;
|
|
|
|
/* compute required scale factor */
|
|
scale = 1;
|
|
for (i = 0; i < fpoint; i++)
|
|
scale *= 10;
|
|
|
|
/* compute amount * scale, checking for overflow */
|
|
result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
|
|
Int64GetDatum(scale)));
|
|
|
|
PG_RETURN_CASH(result);
|
|
}
|
|
|
|
/* int8_cash()
|
|
* Convert int8 (bigint) to cash
|
|
*/
|
|
Datum
|
|
int8_cash(PG_FUNCTION_ARGS)
|
|
{
|
|
int64 amount = PG_GETARG_INT64(0);
|
|
Cash result;
|
|
int fpoint;
|
|
int64 scale;
|
|
int i;
|
|
struct lconv *lconvert = PGLC_localeconv();
|
|
|
|
/* see comments about frac_digits in cash_in() */
|
|
fpoint = lconvert->frac_digits;
|
|
if (fpoint < 0 || fpoint > 10)
|
|
fpoint = 2;
|
|
|
|
/* compute required scale factor */
|
|
scale = 1;
|
|
for (i = 0; i < fpoint; i++)
|
|
scale *= 10;
|
|
|
|
/* compute amount * scale, checking for overflow */
|
|
result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
|
|
Int64GetDatum(scale)));
|
|
|
|
PG_RETURN_CASH(result);
|
|
}
|