Skip to content

Commit 7609239

Browse files
committed
Fix assorted bogosities in cash_in() and cash_out().
cash_out failed to handle multiple-byte thousands separators, as per bug #6277 from Alexander Law. In addition, cash_in didn't handle that either, nor could it handle multiple-byte positive_sign. Both routines failed to support multiple-byte mon_decimal_point, which I did not think was worth changing, but at least now they check for the possibility and fall back to using '.' rather than emitting invalid output. Also, make cash_in handle trailing negative signs, which formerly it would reject. Since cash_out generates trailing negative signs whenever the locale tells it to, this last omission represents a fail-to-reload-dumped-data bug. IMO that justifies patching this all the way back.
1 parent 78d523b commit 7609239

File tree

1 file changed

+109
-95
lines changed

1 file changed

+109
-95
lines changed

src/backend/utils/adt/cash.c

Lines changed: 109 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@
3030
#include "utils/numeric.h"
3131
#include "utils/pg_locale.h"
3232

33-
#define CASH_BUFSZ 36
34-
35-
#define TERMINATOR (CASH_BUFSZ - 1)
36-
#define LAST_PAREN (TERMINATOR - 1)
37-
#define LAST_DIGIT (LAST_PAREN - 1)
38-
3933

4034
/*************************************************************************
4135
* Private routines
@@ -107,13 +101,13 @@ cash_in(PG_FUNCTION_ARGS)
107101
Cash value = 0;
108102
Cash dec = 0;
109103
Cash sgn = 1;
110-
int seen_dot = 0;
104+
bool seen_dot = false;
111105
const char *s = str;
112106
int fpoint;
113-
char dsymbol,
114-
ssymbol,
115-
psymbol;
116-
const char *nsymbol,
107+
char dsymbol;
108+
const char *ssymbol,
109+
*psymbol,
110+
*nsymbol,
117111
*csymbol;
118112
struct lconv *lconvert = PGLC_localeconv();
119113

@@ -131,18 +125,22 @@ cash_in(PG_FUNCTION_ARGS)
131125
if (fpoint < 0 || fpoint > 10)
132126
fpoint = 2; /* best guess in this case, I think */
133127

134-
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
135-
if (*lconvert->mon_thousands_sep != '\0')
136-
ssymbol = *lconvert->mon_thousands_sep;
128+
/* we restrict dsymbol to be a single byte, but not the other symbols */
129+
if (*lconvert->mon_decimal_point != '\0' &&
130+
lconvert->mon_decimal_point[1] == '\0')
131+
dsymbol = *lconvert->mon_decimal_point;
137132
else
138-
/* ssymbol should not equal dsymbol */
139-
ssymbol = (dsymbol != ',') ? ',' : '.';
140-
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
141-
psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
142-
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
133+
dsymbol = '.';
134+
if (*lconvert->mon_thousands_sep != '\0')
135+
ssymbol = lconvert->mon_thousands_sep;
136+
else /* ssymbol should not equal dsymbol */
137+
ssymbol = (dsymbol != ',') ? "," : ".";
138+
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
139+
psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
140+
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
143141

144142
#ifdef CASHDEBUG
145-
printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
143+
printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
146144
fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
147145
#endif
148146

@@ -164,22 +162,20 @@ cash_in(PG_FUNCTION_ARGS)
164162
{
165163
sgn = -1;
166164
s += strlen(nsymbol);
167-
#ifdef CASHDEBUG
168-
printf("cashin- negative symbol; string is '%s'\n", s);
169-
#endif
170165
}
171166
else if (*s == '(')
172167
{
173168
sgn = -1;
174169
s++;
175170
}
176-
else if (*s == psymbol)
177-
s++;
171+
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
172+
s += strlen(psymbol);
178173

179174
#ifdef CASHDEBUG
180175
printf("cashin- string is '%s'\n", s);
181176
#endif
182177

178+
/* allow whitespace and currency symbol after the sign, too */
183179
while (isspace((unsigned char) *s))
184180
s++;
185181
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
@@ -189,7 +185,7 @@ cash_in(PG_FUNCTION_ARGS)
189185
printf("cashin- string is '%s'\n", s);
190186
#endif
191187

192-
for (;; s++)
188+
for (; *s; s++)
193189
{
194190
/* we look for digits as long as we have found less */
195191
/* than the required number of decimal places */
@@ -203,33 +199,44 @@ cash_in(PG_FUNCTION_ARGS)
203199
/* decimal point? then start counting fractions... */
204200
else if (*s == dsymbol && !seen_dot)
205201
{
206-
seen_dot = 1;
202+
seen_dot = true;
207203
}
208204
/* ignore if "thousands" separator, else we're done */
209-
else if (*s != ssymbol)
210-
{
211-
/* round off */
212-
if (isdigit((unsigned char) *s) && *s >= '5')
213-
value++;
214-
215-
/* adjust for less than required decimal places */
216-
for (; dec < fpoint; dec++)
217-
value *= 10;
218-
205+
else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
206+
s += strlen(ssymbol) - 1;
207+
else
219208
break;
220-
}
221209
}
222210

223-
/* should only be trailing digits followed by whitespace or right paren */
211+
/* round off if there's another digit */
212+
if (isdigit((unsigned char) *s) && *s >= '5')
213+
value++;
214+
215+
/* adjust for less than required decimal places */
216+
for (; dec < fpoint; dec++)
217+
value *= 10;
218+
219+
/*
220+
* should only be trailing digits followed by whitespace, right paren,
221+
* or possibly a trailing minus sign
222+
*/
224223
while (isdigit((unsigned char) *s))
225224
s++;
226-
while (isspace((unsigned char) *s) || *s == ')')
227-
s++;
228-
229-
if (*s != '\0')
230-
ereport(ERROR,
231-
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
232-
errmsg("invalid input syntax for type money: \"%s\"", str)));
225+
while (*s)
226+
{
227+
if (isspace((unsigned char) *s) || *s == ')')
228+
s++;
229+
else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
230+
{
231+
sgn = -1;
232+
s += strlen(nsymbol);
233+
}
234+
else
235+
ereport(ERROR,
236+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
237+
errmsg("invalid input syntax for type money: \"%s\"",
238+
str)));
239+
}
233240

234241
result = value * sgn;
235242

@@ -242,26 +249,24 @@ cash_in(PG_FUNCTION_ARGS)
242249

243250

244251
/* cash_out()
245-
* Function to convert cash to a dollars and cents representation.
246-
* XXX HACK This code appears to assume US conventions for
247-
* positive-valued amounts. - tgl 97/04/14
252+
* Function to convert cash to a dollars and cents representation, using
253+
* the lc_monetary locale's formatting.
248254
*/
249255
Datum
250256
cash_out(PG_FUNCTION_ARGS)
251257
{
252258
Cash value = PG_GETARG_CASH(0);
253259
char *result;
254-
char buf[CASH_BUFSZ];
255-
int minus = 0;
256-
int count = LAST_DIGIT;
257-
int point_pos;
258-
int ssymbol_position = 0;
260+
char buf[128];
261+
char *bufptr;
262+
bool minus = false;
263+
int digit_pos;
259264
int points,
260265
mon_group;
261-
char ssymbol;
262-
const char *csymbol,
263-
*nsymbol;
264266
char dsymbol;
267+
const char *ssymbol,
268+
*csymbol,
269+
*nsymbol;
265270
char convention;
266271
struct lconv *lconvert = PGLC_localeconv();
267272

@@ -279,69 +284,78 @@ cash_out(PG_FUNCTION_ARGS)
279284
mon_group = 3;
280285

281286
convention = lconvert->n_sign_posn;
282-
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
283-
if (*lconvert->mon_thousands_sep != '\0')
284-
ssymbol = *lconvert->mon_thousands_sep;
285-
else
286-
/* ssymbol should not equal dsymbol */
287-
ssymbol = (dsymbol != ',') ? ',' : '.';
288-
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
289-
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
290-
291-
point_pos = LAST_DIGIT - points;
292287

293-
point_pos -= (points - 1) / mon_group;
294-
ssymbol_position = point_pos % (mon_group + 1);
288+
/* we restrict dsymbol to be a single byte, but not the other symbols */
289+
if (*lconvert->mon_decimal_point != '\0' &&
290+
lconvert->mon_decimal_point[1] == '\0')
291+
dsymbol = *lconvert->mon_decimal_point;
292+
else
293+
dsymbol = '.';
294+
if (*lconvert->mon_thousands_sep != '\0')
295+
ssymbol = lconvert->mon_thousands_sep;
296+
else /* ssymbol should not equal dsymbol */
297+
ssymbol = (dsymbol != ',') ? "," : ".";
298+
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
299+
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
295300

296301
/* we work with positive amounts and add the minus sign at the end */
297302
if (value < 0)
298303
{
299-
minus = 1;
304+
minus = true;
300305
value = -value;
301306
}
302307

303-
/* allow for trailing negative strings */
304-
MemSet(buf, ' ', CASH_BUFSZ);
305-
buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
308+
/* we build the result string right-to-left in buf[] */
309+
bufptr = buf + sizeof(buf) - 1;
310+
*bufptr = '\0';
306311

307-
while (value || count > (point_pos - 2))
312+
/*
313+
* Generate digits till there are no non-zero digits left and we emitted
314+
* at least one to the left of the decimal point. digit_pos is the
315+
* current digit position, with zero as the digit just left of the decimal
316+
* point, increasing to the right.
317+
*/
318+
digit_pos = points;
319+
do
308320
{
309-
if (points && count == point_pos)
310-
buf[count--] = dsymbol;
311-
else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
312-
buf[count--] = ssymbol;
321+
if (points && digit_pos == 0)
322+
{
323+
/* insert decimal point */
324+
*(--bufptr) = dsymbol;
325+
}
326+
else if (digit_pos < points && (digit_pos % mon_group) == 0)
327+
{
328+
/* insert thousands sep */
329+
bufptr -= strlen(ssymbol);
330+
memcpy(bufptr, ssymbol, strlen(ssymbol));
331+
}
313332

314-
buf[count--] = ((uint64) value % 10) + '0';
333+
*(--bufptr) = ((uint64) value % 10) + '0';
315334
value = ((uint64) value) / 10;
316-
}
317-
318-
strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
319-
count -= strlen(csymbol) - 1;
335+
digit_pos--;
336+
} while (value || digit_pos >= 0);
320337

321-
/*
322-
* If points == 0 and the number of digits % mon_group == 0, the code
323-
* above adds a trailing ssymbol on the far right, so remove it.
324-
*/
325-
if (buf[LAST_DIGIT] == ssymbol)
326-
buf[LAST_DIGIT] = '\0';
338+
/* prepend csymbol */
339+
bufptr -= strlen(csymbol);
340+
memcpy(bufptr, csymbol, strlen(csymbol));
327341

328342
/* see if we need to signify negative amount */
329343
if (minus)
330344
{
331-
result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
345+
result = palloc(strlen(bufptr) + strlen(nsymbol) + 3);
332346

333347
/* Position code of 0 means use parens */
334348
if (convention == 0)
335-
sprintf(result, "(%s)", buf + count);
349+
sprintf(result, "(%s)", bufptr);
336350
else if (convention == 2)
337-
sprintf(result, "%s%s", buf + count, nsymbol);
351+
sprintf(result, "%s%s", bufptr, nsymbol);
338352
else
339-
sprintf(result, "%s%s", nsymbol, buf + count);
353+
sprintf(result, "%s%s", nsymbol, bufptr);
340354
}
341355
else
342356
{
343-
result = palloc(CASH_BUFSZ + 2 - count);
344-
strcpy(result, buf + count);
357+
/* just emit what we have */
358+
result = pstrdup(bufptr);
345359
}
346360

347361
PG_RETURN_CSTRING(result);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy