2626
2727static const char * num_word (Cash value );
2828
29- /* when we go to 64 bit values we will have to modify this */
30- #define CASH_BUFSZ 24
31-
32- #define TERMINATOR (CASH_BUFSZ - 1)
33- #define LAST_PAREN (TERMINATOR - 1)
34- #define LAST_DIGIT (LAST_PAREN - 1)
35-
3629
3730/*
3831 * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
@@ -71,14 +64,14 @@ cash_in(PG_FUNCTION_ARGS)
7164 Cash value = 0 ;
7265 Cash dec = 0 ;
7366 Cash sgn = 1 ;
74- int seen_dot = 0 ;
67+ bool seen_dot = false ;
7568 const char * s = str ;
7669 int fpoint ;
77- char * csymbol ;
78- char dsymbol ,
79- ssymbol ,
80- psymbol ,
81- * nsymbol ;
70+ char dsymbol ;
71+ const char * ssymbol ,
72+ * psymbol ,
73+ * nsymbol ,
74+ * csymbol ;
8275
8376 struct lconv * lconvert = PGLC_localeconv ();
8477
@@ -96,14 +89,22 @@ cash_in(PG_FUNCTION_ARGS)
9689 if (fpoint < 0 || fpoint > 10 )
9790 fpoint = 2 ; /* best guess in this case, I think */
9891
99- dsymbol = ((* lconvert -> mon_decimal_point != '\0' ) ? * lconvert -> mon_decimal_point : '.' );
100- ssymbol = ((* lconvert -> mon_thousands_sep != '\0' ) ? * lconvert -> mon_thousands_sep : ',' );
101- csymbol = ((* lconvert -> currency_symbol != '\0' ) ? lconvert -> currency_symbol : "$" );
102- psymbol = ((* lconvert -> positive_sign != '\0' ) ? * lconvert -> positive_sign : '+' );
103- nsymbol = ((* lconvert -> negative_sign != '\0' ) ? lconvert -> negative_sign : "-" );
92+ /* we restrict dsymbol to be a single byte, but not the other symbols */
93+ if (* lconvert -> mon_decimal_point != '\0' &&
94+ lconvert -> mon_decimal_point [1 ] == '\0' )
95+ dsymbol = * lconvert -> mon_decimal_point ;
96+ else
97+ dsymbol = '.' ;
98+ if (* lconvert -> mon_thousands_sep != '\0' )
99+ ssymbol = lconvert -> mon_thousands_sep ;
100+ else /* ssymbol should not equal dsymbol */
101+ ssymbol = (dsymbol != ',' ) ? "," : "." ;
102+ csymbol = (* lconvert -> currency_symbol != '\0' ) ? lconvert -> currency_symbol : "$" ;
103+ psymbol = (* lconvert -> positive_sign != '\0' ) ? lconvert -> positive_sign : "+" ;
104+ nsymbol = (* lconvert -> negative_sign != '\0' ) ? lconvert -> negative_sign : "-" ;
104105
105106#ifdef CASHDEBUG
106- printf ("cashin- precision '%d'; decimal '%c'; thousands '%c '; currency '%s'; positive '%c '; negative '%s'\n" ,
107+ printf ("cashin- precision '%d'; decimal '%c'; thousands '%s '; currency '%s'; positive '%s '; negative '%s'\n" ,
107108 fpoint , dsymbol , ssymbol , csymbol , psymbol , nsymbol );
108109#endif
109110
@@ -124,23 +125,20 @@ cash_in(PG_FUNCTION_ARGS)
124125 {
125126 sgn = -1 ;
126127 s += strlen (nsymbol );
127- #ifdef CASHDEBUG
128- printf ("cashin- negative symbol; string is '%s'\n" , s );
129- #endif
130128 }
131129 else if (* s == '(' )
132130 {
133131 sgn = -1 ;
134132 s ++ ;
135-
136133 }
137- else if (* s == psymbol )
138- s ++ ;
134+ else if (strncmp ( s , psymbol , strlen ( psymbol )) == 0 )
135+ s += strlen ( psymbol ) ;
139136
140137#ifdef CASHDEBUG
141138 printf ("cashin- string is '%s'\n" , s );
142139#endif
143140
141+ /* allow whitespace and currency symbol after the sign, too */
144142 while (isspace ((unsigned char ) * s ))
145143 s ++ ;
146144 if (strncmp (s , csymbol , strlen (csymbol )) == 0 )
@@ -150,7 +148,7 @@ cash_in(PG_FUNCTION_ARGS)
150148 printf ("cashin- string is '%s'\n" , s );
151149#endif
152150
153- for (;; s ++ )
151+ for (; * s ; s ++ )
154152 {
155153 /* we look for digits as long as we have found less */
156154 /* than the required number of decimal places */
@@ -164,30 +162,44 @@ cash_in(PG_FUNCTION_ARGS)
164162 /* decimal point? then start counting fractions... */
165163 else if (* s == dsymbol && !seen_dot )
166164 {
167- seen_dot = 1 ;
165+ seen_dot = true ;
168166 }
169167 /* ignore if "thousands" separator, else we're done */
170- else if (* s != ssymbol )
171- {
172- /* round off */
173- if (isdigit ((unsigned char ) * s ) && * s >= '5' )
174- value ++ ;
175-
176- /* adjust for less than required decimal places */
177- for (; dec < fpoint ; dec ++ )
178- value *= 10 ;
179-
168+ else if (strncmp (s , ssymbol , strlen (ssymbol )) == 0 )
169+ s += strlen (ssymbol ) - 1 ;
170+ else
180171 break ;
181- }
182172 }
183173
184- while (isspace ((unsigned char ) * s ) || * s == '0' || * s == ')' )
185- s ++ ;
174+ /* round off if there's another digit */
175+ if (isdigit ((unsigned char ) * s ) && * s >= '5' )
176+ value ++ ;
186177
187- if (* s != '\0' )
188- ereport (ERROR ,
189- (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
190- errmsg ("invalid input syntax for type money: \"%s\"" , str )));
178+ /* adjust for less than required decimal places */
179+ for (; dec < fpoint ; dec ++ )
180+ value *= 10 ;
181+
182+ /*
183+ * should only be trailing digits followed by whitespace, right paren,
184+ * or possibly a trailing minus sign
185+ */
186+ while (isdigit ((unsigned char ) * s ))
187+ s ++ ;
188+ while (* s )
189+ {
190+ if (isspace ((unsigned char ) * s ) || * s == ')' )
191+ s ++ ;
192+ else if (strncmp (s , nsymbol , strlen (nsymbol )) == 0 )
193+ {
194+ sgn = -1 ;
195+ s += strlen (nsymbol );
196+ }
197+ else
198+ ereport (ERROR ,
199+ (errcode (ERRCODE_INVALID_TEXT_REPRESENTATION ),
200+ errmsg ("invalid input syntax for type money: \"%s\"" ,
201+ str )));
202+ }
191203
192204 result = value * sgn ;
193205
@@ -200,25 +212,23 @@ cash_in(PG_FUNCTION_ARGS)
200212
201213
202214/* cash_out()
203- * Function to convert cash to a dollars and cents representation.
204- * XXX HACK This code appears to assume US conventions for
205- * positive-valued amounts. - tgl 97/04/14
215+ * Function to convert cash to a dollars and cents representation, using
216+ * the lc_monetary locale's formatting.
206217 */
207218Datum
208219cash_out (PG_FUNCTION_ARGS )
209220{
210221 Cash value = PG_GETARG_CASH (0 );
211222 char * result ;
212- char buf [CASH_BUFSZ ];
213- int minus = 0 ;
214- int count = LAST_DIGIT ;
215- int point_pos ;
216- int comma_position = 0 ;
223+ char buf [128 ];
224+ char * bufptr ;
225+ bool minus = false;
226+ int digit_pos ;
217227 int points ,
218228 mon_group ;
219- char comma ;
220- char * csymbol ,
221- dsymbol ,
229+ char dsymbol ;
230+ const char * ssymbol ,
231+ * csymbol ,
222232 * nsymbol ;
223233 char convention ;
224234
@@ -237,66 +247,79 @@ cash_out(PG_FUNCTION_ARGS)
237247 if (mon_group <= 0 || mon_group > 6 )
238248 mon_group = 3 ;
239249
240- comma = ((* lconvert -> mon_thousands_sep != '\0' ) ? * lconvert -> mon_thousands_sep : ',' );
241250 convention = lconvert -> n_sign_posn ;
242- dsymbol = ((* lconvert -> mon_decimal_point != '\0' ) ? * lconvert -> mon_decimal_point : '.' );
243- csymbol = ((* lconvert -> currency_symbol != '\0' ) ? lconvert -> currency_symbol : "$" );
244- nsymbol = ((* lconvert -> negative_sign != '\0' ) ? lconvert -> negative_sign : "-" );
245-
246- point_pos = LAST_DIGIT - points ;
247251
248- /* allow more than three decimal points and separate them */
249- if (comma )
250- {
251- point_pos -= (points - 1 ) / mon_group ;
252- comma_position = point_pos % (mon_group + 1 );
253- }
252+ /* we restrict dsymbol to be a single byte, but not the other symbols */
253+ if (* lconvert -> mon_decimal_point != '\0' &&
254+ lconvert -> mon_decimal_point [1 ] == '\0' )
255+ dsymbol = * lconvert -> mon_decimal_point ;
256+ else
257+ dsymbol = '.' ;
258+ if (* lconvert -> mon_thousands_sep != '\0' )
259+ ssymbol = lconvert -> mon_thousands_sep ;
260+ else /* ssymbol should not equal dsymbol */
261+ ssymbol = (dsymbol != ',' ) ? "," : "." ;
262+ csymbol = (* lconvert -> currency_symbol != '\0' ) ? lconvert -> currency_symbol : "$" ;
263+ nsymbol = (* lconvert -> negative_sign != '\0' ) ? lconvert -> negative_sign : "-" ;
254264
255265 /* we work with positive amounts and add the minus sign at the end */
256266 if (value < 0 )
257267 {
258- minus = 1 ;
268+ minus = true ;
259269 value = - value ;
260270 }
261271
262- /* allow for trailing negative strings */
263- MemSet ( buf , ' ' , CASH_BUFSZ ) ;
264- buf [ TERMINATOR ] = buf [ LAST_PAREN ] = '\0' ;
272+ /* we build the result string right-to-left in buf[] */
273+ bufptr = buf + sizeof ( buf ) - 1 ;
274+ * bufptr = '\0' ;
265275
266- while (value || count > (point_pos - 2 ))
276+ /*
277+ * Generate digits till there are no non-zero digits left and we emitted
278+ * at least one to the left of the decimal point. digit_pos is the
279+ * current digit position, with zero as the digit just left of the decimal
280+ * point, increasing to the right.
281+ */
282+ digit_pos = points ;
283+ do
267284 {
268- if (points && count == point_pos )
269- buf [count -- ] = dsymbol ;
270- else if (comma && count % (mon_group + 1 ) == comma_position )
271- buf [count -- ] = comma ;
285+ if (points && digit_pos == 0 )
286+ {
287+ /* insert decimal point */
288+ * (-- bufptr ) = dsymbol ;
289+ }
290+ else if (digit_pos < points && (digit_pos % mon_group ) == 0 )
291+ {
292+ /* insert thousands sep */
293+ bufptr -= strlen (ssymbol );
294+ memcpy (bufptr , ssymbol , strlen (ssymbol ));
295+ }
272296
273- buf [ count -- ] = ((unsigned int ) value % 10 ) + '0' ;
297+ * ( -- bufptr ) = ((unsigned int ) value % 10 ) + '0' ;
274298 value = ((unsigned int ) value ) / 10 ;
275- }
276-
277- strncpy ((buf + count - strlen (csymbol ) + 1 ), csymbol , strlen (csymbol ));
278- count -= strlen (csymbol ) - 1 ;
299+ digit_pos -- ;
300+ } while (value || digit_pos >= 0 );
279301
280- if (buf [LAST_DIGIT ] == ',' )
281- buf [LAST_DIGIT ] = buf [LAST_PAREN ];
302+ /* prepend csymbol */
303+ bufptr -= strlen (csymbol );
304+ memcpy (bufptr , csymbol , strlen (csymbol ));
282305
283306 /* see if we need to signify negative amount */
284307 if (minus )
285308 {
286- result = palloc (CASH_BUFSZ + 2 - count + strlen (nsymbol ));
309+ result = palloc (strlen ( bufptr ) + strlen (nsymbol ) + 3 );
287310
288311 /* Position code of 0 means use parens */
289312 if (convention == 0 )
290- sprintf (result , "(%s)" , buf + count );
313+ sprintf (result , "(%s)" , bufptr );
291314 else if (convention == 2 )
292- sprintf (result , "%s%s" , buf + count , nsymbol );
315+ sprintf (result , "%s%s" , bufptr , nsymbol );
293316 else
294- sprintf (result , "%s%s" , nsymbol , buf + count );
317+ sprintf (result , "%s%s" , nsymbol , bufptr );
295318 }
296319 else
297320 {
298- result = palloc ( CASH_BUFSZ + 2 - count );
299- strcpy ( result , buf + count );
321+ /* just emit what we have */
322+ result = pstrdup ( bufptr );
300323 }
301324
302325 PG_RETURN_CSTRING (result );
0 commit comments