postgresql/src/backend/utils/adt/cash.c
Peter Eisentraut 656df624c0 Add overflow checks to money type input function
The money type input function did not have any overflow checks at all.
There were some regression tests that purported to check for overflow,
but they actually checked for the overflow behavior of the int8 type
before casting to money.  Remove those unnecessary checks and add some
that actually check the money input function.

Reviewed-by: Fabien COELHO <coelho@cri.ensmp.fr>
2016-09-14 12:00:00 -05:00

1160 lines
25 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 <locale.h>
#include "libpq/pqformat.h"
#include "utils/builtins.h"
#include "utils/cash.h"
#include "utils/int8.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 *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 **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() */
/* 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);
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))
{
Cash newvalue = (value * 10) - (*s - '0');
if (newvalue / 10 != value)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type money",
str)));
value = newvalue;
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')
value--; /* remember we build the value in the negative */
if (value > 0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type money",
str)));
/* adjust for less than required decimal places */
for (; dec < fpoint; dec++)
{
Cash newvalue = value * 10;
if (newvalue / 10 != value)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type money",
str)));
value = newvalue;
}
/*
* 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
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type money: \"%s\"",
str)));
}
/* If the value is supposed to be positive, flip the sign, but check for
* the most negative number. */
if (sgn > 0)
{
result = -value;
if (result < 0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type money",
str)));
}
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);
Cash result;
result = c1 + c2;
PG_RETURN_CASH(result);
}
/* cash_mi()
* Subtract two cash values.
*/
Datum
cash_mi(PG_FUNCTION_ARGS)
{
Cash c1 = PG_GETARG_CASH(0);
Cash c2 = PG_GETARG_CASH(1);
Cash result;
result = c1 - c2;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = c * f;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = f * c;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
if (f == 0.0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / f);
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = c * f;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = f * c;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
if (f == 0.0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / f);
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = c * i;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = i * c;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
if (i == 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / i);
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = c * i;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = i * c;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
if (i == 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / i);
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = c * s;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
result = s * c;
PG_RETURN_CASH(result);
}
/* 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);
Cash result;
if (s == 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / s);
PG_RETURN_CASH(result);
}
/* 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);
Numeric result;
int fpoint;
int64 scale;
int i;
Datum amount;
Datum numeric_scale;
Datum quotient;
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;
/* form the result as money / scale */
amount = DirectFunctionCall1(int8_numeric, Int64GetDatum(money));
numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale));
quotient = DirectFunctionCall2(numeric_div, amount, numeric_scale);
/* forcibly round to exactly the intended number of digits */
result = DatumGetNumeric(DirectFunctionCall2(numeric_round,
quotient,
Int32GetDatum(fpoint)));
PG_RETURN_NUMERIC(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 = DirectFunctionCall1(int8_numeric, Int64GetDatum(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);
}