mirror of
https://github.com/postgres/postgres.git
synced 2025-11-10 17:42:29 +03:00
division and modulo functions, to avoid problems on OS X (which fails to trap 0 divide at all) and Windows (which traps it in some bizarre nonstandard fashion). Standardize on 'division by zero' as the one true spelling of this error message. Add regression tests as suggested by Neil Conway.
758 lines
15 KiB
C
758 lines
15 KiB
C
/*
|
|
* cash.c
|
|
* Written by D'Arcy J.M. Cain
|
|
*
|
|
* Functions to allow input and output of money normally but store
|
|
* and handle it as int4s
|
|
*
|
|
* 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.
|
|
*
|
|
* $Header: /cvsroot/pgsql/src/backend/utils/adt/cash.c,v 1.57 2003/03/11 21:01:33 tgl Exp $
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <limits.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <locale.h>
|
|
|
|
#include "miscadmin.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/cash.h"
|
|
#include "utils/pg_locale.h"
|
|
|
|
|
|
static const char *num_word(Cash value);
|
|
|
|
/* when we go to 64 bit values we will have to modify this */
|
|
#define CASH_BUFSZ 24
|
|
|
|
#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)
|
|
|
|
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
|
|
*
|
|
* This is currently implemented as a 32-bit integer.
|
|
* XXX HACK It looks as though some of the symbols for
|
|
* monetary values returned by localeconv() can be multiple
|
|
* bytes/characters. This code assumes one byte only. - tgl 97/04/14
|
|
* XXX UNHACK Allow the currency symbol to be multibyte.
|
|
* - thomas 1998-03-01
|
|
*/
|
|
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 *csymbol;
|
|
char dsymbol,
|
|
ssymbol,
|
|
psymbol,
|
|
*nsymbol;
|
|
|
|
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 */
|
|
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 int4 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;
|
|
}
|
|
}
|
|
|
|
while (isspace((unsigned char) *s) || *s == '0' || *s == ')')
|
|
s++;
|
|
|
|
if (*s != '\0')
|
|
elog(ERROR, "Bad money external representation %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;
|
|
char *csymbol,
|
|
dsymbol,
|
|
*nsymbol;
|
|
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--] = ((unsigned int) value % 10) + '0';
|
|
value = ((unsigned int) 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)
|
|
{
|
|
if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
|
|
elog(ERROR, "Memory allocation failed, can't output cash");
|
|
|
|
/* 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
|
|
{
|
|
if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
|
|
elog(ERROR, "Memory allocation failed, can't output cash");
|
|
|
|
strcpy(result, buf + count);
|
|
}
|
|
|
|
PG_RETURN_CSTRING(result);
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/* 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.
|
|
*
|
|
* XXX Don't know if rounding or truncating is correct behavior.
|
|
* Round for now. - tgl 97/04/15
|
|
*/
|
|
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)
|
|
elog(ERROR, "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.
|
|
*
|
|
* XXX Don't know if rounding or truncating is correct behavior.
|
|
* Round for now. - tgl 97/04/15
|
|
*/
|
|
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)
|
|
elog(ERROR, "division by zero");
|
|
|
|
result = rint(c / f);
|
|
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.
|
|
*
|
|
* XXX Don't know if rounding or truncating is correct behavior.
|
|
* Round for now. - tgl 97/04/15
|
|
*/
|
|
Datum
|
|
cash_div_int4(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int32 i = PG_GETARG_INT32(1);
|
|
Cash result;
|
|
|
|
if (i == 0)
|
|
elog(ERROR, "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.
|
|
*
|
|
* XXX Don't know if rounding or truncating is correct behavior.
|
|
* Round for now. - tgl 97/04/15
|
|
*/
|
|
Datum
|
|
cash_div_int2(PG_FUNCTION_ARGS)
|
|
{
|
|
Cash c = PG_GETARG_CASH(0);
|
|
int16 s = PG_GETARG_INT16(1);
|
|
Cash result;
|
|
|
|
if (s == 0)
|
|
elog(ERROR, "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);
|
|
unsigned int val;
|
|
char buf[256];
|
|
char *p = buf;
|
|
Cash m0;
|
|
Cash m1;
|
|
Cash m2;
|
|
Cash m3;
|
|
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 = (unsigned int) value;
|
|
|
|
m0 = val % 100; /* cents */
|
|
m1 = (val / 100) % 1000; /* hundreds */
|
|
m2 = (val / 100000) % 1000; /* thousands */
|
|
m3 = val / 100000000 % 1000; /* millions */
|
|
|
|
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] = toupper((unsigned char) buf[0]);
|
|
|
|
/* make a text type for output */
|
|
result = (text *) palloc(strlen(buf) + VARHDRSZ);
|
|
VARATT_SIZEP(result) = strlen(buf) + VARHDRSZ;
|
|
memcpy(VARDATA(result), buf, strlen(buf));
|
|
|
|
PG_RETURN_TEXT_P(result);
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
* 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() */
|