/*
* 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 void
append_num_word(StringInfo buf, Cash value)
{
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 )
{
appendStringInfoString(buf, small[value]);
return ;
}
/* is it an even multiple of 100? */
if (!tu)
{
appendStringInfo(buf, "%s hundred" , small[value / 100 ]);
return ;
}
/* more than 99? */
if (value > 99 )
{
/* is it an even multiple of 10 other than 10? */
if (value % 10 == 0 && tu > 10 )
appendStringInfo(buf, "%s hundred %s" ,
small[value / 100 ], big[tu / 10 ]);
else if (tu < 20 )
appendStringInfo(buf, "%s hundred and %s" ,
small[value / 100 ], small[tu]);
else
appendStringInfo(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 )
appendStringInfoString(buf, big[tu / 10 ]);
else if (tu < 20 )
appendStringInfoString(buf, small[tu]);
else
appendStringInfo(buf, "%s %s" , big[tu / 10 ], small[tu % 10 ]);
}
}
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 );
uint64 uvalue;
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 )
{
/* 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;
}
/* make the amount positive for digit-reconstruction loop */
uvalue = pg_abs_s64(value);
/* 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) = (uvalue % 10 ) + '0' ;
uvalue = uvalue / 10 ;
digit_pos--;
} while (uvalue || 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;
StringInfoData buf;
text *res;
Cash dollars;
Cash m0;
Cash m1;
Cash m2;
Cash m3;
Cash m4;
Cash m5;
Cash m6;
initStringInfo(&buf);
/* work with positive numbers */
if (value < 0 )
{
value = -value;
appendStringInfoString(&buf, "minus " );
}
/* Now treat as unsigned, to avoid trouble at INT_MIN */
val = (uint64) value;
dollars = val / INT64CONST(100 );
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)
{
append_num_word(&buf, m6);
appendStringInfoString(&buf, " quadrillion " );
}
if (m5)
{
append_num_word(&buf, m5);
appendStringInfoString(&buf, " trillion " );
}
if (m4)
{
append_num_word(&buf, m4);
appendStringInfoString(&buf, " billion " );
}
if (m3)
{
append_num_word(&buf, m3);
appendStringInfoString(&buf, " million " );
}
if (m2)
{
append_num_word(&buf, m2);
appendStringInfoString(&buf, " thousand " );
}
if (m1)
append_num_word(&buf, m1);
if (dollars == 0 )
appendStringInfoString(&buf, "zero" );
appendStringInfoString(&buf, dollars == 1 ? " dollar and " : " dollars and " );
append_num_word(&buf, m0);
appendStringInfoString(&buf, m0 == 1 ? " cent" : " cents" );
/* capitalize output */
buf.data[0 ] = pg_toupper((unsigned char ) buf.data[0 ]);
/* return as text datum */
res = cstring_to_text_with_len(buf.data, buf.len);
pfree(buf.data);
PG_RETURN_TEXT_P(res);
}
/* 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);
}
Messung V0.5 in Prozent C=94 H=93 G=93
¤ Dauer der Verarbeitung: 0.24 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland