/* 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); }
/* cash_numeric() * Convert cash to numeric. */ Datum cash_numeric(PG_FUNCTION_ARGS) { Cash money = PG_GETARG_CASH(0); Numeric result; int fpoint; int64 scale; int i; Datum amount; Datum numeric_scale; Datum quotient; 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; /* form the result as money / scale */ amount = DirectFunctionCall1(int8_numeric, Int64GetDatum(money)); numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale)); quotient = DirectFunctionCall2(numeric_div, amount, numeric_scale); /* forcibly round to exactly the intended number of digits */ result = DatumGetNumeric(DirectFunctionCall2(numeric_round, quotient, Int32GetDatum(fpoint))); PG_RETURN_NUMERIC(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 = DirectFunctionCall1(int8_numeric, Int64GetDatum(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); }
Datum orafce_to_char_numeric(PG_FUNCTION_ARGS) { Numeric arg0 = PG_GETARG_NUMERIC(0); StringInfo buf = makeStringInfo(); struct lconv *lconv = PGLC_localeconv(); char *p; char *decimal = NULL; appendStringInfoString(buf, DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(arg0)))); for (p = buf->data; *p; p++) if (*p == '.') { *p = lconv->decimal_point[0]; decimal = p; /* save decimal point position for the next loop */ } /* Simulate the default Oracle to_char template (TM9 - Text Minimum) by removing unneeded digits after the decimal point; if no digits are left, then remove the decimal point too */ for (p = buf->data + buf->len - 1; decimal && p >= decimal; p--) { if (*p == '0' || *p == lconv->decimal_point[0]) *p = 0; else break; /* non-zero digit found, exit the loop */ } PG_RETURN_TEXT_P(cstring_to_text(buf->data)); }
Datum orafce_to_char_numeric(PG_FUNCTION_ARGS) { Numeric arg0 = PG_GETARG_NUMERIC(0); StringInfo buf = makeStringInfo(); struct lconv *lconv = PGLC_localeconv(); char *p; appendStringInfoString(buf, DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(arg0)))); for (p = buf->data; *p; p++) if (*p == '.') *p = lconv->decimal_point[0]; PG_RETURN_TEXT_P(cstring_to_text(buf->data)); }
Datum orafce_to_char_float8(PG_FUNCTION_ARGS) { float8 arg0 = PG_GETARG_FLOAT8(0); StringInfo buf = makeStringInfo(); struct lconv *lconv = PGLC_localeconv(); char *p; appendStringInfo(buf, "%f", arg0); for (p = buf->data; *p; p++) if (*p == '.') *p = lconv->decimal_point[0]; PG_RETURN_TEXT_P(cstring_to_text(buf->data)); }
Datum orafce_to_number(PG_FUNCTION_ARGS) { text *arg0 = PG_GETARG_TEXT_PP(0); char *buf; struct lconv *lconv = PGLC_localeconv(); Numeric res; char *p; buf = text_to_cstring(arg0); for (p = buf; *p; p++) if (*p == lconv->decimal_point[0] && lconv->decimal_point[0]) *p = '.'; else if (*p == lconv->thousands_sep[0] && lconv->thousands_sep[0]) *p = ','; res = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(buf), 0, -1)); PG_RETURN_NUMERIC(res); }
/* 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_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)))) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); /* 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))) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); strcpy(result, buf + count); } PG_RETURN_CSTRING(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_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_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 ssymbol_position = 0; int points, mon_group; char ssymbol; const char *csymbol, *nsymbol; char dsymbol; 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; convention = lconvert->n_sign_posn; dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.'); 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 : "$"); nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-"); point_pos = LAST_DIGIT - points; point_pos -= (points - 1) / mon_group; ssymbol_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 (ssymbol && count % (mon_group + 1) == ssymbol_position) buf[count--] = ssymbol; buf[count--] = ((uint64) value % 10) + '0'; value = ((uint64) value) / 10; } strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol)); count -= strlen(csymbol) - 1; /* * If points == 0 and the number of digits % mon_group == 0, the code * above adds a trailing ssymbol on the far right, so remove it. */ if (buf[LAST_DIGIT] == ssymbol) buf[LAST_DIGIT] = '\0'; /* see if we need to signify negative amount */ if (minus) { result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol)); /* 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 { result = palloc(CASH_BUFSZ + 2 - count); strcpy(result, buf + count); } PG_RETURN_CSTRING(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); }
/* 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; bool minus = false; int digit_pos; int points, mon_group; char dsymbol; const char *ssymbol, *csymbol, *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; convention = lconvert->n_sign_posn; /* 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 : "$"; nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-"; /* we work with positive amounts and add the minus sign at the end */ if (value < 0) { minus = true; value = -value; } /* we build the result 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 */ *(--bufptr) = dsymbol; } else if (digit_pos < points && (digit_pos % mon_group) == 0) { /* insert thousands sep */ bufptr -= strlen(ssymbol); memcpy(bufptr, ssymbol, strlen(ssymbol)); } *(--bufptr) = ((uint64) value % 10) + '0'; value = ((uint64) value) / 10; digit_pos--; } while (value || digit_pos >= 0); /* prepend csymbol */ bufptr -= strlen(csymbol); memcpy(bufptr, csymbol, strlen(csymbol)); /* see if we need to signify negative amount */ if (minus) { result = palloc(strlen(bufptr) + strlen(nsymbol) + 3); /* Position code of 0 means use parens */ if (convention == 0) sprintf(result, "(%s)", bufptr); else if (convention == 2) sprintf(result, "%s%s", bufptr, nsymbol); else sprintf(result, "%s%s", nsymbol, bufptr); } else { /* just emit what we have */ result = pstrdup(bufptr); } PG_RETURN_CSTRING(result); }