/* 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_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_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) ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); result = rint(c / s); 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) ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); result = rint(c / f); 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); int32 i = PG_GETARG_INT32(1); Cash result; if (i == 0) ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); result = c / i; PG_RETURN_CASH(result); }
Datum cash_dist(PG_FUNCTION_ARGS) { Cash a = PG_GETARG_CASH(0); Cash b = PG_GETARG_CASH(1); Cash r; Cash ra; r = a - b; ra = Abs(r); /* Overflow check. */ if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a))) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("money out of range"))); PG_RETURN_CASH(ra); }
/* 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') 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_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; 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)) ereport(ERROR, (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)) ereport(ERROR, (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)) ereport(ERROR, (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 ereport(ERROR, (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) ereport(ERROR, (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_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; 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); #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); #ifdef CASHDEBUG printf("cashin- string is '%s'\n", s); #endif 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)) { value = (value * 10) + (*s - '0'); 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') value++; /* adjust for less than required decimal places */ for (; dec < fpoint; dec++) value *= 10; /* * should only be trailing digits followed by whitespace, right paren, * or possibly a trailing minus sign */ 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 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 " INT64_FORMAT "\n", result); #endif PG_RETURN_CASH(result); }