3 * Written by D'Arcy J.M. Cain
5 * http://www.druid.net/darcy/
7 * Functions to allow input and output of money normally but store
8 * and handle it as 64 bit ints
10 * A slightly modified version of this file and a discussion of the
11 * workings can be found in the book "Software Solutions in C" by
12 * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
13 * this version handles 64 bit numbers and so can hold values up to
14 * 92,233,720,368,547,758ドル.07.
16 * src/backend/utils/adt/cash.c
34/*************************************************************************
36 ************************************************************************/
41 static const char *
const small[] = {
42 "zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
43 "eight",
"nine",
"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
44 "fifteen",
"sixteen",
"seventeen",
"eighteen",
"nineteen",
"twenty",
45 "thirty",
"forty",
"fifty",
"sixty",
"seventy",
"eighty",
"ninety"
47 const char *
const *big = small + 18;
50 /* deal with the simple cases first */
57 /* is it an even multiple of 100? */
67 /* is it an even multiple of 10 other than 10? */
68 if (
value % 10 == 0 && tu > 10)
70 small[
value / 100], big[tu / 10]);
73 small[
value / 100], small[tu]);
76 small[
value / 100], big[tu / 10], small[tu % 10]);
80 /* is it an even multiple of 10 other than 10? */
81 if (
value % 10 == 0 && tu > 10)
97 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
98 errmsg(
"money out of range")));
110 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
111 errmsg(
"money out of range")));
123 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
124 errmsg(
"money out of range")));
136 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
137 errmsg(
"money out of range")));
149 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
150 errmsg(
"money out of range")));
160 (
errcode(ERRCODE_DIVISION_BY_ZERO),
161 errmsg(
"division by zero")));
167 * Convert a string to a cash data type.
168 * Format is [$]###[,]###[.##]
169 * Examples: 123.45 123ドル.45 123,456ドル.78
176 Node *escontext = fcinfo->context;
181 bool seen_dot =
false;
192 * frac_digits will be CHAR_MAX in some locales, notably C. However, just
193 * testing for == CHAR_MAX is risky, because of compilers like gcc that
194 * "helpfully" let you alter the platform-standard definition of whether
195 * char is signed or not. If we are so unfortunate as to get compiled
196 * with a nonstandard -fsigned-char or -funsigned-char switch, then our
197 * idea of CHAR_MAX will not agree with libc's. The safest course is not
198 * to test for CHAR_MAX at all, but to impose a range check for plausible
199 * frac_digits values.
201 fpoint = lconvert->frac_digits;
202 if (fpoint < 0 || fpoint > 10)
203 fpoint = 2;
/* best guess in this case, I think */
205 /* we restrict dsymbol to be a single byte, but not the other symbols */
206 if (*lconvert->mon_decimal_point !=
'0円' &&
207 lconvert->mon_decimal_point[1] ==
'0円')
208 dsymbol = *lconvert->mon_decimal_point;
211 if (*lconvert->mon_thousands_sep !=
'0円')
212 ssymbol = lconvert->mon_thousands_sep;
213 else /* ssymbol should not equal dsymbol */
214 ssymbol = (dsymbol !=
',') ?
"," :
".";
215 csymbol = (*lconvert->currency_symbol !=
'0円') ? lconvert->currency_symbol :
"$";
216 psymbol = (*lconvert->positive_sign !=
'0円') ? lconvert->positive_sign :
"+";
217 nsymbol = (*lconvert->negative_sign !=
'0円') ? lconvert->negative_sign :
"-";
220 printf(
"cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
221 fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
224 /* we need to add all sorts of checking here. For now just */
225 /* strip all leading whitespace and any leading currency symbol */
226 while (isspace((
unsigned char) *s))
228 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
229 s += strlen(csymbol);
230 while (isspace((
unsigned char) *s))
234 printf(
"cashin- string is '%s'\n", s);
237 /* a leading minus or paren signifies a negative number */
238 /* again, better heuristics needed */
239 /* XXX - doesn't properly check for balanced parens - djmc */
240 if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
243 s += strlen(nsymbol);
250 else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
251 s += strlen(psymbol);
254 printf(
"cashin- string is '%s'\n", s);
257 /* allow whitespace and currency symbol after the sign, too */
258 while (isspace((
unsigned char) *s))
260 if (strncmp(s, csymbol, strlen(csymbol)) == 0)
261 s += strlen(csymbol);
262 while (isspace((
unsigned char) *s))
266 printf(
"cashin- string is '%s'\n", s);
270 * We accumulate the absolute amount in "value" and then apply the sign at
271 * the end. (The sign can appear before or after the digits, so it would
272 * be more complicated to do otherwise.) Because of the larger range of
273 * negative signed integers, we build "value" in the negative and then
274 * flip the sign at the end, catching most-negative-number overflow if
281 * We look for digits as long as we have found less than the required
282 * number of decimal places.
284 if (isdigit((
unsigned char) *s) && (!seen_dot || dec < fpoint))
286 int8 digit = *s -
'0';
291 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
292 errmsg(
"value \"%s\" is out of range for type %s",
298 /* decimal point? then start counting fractions... */
299 else if (*s == dsymbol && !seen_dot)
303 /* ignore if "thousands" separator, else we're done */
304 else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
305 s += strlen(ssymbol) - 1;
310 /* round off if there's another digit */
311 if (isdigit((
unsigned char) *s) && *s >=
'5')
313 /* remember we build the value in the negative */
316 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
317 errmsg(
"value \"%s\" is out of range for type %s",
321 /* adjust for less than required decimal places */
322 for (; dec < fpoint; dec++)
326 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
327 errmsg(
"value \"%s\" is out of range for type %s",
332 * should only be trailing digits followed by whitespace, right paren,
333 * trailing sign, and/or trailing currency symbol
335 while (isdigit((
unsigned char) *s))
340 if (isspace((
unsigned char) *s) || *s ==
')')
342 else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
345 s += strlen(nsymbol);
347 else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
348 s += strlen(psymbol);
349 else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
350 s += strlen(csymbol);
353 (
errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
354 errmsg(
"invalid input syntax for type %s: \"%s\"",
359 * If the value is supposed to be positive, flip the sign, but check for
360 * the most negative number.
366 (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
367 errmsg(
"value \"%s\" is out of range for type %s",
383 * Function to convert cash to a dollars and cents representation, using
384 * the lc_monetary locale's formatting.
406 /* see comments about frac_digits in cash_in() */
407 points = lconvert->frac_digits;
408 if (points < 0 || points > 10)
409 points = 2;
/* best guess in this case, I think */
412 * As with frac_digits, must apply a range check to mon_grouping to avoid
413 * being fooled by variant CHAR_MAX values.
415 mon_group = *lconvert->mon_grouping;
416 if (mon_group <= 0 || mon_group > 6)
419 /* we restrict dsymbol to be a single byte, but not the other symbols */
420 if (*lconvert->mon_decimal_point !=
'0円' &&
421 lconvert->mon_decimal_point[1] ==
'0円')
422 dsymbol = *lconvert->mon_decimal_point;
425 if (*lconvert->mon_thousands_sep !=
'0円')
426 ssymbol = lconvert->mon_thousands_sep;
427 else /* ssymbol should not equal dsymbol */
428 ssymbol = (dsymbol !=
',') ?
"," :
".";
429 csymbol = (*lconvert->currency_symbol !=
'0円') ? lconvert->currency_symbol :
"$";
433 /* set up formatting data */
434 signsymbol = (*lconvert->negative_sign !=
'0円') ? lconvert->negative_sign :
"-";
435 sign_posn = lconvert->n_sign_posn;
436 cs_precedes = lconvert->n_cs_precedes;
437 sep_by_space = lconvert->n_sep_by_space;
441 signsymbol = lconvert->positive_sign;
442 sign_posn = lconvert->p_sign_posn;
443 cs_precedes = lconvert->p_cs_precedes;
444 sep_by_space = lconvert->p_sep_by_space;
447 /* make the amount positive for digit-reconstruction loop */
450 /* we build the digits+decimal-point+sep string right-to-left in buf[] */
451 bufptr =
buf +
sizeof(
buf) - 1;
455 * Generate digits till there are no non-zero digits left and we emitted
456 * at least one to the left of the decimal point. digit_pos is the
457 * current digit position, with zero as the digit just left of the decimal
458 * point, increasing to the right.
463 if (points && digit_pos == 0)
465 /* insert decimal point, but not if value cannot be fractional */
466 *(--bufptr) = dsymbol;
468 else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
470 /* insert thousands sep, but only to left of radix point */
471 bufptr -= strlen(ssymbol);
472 memcpy(bufptr, ssymbol, strlen(ssymbol));
475 *(--bufptr) = (uvalue % 10) +
'0';
476 uvalue = uvalue / 10;
478 }
while (uvalue || digit_pos >= 0);
481 * Now, attach currency symbol and sign symbol in the correct order.
483 * The POSIX spec defines these values controlling this code:
486 * 0 Parentheses enclose the quantity and the currency_symbol.
487 * 1 The sign string precedes the quantity and the currency_symbol.
488 * 2 The sign string succeeds the quantity and the currency_symbol.
489 * 3 The sign string precedes the currency_symbol.
490 * 4 The sign string succeeds the currency_symbol.
492 * p/n_cs_precedes: 0 means currency symbol after value, else before it.
495 * 0 No <space> separates the currency symbol and value.
496 * 1 If the currency symbol and sign string are adjacent, a <space>
497 * separates them from the value; otherwise, a <space> separates
498 * the currency symbol from the value.
499 * 2 If the currency symbol and sign string are adjacent, a <space>
500 * separates them; otherwise, a <space> separates the sign string
510 (sep_by_space == 1) ?
" " :
"",
515 (sep_by_space == 1) ?
" " :
"",
523 (sep_by_space == 2) ?
" " :
"",
525 (sep_by_space == 1) ?
" " :
"",
530 (sep_by_space == 2) ?
" " :
"",
532 (sep_by_space == 1) ?
" " :
"",
539 (sep_by_space == 1) ?
" " :
"",
541 (sep_by_space == 2) ?
" " :
"",
546 (sep_by_space == 1) ?
" " :
"",
548 (sep_by_space == 2) ?
" " :
"",
555 (sep_by_space == 2) ?
" " :
"",
557 (sep_by_space == 1) ?
" " :
"",
562 (sep_by_space == 1) ?
" " :
"",
564 (sep_by_space == 2) ?
" " :
"",
571 (sep_by_space == 2) ?
" " :
"",
573 (sep_by_space == 1) ?
" " :
"",
578 (sep_by_space == 1) ?
" " :
"",
580 (sep_by_space == 2) ?
" " :
"",
589 * cash_recv - converts external binary format to cash
600 * cash_send - converts cash to binary format
614 * Comparison functions
687 * Add two cash values.
700 * Subtract two cash values.
713 * Divide cash by cash, returning float8.
724 (
errcode(ERRCODE_DIVISION_BY_ZERO),
725 errmsg(
"division by zero")));
733 * Multiply cash by float8.
746 * Multiply float8 by cash.
759 * Divide cash by float8.
772 * Multiply cash by float4.
785 * Multiply float4 by cash.
798 * Divide cash by float4.
812 * Multiply cash by int8.
825 * Multiply int8 by cash.
837 * Divide cash by 8-byte integer.
850 * Multiply cash by int4.
863 * Multiply int4 by cash.
876 * Divide cash by 4-byte integer.
890 * Multiply cash by int2.
902 * Multiply int2 by cash.
914 * Divide cash by int2.
927 * Return larger of two cash values.
936 result = (c1 > c2) ? c1 : c2;
942 * Return smaller of two cash values.
951 result = (c1 < c2) ? c1 : c2;
957 * This converts an int4 as well but to a representation using words
958 * Obviously way North American centric - sorry
978 /* work with positive numbers */
985 /* Now treat as unsigned, to avoid trouble at INT_MIN */
994 m5 = (
val /
INT64CONST(100000000000000)) % 1000;
/* trillions */
995 m6 = (
val /
INT64CONST(100000000000000000)) % 1000;
/* quadrillions */
1037 /* capitalize output */
1040 /* return as text datum */
1048 * Convert cash to numeric.
1058 /* see comments about frac_digits in cash_in() */
1059 fpoint = lconvert->frac_digits;
1060 if (fpoint < 0 || fpoint > 10)
1063 /* convert the integral money value to numeric */
1066 /* scale appropriately, if needed */
1074 /* compute required scale factor */
1076 for (
i = 0;
i < fpoint;
i++)
1081 * Given integral inputs approaching INT64_MAX, select_div_scale()
1082 * might choose a result scale of zero, causing loss of fractional
1083 * digits in the quotient. We can ensure an exact result by setting
1084 * the dscale of either input to be at least as large as the desired
1085 * result scale. numeric_round() will do that for us.
1091 /* Now we can safely divide ... */
1094 /* ... and forcibly round to exactly the intended number of digits */
1104 * Convert numeric to cash.
1117 /* see comments about frac_digits in cash_in() */
1118 fpoint = lconvert->frac_digits;
1119 if (fpoint < 0 || fpoint > 10)
1122 /* compute required scale factor */
1124 for (
i = 0;
i < fpoint;
i++)
1127 /* multiply the input amount by scale factor */
1131 /* note that numeric_int8 will round to nearest integer for us */
1138 * Convert int4 (int) to cash
1150 /* see comments about frac_digits in cash_in() */
1151 fpoint = lconvert->frac_digits;
1152 if (fpoint < 0 || fpoint > 10)
1155 /* compute required scale factor */
1157 for (
i = 0;
i < fpoint;
i++)
1160 /* compute amount * scale, checking for overflow */
1168 * Convert int8 (bigint) to cash
1180 /* see comments about frac_digits in cash_in() */
1181 fpoint = lconvert->frac_digits;
1182 if (fpoint < 0 || fpoint > 10)
1185 /* compute required scale factor */
1187 for (
i = 0;
i < fpoint;
i++)
1190 /* compute amount * scale, checking for overflow */
Datum numeric_div(PG_FUNCTION_ARGS)
Datum numeric_round(PG_FUNCTION_ARGS)
Numeric int64_to_numeric(int64 val)
Datum numeric_int8(PG_FUNCTION_ARGS)
Datum numeric_scale(PG_FUNCTION_ARGS)
Datum numeric_mul(PG_FUNCTION_ARGS)
#define FLOAT8_FITS_IN_INT64(num)
Datum int8_cash(PG_FUNCTION_ARGS)
Datum cash_mul_int8(PG_FUNCTION_ARGS)
Datum flt8_mul_cash(PG_FUNCTION_ARGS)
static Cash cash_mi_cash(Cash c1, Cash c2)
Datum cash_mi(PG_FUNCTION_ARGS)
Datum flt4_mul_cash(PG_FUNCTION_ARGS)
Datum cash_numeric(PG_FUNCTION_ARGS)
static Cash cash_div_int64(Cash c, int64 i)
Datum cash_gt(PG_FUNCTION_ARGS)
Datum cash_div_cash(PG_FUNCTION_ARGS)
Datum cash_mul_flt4(PG_FUNCTION_ARGS)
Datum cash_ne(PG_FUNCTION_ARGS)
Datum cash_out(PG_FUNCTION_ARGS)
static Cash cash_mul_int64(Cash c, int64 i)
static Cash cash_mul_float8(Cash c, float8 f)
Datum cash_ge(PG_FUNCTION_ARGS)
Datum cash_div_int4(PG_FUNCTION_ARGS)
Datum cash_in(PG_FUNCTION_ARGS)
Datum numeric_cash(PG_FUNCTION_ARGS)
Datum cash_pl(PG_FUNCTION_ARGS)
Datum cash_eq(PG_FUNCTION_ARGS)
static Cash cash_div_float8(Cash c, float8 f)
Datum cash_div_int2(PG_FUNCTION_ARGS)
static void append_num_word(StringInfo buf, Cash value)
Datum cash_mul_flt8(PG_FUNCTION_ARGS)
Datum int4_mul_cash(PG_FUNCTION_ARGS)
Datum int2_mul_cash(PG_FUNCTION_ARGS)
Datum cash_div_int8(PG_FUNCTION_ARGS)
Datum cash_lt(PG_FUNCTION_ARGS)
Datum cashlarger(PG_FUNCTION_ARGS)
Datum cash_send(PG_FUNCTION_ARGS)
Datum cash_mul_int4(PG_FUNCTION_ARGS)
static Cash cash_pl_cash(Cash c1, Cash c2)
Datum cash_recv(PG_FUNCTION_ARGS)
Datum cash_div_flt8(PG_FUNCTION_ARGS)
Datum cashsmaller(PG_FUNCTION_ARGS)
Datum cash_le(PG_FUNCTION_ARGS)
Datum cash_div_flt4(PG_FUNCTION_ARGS)
Datum int8_mul_cash(PG_FUNCTION_ARGS)
Datum cash_cmp(PG_FUNCTION_ARGS)
Datum cash_mul_int2(PG_FUNCTION_ARGS)
Datum int4_cash(PG_FUNCTION_ARGS)
Datum cash_words(PG_FUNCTION_ARGS)
#define PG_RETURN_CASH(x)
#define PG_GETARG_CASH(n)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereturn(context, dummy_value,...)
#define ereport(elevel,...)
static float8 float8_mul(const float8 val1, const float8 val2)
static float8 float8_div(const float8 val1, const float8 val2)
#define PG_RETURN_BYTEA_P(x)
#define DirectFunctionCall2(func, arg1, arg2)
#define PG_GETARG_FLOAT8(n)
#define PG_RETURN_FLOAT8(x)
#define PG_GETARG_POINTER(n)
#define PG_RETURN_CSTRING(x)
#define DirectFunctionCall1(func, arg1)
#define PG_GETARG_DATUM(n)
#define PG_GETARG_CSTRING(n)
#define PG_GETARG_INT64(n)
#define PG_RETURN_TEXT_P(x)
#define PG_RETURN_INT32(x)
#define PG_GETARG_INT32(n)
#define PG_RETURN_DATUM(x)
#define PG_GETARG_FLOAT4(n)
#define PG_RETURN_BOOL(x)
#define PG_GETARG_INT16(n)
Datum int8mul(PG_FUNCTION_ARGS)
static bool pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
static uint64 pg_abs_s64(int64 a)
static bool pg_sub_s64_overflow(int64 a, int64 b, int64 *result)
static bool pg_add_s64_overflow(int64 a, int64 b, int64 *result)
if(TABLE==NULL||TABLE_index==NULL)
void pfree(void *pointer)
static Datum NumericGetDatum(Numeric X)
struct lconv * PGLC_localeconv(void)
unsigned char pg_toupper(unsigned char ch)
static Datum Int64GetDatum(int64 X)
static int64 DatumGetInt64(Datum X)
static Datum Int32GetDatum(int32 X)
char * psprintf(const char *fmt,...)
struct StringInfoData * StringInfo
void appendStringInfo(StringInfo str, const char *fmt,...)
void appendStringInfoString(StringInfo str, const char *s)
void initStringInfo(StringInfo str)
text * cstring_to_text_with_len(const char *s, int len)