#include <limits.h>
#include <ctype.h>
#include <math.h>
-#include <locale.h>
#include "libpq/pqformat.h"
#include "utils/builtins.h"
dsymbol = '.';
if (*lconvert->mon_thousands_sep != '\0')
ssymbol = lconvert->mon_thousands_sep;
- else /* ssymbol should not equal dsymbol */
+ else /* ssymbol should not equal dsymbol */
ssymbol = (dsymbol != ',') ? "," : ".";
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
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);
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))
{
- value = (value * 10) + (*s - '0');
+ Cash newvalue = (value * 10) - (*s - '0');
+
+ if (newvalue / 10 != value)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type %s",
+ str, "money")));
+
+ value = newvalue;
if (seen_dot)
dec++;
/* round off if there's another digit */
if (isdigit((unsigned char) *s) && *s >= '5')
- value++;
+ value--; /* remember we build the value in the negative */
+
+ if (value > 0)
+ 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++)
- value *= 10;
+ {
+ Cash newvalue = value * 10;
+
+ if (newvalue / 10 != value)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type %s",
+ str, "money")));
+
+ value = newvalue;
+ }
/*
* should only be trailing digits followed by whitespace, right paren,
- * or possibly a trailing minus sign
+ * trailing sign, and/or trailing currency symbol
*/
while (isdigit((unsigned char) *s))
s++;
+
while (*s)
{
if (isspace((unsigned char) *s) || *s == ')')
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 money: \"%s\"",
- str)));
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "money", str)));
}
- result = value * sgn;
+ /* If the value is supposed to be positive, flip the sign, but check for
+ * the most negative number. */
+ if (sgn > 0)
+ {
+ result = -value;
+ if (result < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type %s",
+ str, "money")));
+ }
+ else
+ result = value;
#ifdef CASHDEBUG
printf("cashin- result is " INT64_FORMAT "\n", result);
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;
+ *signsymbol;
+ char sign_posn,
+ cs_precedes,
+ sep_by_space;
struct lconv *lconvert = PGLC_localeconv();
/* see comments about frac_digits in cash_in() */
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 = '.';
if (*lconvert->mon_thousands_sep != '\0')
ssymbol = lconvert->mon_thousands_sep;
- else /* ssymbol should not equal dsymbol */
+ 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;
+ /* 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 result string right-to-left in buf[] */
+ /* we build the digits+decimal-point+sep string right-to-left in buf[] */
bufptr = buf + sizeof(buf) - 1;
*bufptr = '\0';
{
if (points && digit_pos == 0)
{
- /* insert decimal point */
+ /* insert decimal point, but not if value cannot be fractional */
*(--bufptr) = dsymbol;
}
- else if (digit_pos < points && (digit_pos % mon_group) == 0)
+ else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
{
- /* insert thousands sep */
+ /* insert thousands sep, but only to left of radix point */
bufptr -= strlen(ssymbol);
memcpy(bufptr, ssymbol, strlen(ssymbol));
}
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
+ /*----------
+ * 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)
{
- /* just emit what we have */
- result = pstrdup(bufptr);
+ 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_words()
- * This converts a int4 as well but to a representation using words
+ * This converts an int4 as well but to a representation using words
* Obviously way North American centric - sorry
*/
Datum