| /* |
| * 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 const char * |
| num_word(Cash value) |
| { |
| static char buf[128]; |
| 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) |
| 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 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); |
| 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); |
| |
| 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; |
| 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); |
| 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); |
| } |