1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-29 23:43:17 +03:00
Files
postgres/src/backend/utils/adt/cash.c
Tom Lane 234a02b2a8 Replace direct assignments to VARATT_SIZEP(x) with SET_VARSIZE(x, len).
Get rid of VARATT_SIZE and VARATT_DATA, which were simply redundant with
VARSIZE and VARDATA, and as a consequence almost no code was using the
longer names.  Rename the length fields of struct varlena and various
derived structures to catch anyplace that was accessing them directly;
and clean up various places so caught.  In itself this patch doesn't
change any behavior at all, but it is necessary infrastructure if we hope
to play any games with the representation of varlena headers.
Greg Stark and Tom Lane
2007-02-27 23:48:10 +00:00

868 lines
17 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.
*
* $PostgreSQL: pgsql/src/backend/utils/adt/cash.c,v 1.70 2007/02/27 23:48:07 tgl Exp $
*/
#include "postgres.h"
#include <limits.h>
#include <ctype.h>
#include <math.h>
#include <locale.h>
#include "libpq/pqformat.h"
#include "utils/cash.h"
#include "utils/pg_locale.h"
#define CASH_BUFSZ 36
#define TERMINATOR (CASH_BUFSZ - 1)
#define LAST_PAREN (TERMINATOR - 1)
#define LAST_DIGIT (LAST_PAREN - 1)
/*
* Cash is a pass-by-ref SQL type, so we must pass and return pointers.
* These macros and support routine hide the pass-by-refness.
*/
#define PG_GETARG_CASH(n) (* ((Cash *) PG_GETARG_POINTER(n)))
#define PG_RETURN_CASH(x) return CashGetDatum(x)
/*************************************************************************
* 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() */
static Datum
CashGetDatum(Cash value)
{
Cash *result = (Cash *) palloc(sizeof(Cash));
*result = value;
return PointerGetDatum(result);
}
/* 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;
int seen_dot = 0;
const char *s = str;
int fpoint;
char dsymbol,
ssymbol,
psymbol;
const char *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 */
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
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 '%c'; currency '%s'; positive '%c'; 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);
#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);
#ifdef CASHDEBUG
printf("cashin- negative symbol; string is '%s'\n", s);
#endif
}
else if (*s == '(')
{
sgn = -1;
s++;
}
else if (*s == psymbol)
s++;
#ifdef CASHDEBUG
printf("cashin- string is '%s'\n", s);
#endif
while (isspace((unsigned char) *s))
s++;
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
s += strlen(csymbol);
#ifdef CASHDEBUG
printf("cashin- string is '%s'\n", s);
#endif
for (;; s++)
{
/* we look for digits as int8 as we have less */
/* than the required number of decimal places */
if (isdigit((unsigned char) *s) && dec < fpoint)
{
value = (value * 10) + *s - '0';
if (seen_dot)
dec++;
}
/* decimal point? then start counting fractions... */
else if (*s == dsymbol && !seen_dot)
{
seen_dot = 1;
}
/* "thousands" separator? then skip... */
else if (*s == ssymbol)
{
}
else
{
/* round off */
if (isdigit((unsigned char) *s) && *s >= '5')
value++;
/* adjust for less than required decimal places */
for (; dec < fpoint; dec++)
value *= 10;
break;
}
}
/* should only be trailing digits followed by whitespace or closing paren */
while (isdigit(*s)) s++;
while (isspace((unsigned char) *s) || *s == ')')
s++;
if (*s != '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type money: \"%s\"", str)));
result = value * sgn;
#ifdef CASHDEBUG
printf("cashin- result is %d\n", result);
#endif
PG_RETURN_CASH(result);
}
/* cash_out()
* Function to convert cash to a dollars and cents representation.
* XXX HACK This code appears to assume US conventions for
* positive-valued amounts. - tgl 97/04/14
*/
Datum
cash_out(PG_FUNCTION_ARGS)
{
Cash value = PG_GETARG_CASH(0);
char *result;
char buf[CASH_BUFSZ];
int minus = 0;
int count = LAST_DIGIT;
int point_pos;
int comma_position = 0;
int points,
mon_group;
char comma;
const char *csymbol,
*nsymbol;
char dsymbol;
char convention;
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;
comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
convention = lconvert->n_sign_posn;
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
point_pos = LAST_DIGIT - points;
/* allow more than three decimal points and separate them */
if (comma)
{
point_pos -= (points - 1) / mon_group;
comma_position = point_pos % (mon_group + 1);
}
/* we work with positive amounts and add the minus sign at the end */
if (value < 0)
{
minus = 1;
value = -value;
}
/* allow for trailing negative strings */
MemSet(buf, ' ', CASH_BUFSZ);
buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
while (value || count > (point_pos - 2))
{
if (points && count == point_pos)
buf[count--] = dsymbol;
else if (comma && count % (mon_group + 1) == comma_position)
buf[count--] = comma;
buf[count--] = ((uint64) value % 10) + '0';
value = ((uint64) value) / 10;
}
strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
count -= strlen(csymbol) - 1;
if (buf[LAST_DIGIT] == ',')
buf[LAST_DIGIT] = buf[LAST_PAREN];
/* see if we need to signify negative amount */
if (minus)
{
result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
/* Position code of 0 means use parens */
if (convention == 0)
sprintf(result, "(%s)", buf + count);
else if (convention == 2)
sprintf(result, "%s%s", buf + count, nsymbol);
else
sprintf(result, "%s%s", nsymbol, buf + count);
}
else
{
result = palloc(CASH_BUFSZ + 2 - count);
strcpy(result, buf + count);
}
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_getmsgint(buf, sizeof(Cash)));
}
/*
* 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_sendint(&buf, arg1, sizeof(Cash));
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_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);
int64 i = PG_GETARG_INT64(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);
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_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 a 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;
text *result;
/* 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 % 100ll; /* cents */
m1 = (val / 100ll) % 1000; /* hundreds */
m2 = (val / 100000ll) % 1000; /* thousands */
m3 = val / 100000000ll % 1000; /* millions */
m4 = val / 100000000000ll % 1000; /* billions */
m5 = val / 100000000000000ll % 1000; /* trillions */
m6 = val / 100000000000000000ll % 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]);
/* make a text type for output */
result = (text *) palloc(strlen(buf) + VARHDRSZ);
SET_VARSIZE(result, strlen(buf) + VARHDRSZ);
memcpy(VARDATA(result), buf, strlen(buf));
PG_RETURN_TEXT_P(result);
}