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)
107101Cash value = 0 ;
108102Cash dec = 0 ;
109103Cash sgn = 1 ;
110- int seen_dot = 0 ;
104+ bool seen_dot = false ;
111105const char * s = str ;
112106int 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 ;
118112struct lconv * lconvert = PGLC_localeconv ();
119113
@@ -131,18 +125,22 @@ cash_in(PG_FUNCTION_ARGS)
131125if (fpoint < 0 || fpoint > 10 )
132126fpoint = 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 ;
137132else
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" ,
146144fpoint ,dsymbol ,ssymbol ,csymbol ,psymbol ,nsymbol );
147145#endif
148146
@@ -164,22 +162,20 @@ cash_in(PG_FUNCTION_ARGS)
164162{
165163sgn = -1 ;
166164s += strlen (nsymbol );
167- #ifdef CASHDEBUG
168- printf ("cashin- negative symbol; string is '%s'\n" ,s );
169- #endif
170165}
171166else if (* s == '(' )
172167{
173168sgn = -1 ;
174169s ++ ;
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
180175printf ("cashin- string is '%s'\n" ,s );
181176#endif
182177
178+ /* allow whitespace and currency symbol after the sign, too */
183179while (isspace ((unsignedchar )* s ))
184180s ++ ;
185181if (strncmp (s ,csymbol ,strlen (csymbol ))== 0 )
@@ -189,7 +185,7 @@ cash_in(PG_FUNCTION_ARGS)
189185printf ("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... */
204200else 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 ((unsignedchar )* 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
219208break ;
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 ((unsignedchar )* 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+ */
224223while (isdigit ((unsignedchar )* s ))
225224s ++ ;
226- while (isspace ((unsignedchar )* 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 ((unsignedchar )* 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
234241result = 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 */
249255Datum
250256cash_out (PG_FUNCTION_ARGS )
251257{
252258Cash value = PG_GETARG_CASH (0 );
253259char * 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 ;
259264int points ,
260265mon_group ;
261- char ssymbol ;
262- const char * csymbol ,
263- * nsymbol ;
264266char dsymbol ;
267+ const char * ssymbol ,
268+ * csymbol ,
269+ * nsymbol ;
265270char convention ;
266271struct lconv * lconvert = PGLC_localeconv ();
267272
@@ -279,69 +284,78 @@ cash_out(PG_FUNCTION_ARGS)
279284mon_group = 3 ;
280285
281286convention = 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 */
297302if (value < 0 )
298303{
299- minus = 1 ;
304+ minus = true ;
300305value = - 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' ;
315334value = ((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 */
329343if (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 */
334348if (convention == 0 )
335- sprintf (result ,"(%s)" ,buf + count );
349+ sprintf (result ,"(%s)" ,bufptr );
336350else if (convention == 2 )
337- sprintf (result ,"%s%s" ,buf + count ,nsymbol );
351+ sprintf (result ,"%s%s" ,bufptr ,nsymbol );
338352else
339- sprintf (result ,"%s%s" ,nsymbol ,buf + count );
353+ sprintf (result ,"%s%s" ,nsymbol ,bufptr );
340354}
341355else
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
347361PG_RETURN_CSTRING (result );