1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2019 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, see
18 <http://www.gnu.org/licenses/>. */
25 #include "../libio/libioP.h"
26 #include "../libio/strfile.h"
31 #include "../locale/localeinfo.h"
34 #define out_char(Ch) \
36 if (dest >= s + maxsize - 1) \
38 __set_errno (E2BIG); \
45 #define out_string(String) \
47 const char *_s = (String); \
52 #define out_nstring(String, N) \
55 const char *_s = (String); \
60 #define to_digit(Ch) ((Ch) - '0')
63 /* We use this code also for the extended locale handling where the
64 function gets as an additional argument the locale which has to be
65 used. To access the values we have to redefine the _NL_CURRENT
68 #define _NL_CURRENT(category, item) \
69 (current->values[_NL_ITEM_INDEX (item)].string)
72 /* We have to overcome some problems with this implementation. On the
73 one hand the strfmon() function is specified in XPG4 and of course
74 it has to follow this. But on the other hand POSIX.2 specifies
75 some information in the LC_MONETARY category which should be used,
76 too. Some of the information contradicts the information which can
77 be specified in format string. */
79 __vstrfmon_l_internal (char *s
, size_t maxsize
, locale_t loc
,
80 const char *format
, va_list ap
, unsigned int flags
)
82 struct __locale_data
*current
= loc
->__locales
[LC_MONETARY
];
84 struct printf_info info
;
85 char *dest
; /* Pointer so copy the output. */
86 const char *fmt
; /* Pointer that walks through format. */
91 /* Loop through the format-string. */
94 /* The floating-point value to output. */
102 int print_curr_symbol
;
116 int other_sep_by_space
;
118 int other_cs_precedes
;
119 const char *sign_string
;
120 const char *other_sign_string
;
122 const char *currency_symbol
;
123 size_t currency_symbol_len
;
129 /* Process all character which do not introduce a format
137 /* "%%" means a single '%' character. */
145 /* Defaults for formatting. */
146 int_format
= 0; /* Use international curr. symbol */
147 print_curr_symbol
= 1; /* Print the currency symbol. */
148 left_prec
= -1; /* No left precision specified. */
149 right_prec
= -1; /* No right precision specified. */
150 group
= 1; /* Print digits grouped. */
151 pad
= ' '; /* Fill character is <SP>. */
152 is_long_double
= 0; /* Double argument by default. */
153 p_sign_posn
= -2; /* This indicates whether the */
154 n_sign_posn
= -2; /* '(' flag is given. */
155 width
= -1; /* No width specified so far. */
156 left
= 0; /* Right justified by default. */
158 /* Parse group characters. */
163 case '=': /* Set fill character. */
168 __set_errno (EINVAL
);
172 case '^': /* Don't group digits. */
175 case '+': /* Use +/- for sign of number. */
176 if (n_sign_posn
!= -2)
178 __set_errno (EINVAL
);
181 p_sign_posn
= *_NL_CURRENT (LC_MONETARY
, P_SIGN_POSN
);
182 n_sign_posn
= *_NL_CURRENT (LC_MONETARY
, N_SIGN_POSN
);
184 case '(': /* Use ( ) for negative sign. */
185 if (n_sign_posn
!= -2)
187 __set_errno (EINVAL
);
193 case '!': /* Don't print the currency symbol. */
194 print_curr_symbol
= 0;
196 case '-': /* Print left justified. */
200 /* Will stop the loop. */;
207 /* Parse field width. */
208 width
= to_digit (*fmt
);
210 while (isdigit (*++fmt
))
212 int val
= to_digit (*fmt
);
214 if (width
> LONG_MAX
/ 10
215 || (width
== LONG_MAX
&& val
> LONG_MAX
% 10))
221 width
= width
* 10 + val
;
224 /* If we don't have enough room for the demanded width we
225 can stop now and return an error. */
226 if (width
>= maxsize
- (dest
- s
))
233 /* Recognize left precision. */
236 if (!isdigit (*++fmt
))
238 __set_errno (EINVAL
);
241 left_prec
= to_digit (*fmt
);
243 while (isdigit (*++fmt
))
246 left_prec
+= to_digit (*fmt
);
250 /* Recognize right precision. */
253 if (!isdigit (*++fmt
))
255 __set_errno (EINVAL
);
258 right_prec
= to_digit (*fmt
);
260 while (isdigit (*++fmt
))
263 right_prec
+= to_digit (*fmt
);
267 /* Handle modifier. This is an extension. */
271 if (__glibc_likely ((flags
& STRFMON_LDBL_IS_DBL
) == 0))
275 /* Handle format specifier. */
279 case 'i': { /* Use international currency symbol. */
280 const char *int_curr_symbol
;
282 int_curr_symbol
= _NL_CURRENT (LC_MONETARY
, INT_CURR_SYMBOL
);
283 strncpy(int_symbol
, int_curr_symbol
, 3);
284 int_symbol
[3] = '\0';
286 currency_symbol_len
= 3;
287 currency_symbol
= &int_symbol
[0];
288 space_char
= int_curr_symbol
[3];
292 case 'n': /* Use national currency symbol. */
293 currency_symbol
= _NL_CURRENT (LC_MONETARY
, CURRENCY_SYMBOL
);
294 currency_symbol_len
= strlen (currency_symbol
);
298 default: /* Any unrecognized format is an error. */
299 __set_errno (EINVAL
);
303 /* If not specified by the format string now find the values for
304 the format specification. */
305 if (p_sign_posn
== -2)
306 p_sign_posn
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SIGN_POSN
: P_SIGN_POSN
);
307 if (n_sign_posn
== -2)
308 n_sign_posn
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SIGN_POSN
: N_SIGN_POSN
);
310 if (right_prec
== -1)
312 right_prec
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_FRAC_DIGITS
: FRAC_DIGITS
);
314 if (right_prec
== '\377')
318 /* If we have to print the digits grouped determine how many
319 extra characters this means. */
320 if (group
&& left_prec
!= -1)
321 left_prec
+= __guess_grouping (left_prec
,
322 _NL_CURRENT (LC_MONETARY
, MON_GROUPING
));
324 /* Now it's time to get the value. */
325 if (is_long_double
== 1)
327 fpnum
.ldbl
= va_arg (ap
, long double);
328 is_negative
= fpnum
.ldbl
< 0;
330 fpnum
.ldbl
= -fpnum
.ldbl
;
334 fpnum
.dbl
= va_arg (ap
, double);
335 is_negative
= fpnum
.dbl
< 0;
337 fpnum
.dbl
= -fpnum
.dbl
;
340 /* We now know the sign of the value and can determine the format. */
343 sign_string
= _NL_CURRENT (LC_MONETARY
, NEGATIVE_SIGN
);
344 /* If the locale does not specify a character for the
345 negative sign we use a '-'. */
346 if (*sign_string
== '\0')
347 sign_string
= (const char *) "-";
348 cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_CS_PRECEDES
: N_CS_PRECEDES
);
349 sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SEP_BY_SPACE
: N_SEP_BY_SPACE
);
350 sign_posn
= n_sign_posn
;
352 other_sign_string
= _NL_CURRENT (LC_MONETARY
, POSITIVE_SIGN
);
353 other_cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_CS_PRECEDES
: P_CS_PRECEDES
);
354 other_sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SEP_BY_SPACE
: P_SEP_BY_SPACE
);
355 other_sign_posn
= p_sign_posn
;
359 sign_string
= _NL_CURRENT (LC_MONETARY
, POSITIVE_SIGN
);
360 cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_CS_PRECEDES
: P_CS_PRECEDES
);
361 sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SEP_BY_SPACE
: P_SEP_BY_SPACE
);
362 sign_posn
= p_sign_posn
;
364 other_sign_string
= _NL_CURRENT (LC_MONETARY
, NEGATIVE_SIGN
);
365 if (*other_sign_string
== '\0')
366 other_sign_string
= (const char *) "-";
367 other_cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_CS_PRECEDES
: N_CS_PRECEDES
);
368 other_sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SEP_BY_SPACE
: N_SEP_BY_SPACE
);
369 other_sign_posn
= n_sign_posn
;
372 /* Set default values for unspecified information. */
373 if (cs_precedes
!= 0)
375 if (other_cs_precedes
!= 0)
376 other_cs_precedes
= 1;
377 if (sep_by_space
== '\377')
379 if (other_sep_by_space
== '\377')
380 other_sep_by_space
= 0;
381 if (sign_posn
== '\377')
383 if (other_sign_posn
== '\377')
386 /* Check for degenerate cases */
387 if (sep_by_space
== 2)
390 || (sign_posn
== 1 && !cs_precedes
)
391 || (sign_posn
== 2 && cs_precedes
))
392 /* sign and symbol are not adjacent, so no separator */
395 if (other_sep_by_space
== 2)
397 if (other_sign_posn
== 0
398 || (other_sign_posn
== 1 && !other_cs_precedes
)
399 || (other_sign_posn
== 2 && other_cs_precedes
))
400 /* sign and symbol are not adjacent, so no separator */
401 other_sep_by_space
= 0;
404 /* Set the left precision and padding needed for alignment */
412 /* Set left_pad to number of spaces needed to align positive
413 and negative formats */
416 int other_left_bytes
= 0;
418 /* Work out number of bytes for currency string and separator
419 preceding the value */
422 left_bytes
+= currency_symbol_len
;
423 if (sep_by_space
!= 0)
427 if (other_cs_precedes
)
429 other_left_bytes
+= currency_symbol_len
;
430 if (other_sep_by_space
!= 0)
434 /* Work out number of bytes for the sign (or left parenthesis)
435 preceding the value */
436 if (sign_posn
== 0 && is_negative
)
438 else if (sign_posn
== 1)
439 left_bytes
+= strlen (sign_string
);
440 else if (cs_precedes
&& (sign_posn
== 3 || sign_posn
== 4))
441 left_bytes
+= strlen (sign_string
);
443 if (other_sign_posn
== 0 && !is_negative
)
445 else if (other_sign_posn
== 1)
446 other_left_bytes
+= strlen (other_sign_string
);
447 else if (other_cs_precedes
448 && (other_sign_posn
== 3 || other_sign_posn
== 4))
449 other_left_bytes
+= strlen (other_sign_string
);
451 /* Compare the number of bytes preceding the value for
452 each format, and set the padding accordingly */
453 if (other_left_bytes
> left_bytes
)
454 left_pad
= other_left_bytes
- left_bytes
;
459 /* Perhaps we'll someday make these things configurable so
460 better start using symbolic names now. */
461 #define left_paren '('
462 #define right_paren ')'
464 startp
= dest
; /* Remember start so we can compute length. */
466 while (left_pad
-- > 0)
469 if (sign_posn
== 0 && is_negative
)
470 out_char (left_paren
);
474 if (sign_posn
!= 0 && sign_posn
!= 2 && sign_posn
!= 4
477 out_string (sign_string
);
478 if (sep_by_space
== 2)
482 if (print_curr_symbol
)
483 out_string (currency_symbol
);
487 if (print_curr_symbol
&& sep_by_space
== 2)
488 out_char (space_char
);
489 out_string (sign_string
);
490 if (sep_by_space
== 1)
491 /* POSIX.2 and SUS are not clear on this case, but C99
492 says a space follows the adjacent-symbol-and-sign */
496 if (print_curr_symbol
&& sep_by_space
== 1)
497 out_char (space_char
);
500 if (sign_posn
!= 0 && sign_posn
!= 2 && sign_posn
!= 3
501 && sign_posn
!= 4 && sign_posn
!= 5)
502 out_string (sign_string
);
504 /* Print the number. */
506 f
._sbf
._f
._lock
= NULL
;
508 _IO_init_internal (&f
._sbf
._f
, 0);
509 _IO_JUMPS (&f
._sbf
) = &_IO_str_jumps
;
510 _IO_str_init_static_internal (&f
, dest
, (s
+ maxsize
) - dest
, dest
);
511 /* We clear the last available byte so we can find out whether
512 the numeric representation is too long. */
513 s
[maxsize
- 1] = '\0';
515 memset (&info
, '\0', sizeof (info
));
516 info
.prec
= right_prec
;
517 info
.width
= left_prec
+ (right_prec
? (right_prec
+ 1) : 0);
519 info
.is_long_double
= is_long_double
;
522 info
.extra
= 1; /* This means use values from LC_MONETARY. */
525 done
= __printf_fp_l (&f
._sbf
._f
, loc
, &info
, &ptr
);
529 if (s
[maxsize
- 1] != '\0')
541 if (sep_by_space
== 1)
543 out_string (sign_string
);
546 if (print_curr_symbol
)
548 if ((sign_posn
== 3 && sep_by_space
== 2)
549 || (sign_posn
== 4 && sep_by_space
== 1)
550 || (sign_posn
== 2 && sep_by_space
== 1)
551 || (sign_posn
== 1 && sep_by_space
== 1)
552 || (sign_posn
== 0 && sep_by_space
== 1))
553 out_char (space_char
);
554 out_nstring (currency_symbol
, currency_symbol_len
);
559 if (sep_by_space
== 2)
561 out_string (sign_string
);
567 if (sep_by_space
== 2)
569 out_string (sign_string
);
572 if (sign_posn
== 0 && is_negative
)
573 out_char (right_paren
);
575 /* Now test whether the output width is filled. */
576 if (dest
- startp
< width
)
579 /* We simply have to fill using spaces. */
582 while (dest
- startp
< width
);
585 long int dist
= width
- (dest
- startp
);
586 for (char *cp
= dest
- 1; cp
>= startp
; --cp
)
592 startp
[--dist
] = ' ';
598 /* Terminate the string. */
605 ___strfmon_l (char *s
, size_t maxsize
, locale_t loc
, const char *format
, ...)
609 va_start (ap
, format
);
611 ssize_t res
= __vstrfmon_l_internal (s
, maxsize
, loc
, format
, ap
, 0);
617 ldbl_strong_alias (___strfmon_l
, __strfmon_l
)
618 ldbl_weak_alias (___strfmon_l
, strfmon_l
)