1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2023 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
24 #include "../libio/libioP.h"
25 #include "../libio/strfile.h"
30 #include "../locale/localeinfo.h"
31 #include <bits/floatn.h>
32 #include <stdio-common/grouping_iterator.h>
33 #include <printf_buffer.h>
35 #define to_digit(Ch) ((Ch) - '0')
38 /* We use this code also for the extended locale handling where the
39 function gets as an additional argument the locale which has to be
40 used. To access the values we have to redefine the _NL_CURRENT
43 #define _NL_CURRENT(category, item) \
44 (current->values[_NL_ITEM_INDEX (item)].string)
47 /* We have to overcome some problems with this implementation. On the
48 one hand the strfmon() function is specified in XPG4 and of course
49 it has to follow this. But on the other hand POSIX.2 specifies
50 some information in the LC_MONETARY category which should be used,
51 too. Some of the information contradicts the information which can
52 be specified in format string. */
54 __vstrfmon_l_buffer (struct __printf_buffer
*buf
, locale_t loc
,
55 const char *fmt
, va_list ap
, unsigned int flags
)
57 struct __locale_data
*current
= loc
->__locales
[LC_MONETARY
];
58 struct printf_info info
;
60 /* Loop through the format-string. */
61 while (*fmt
!= '\0' && !__printf_buffer_has_failed (buf
))
63 /* The floating-point value to output. */
68 #if __HAVE_DISTINCT_FLOAT128
74 int print_curr_symbol
;
89 int other_sep_by_space
;
91 int other_cs_precedes
;
92 const char *sign_string
;
93 const char *other_sign_string
;
94 const char *currency_symbol
;
95 size_t currency_symbol_len
;
100 /* Process all character which do not introduce a format
104 __printf_buffer_putc (buf
, *fmt
++);
108 /* "%%" means a single '%' character. */
111 __printf_buffer_putc (buf
, *++fmt
);
116 /* Defaults for formatting. */
117 int_format
= 0; /* Use international curr. symbol */
118 print_curr_symbol
= 1; /* Print the currency symbol. */
119 left_prec
= -1; /* No left precision specified. */
120 right_prec
= -1; /* No right precision specified. */
121 group
= 1; /* Print digits grouped. */
122 pad
= ' '; /* Fill character is <SP>. */
123 is_long_double
= 0; /* Double argument by default. */
124 is_binary128
= 0; /* Long double argument by default. */
125 p_sign_posn
= -2; /* This indicates whether the */
126 n_sign_posn
= -2; /* '(' flag is given. */
127 width
= -1; /* No width specified so far. */
128 left
= 0; /* Right justified by default. */
130 /* Parse group characters. */
135 case '=': /* Set fill character. */
140 __set_errno (EINVAL
);
141 __printf_buffer_mark_failed (buf
);
145 case '^': /* Don't group digits. */
148 case '+': /* Use +/- for sign of number. */
149 if (n_sign_posn
!= -2)
151 __set_errno (EINVAL
);
152 __printf_buffer_mark_failed (buf
);
155 p_sign_posn
= *_NL_CURRENT (LC_MONETARY
, P_SIGN_POSN
);
156 n_sign_posn
= *_NL_CURRENT (LC_MONETARY
, N_SIGN_POSN
);
158 case '(': /* Use ( ) for negative sign. */
159 if (n_sign_posn
!= -2)
161 __set_errno (EINVAL
);
162 __printf_buffer_mark_failed (buf
);
168 case '!': /* Don't print the currency symbol. */
169 print_curr_symbol
= 0;
171 case '-': /* Print left justified. */
175 /* Will stop the loop. */;
182 /* Parse field width. */
183 width
= to_digit (*fmt
);
185 while (isdigit (*++fmt
))
187 int val
= to_digit (*fmt
);
189 if (width
> LONG_MAX
/ 10
190 || (width
== LONG_MAX
&& val
> LONG_MAX
% 10))
193 __printf_buffer_mark_failed (buf
);
197 width
= width
* 10 + val
;
201 /* Recognize left precision. */
204 if (!isdigit (*++fmt
))
206 __set_errno (EINVAL
);
207 __printf_buffer_mark_failed (buf
);
210 left_prec
= to_digit (*fmt
);
212 while (isdigit (*++fmt
))
215 left_prec
+= to_digit (*fmt
);
219 /* Recognize right precision. */
222 if (!isdigit (*++fmt
))
224 __set_errno (EINVAL
);
225 __printf_buffer_mark_failed (buf
);
228 right_prec
= to_digit (*fmt
);
230 while (isdigit (*++fmt
))
233 right_prec
+= to_digit (*fmt
);
237 /* Handle modifier. This is an extension. */
241 if (__glibc_likely ((flags
& STRFMON_LDBL_IS_DBL
) == 0))
243 #if __HAVE_DISTINCT_FLOAT128
244 if (__glibc_likely ((flags
& STRFMON_LDBL_USES_FLOAT128
) != 0))
245 is_binary128
= is_long_double
;
249 /* Handle format specifier. */
253 case 'i': { /* Use international currency symbol. */
254 const char *int_curr_symbol
;
256 int_curr_symbol
= _NL_CURRENT (LC_MONETARY
, INT_CURR_SYMBOL
);
257 strncpy(int_symbol
, int_curr_symbol
, 3);
258 int_symbol
[3] = '\0';
260 currency_symbol_len
= 3;
261 currency_symbol
= &int_symbol
[0];
262 space_char
= int_curr_symbol
[3];
266 case 'n': /* Use national currency symbol. */
267 currency_symbol
= _NL_CURRENT (LC_MONETARY
, CURRENCY_SYMBOL
);
268 currency_symbol_len
= strlen (currency_symbol
);
272 default: /* Any unrecognized format is an error. */
273 __set_errno (EINVAL
);
274 __printf_buffer_mark_failed (buf
);
278 /* If not specified by the format string now find the values for
279 the format specification. */
280 if (p_sign_posn
== -2)
281 p_sign_posn
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SIGN_POSN
: P_SIGN_POSN
);
282 if (n_sign_posn
== -2)
283 n_sign_posn
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SIGN_POSN
: N_SIGN_POSN
);
285 if (right_prec
== -1)
287 right_prec
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_FRAC_DIGITS
: FRAC_DIGITS
);
289 if (right_prec
== '\377')
293 /* If we have to print the digits grouped determine how many
294 extra characters this means. */
295 if (group
&& left_prec
!= -1)
297 struct grouping_iterator it
;
298 __grouping_iterator_init (&it
, LC_MONETARY
, loc
, left_prec
);
299 left_prec
+= it
.separators
;
302 /* Now it's time to get the value. */
303 if (is_long_double
== 1)
305 #if __HAVE_DISTINCT_FLOAT128
306 if (is_binary128
== 1)
308 fpnum
.f128
= va_arg (ap
, _Float128
);
309 is_negative
= fpnum
.f128
< 0;
311 fpnum
.f128
= -fpnum
.f128
;
316 fpnum
.ldbl
= va_arg (ap
, long double);
317 is_negative
= fpnum
.ldbl
< 0;
319 fpnum
.ldbl
= -fpnum
.ldbl
;
324 fpnum
.dbl
= va_arg (ap
, double);
325 is_negative
= fpnum
.dbl
< 0;
327 fpnum
.dbl
= -fpnum
.dbl
;
330 /* We now know the sign of the value and can determine the format. */
333 sign_string
= _NL_CURRENT (LC_MONETARY
, NEGATIVE_SIGN
);
334 /* If the locale does not specify a character for the
335 negative sign we use a '-'. */
336 if (*sign_string
== '\0')
337 sign_string
= (const char *) "-";
338 cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_CS_PRECEDES
: N_CS_PRECEDES
);
339 sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SEP_BY_SPACE
: N_SEP_BY_SPACE
);
340 sign_posn
= n_sign_posn
;
342 other_sign_string
= _NL_CURRENT (LC_MONETARY
, POSITIVE_SIGN
);
343 other_cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_CS_PRECEDES
: P_CS_PRECEDES
);
344 other_sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SEP_BY_SPACE
: P_SEP_BY_SPACE
);
345 other_sign_posn
= p_sign_posn
;
349 sign_string
= _NL_CURRENT (LC_MONETARY
, POSITIVE_SIGN
);
350 cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_CS_PRECEDES
: P_CS_PRECEDES
);
351 sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SEP_BY_SPACE
: P_SEP_BY_SPACE
);
352 sign_posn
= p_sign_posn
;
354 other_sign_string
= _NL_CURRENT (LC_MONETARY
, NEGATIVE_SIGN
);
355 if (*other_sign_string
== '\0')
356 other_sign_string
= (const char *) "-";
357 other_cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_CS_PRECEDES
: N_CS_PRECEDES
);
358 other_sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SEP_BY_SPACE
: N_SEP_BY_SPACE
);
359 other_sign_posn
= n_sign_posn
;
362 /* Set default values for unspecified information. */
363 if (cs_precedes
!= 0)
365 if (other_cs_precedes
!= 0)
366 other_cs_precedes
= 1;
367 if (sep_by_space
== '\377')
369 if (other_sep_by_space
== '\377')
370 other_sep_by_space
= 0;
371 if (sign_posn
== '\377')
373 if (other_sign_posn
== '\377')
376 /* Check for degenerate cases */
377 if (sep_by_space
== 2)
380 || (sign_posn
== 1 && !cs_precedes
)
381 || (sign_posn
== 2 && cs_precedes
))
382 /* sign and symbol are not adjacent, so no separator */
385 if (other_sep_by_space
== 2)
387 if (other_sign_posn
== 0
388 || (other_sign_posn
== 1 && !other_cs_precedes
)
389 || (other_sign_posn
== 2 && other_cs_precedes
))
390 /* sign and symbol are not adjacent, so no separator */
391 other_sep_by_space
= 0;
394 /* Set the left precision and padding needed for alignment */
402 /* Set left_pad to number of spaces needed to align positive
403 and negative formats */
406 int other_left_bytes
= 0;
408 /* Work out number of bytes for currency string and separator
409 preceding the value */
412 left_bytes
+= currency_symbol_len
;
413 if (sep_by_space
!= 0)
417 if (other_cs_precedes
)
419 other_left_bytes
+= currency_symbol_len
;
420 if (other_sep_by_space
!= 0)
424 /* Work out number of bytes for the sign (or left parenthesis)
425 preceding the value */
426 if (sign_posn
== 0 && is_negative
)
428 else if (sign_posn
== 1)
429 left_bytes
+= strlen (sign_string
);
430 else if (cs_precedes
&& (sign_posn
== 3 || sign_posn
== 4))
431 left_bytes
+= strlen (sign_string
);
433 if (other_sign_posn
== 0 && !is_negative
)
435 else if (other_sign_posn
== 1)
436 other_left_bytes
+= strlen (other_sign_string
);
437 else if (other_cs_precedes
438 && (other_sign_posn
== 3 || other_sign_posn
== 4))
439 other_left_bytes
+= strlen (other_sign_string
);
441 /* Compare the number of bytes preceding the value for
442 each format, and set the padding accordingly */
443 if (other_left_bytes
> left_bytes
)
444 left_pad
= other_left_bytes
- left_bytes
;
449 /* Perhaps we'll someday make these things configurable so
450 better start using symbolic names now. */
451 #define left_paren '('
452 #define right_paren ')'
454 char *startp
= buf
->write_ptr
;
456 __printf_buffer_pad (buf
, ' ', left_pad
);
458 if (sign_posn
== 0 && is_negative
)
459 __printf_buffer_putc (buf
, left_paren
);
463 if (sign_posn
!= 0 && sign_posn
!= 2 && sign_posn
!= 4
466 __printf_buffer_puts (buf
, sign_string
);
467 if (sep_by_space
== 2)
468 __printf_buffer_putc (buf
, ' ');
471 if (print_curr_symbol
)
472 __printf_buffer_puts (buf
, currency_symbol
);
476 if (print_curr_symbol
&& sep_by_space
== 2)
477 __printf_buffer_putc (buf
, space_char
);
478 __printf_buffer_puts (buf
, sign_string
);
479 if (sep_by_space
== 1)
480 /* POSIX.2 and SUS are not clear on this case, but C99
481 says a space follows the adjacent-symbol-and-sign */
482 __printf_buffer_putc (buf
, ' ');
485 if (print_curr_symbol
&& sep_by_space
== 1)
486 __printf_buffer_putc (buf
, space_char
);
489 if (sign_posn
!= 0 && sign_posn
!= 2 && sign_posn
!= 3
490 && sign_posn
!= 4 && sign_posn
!= 5)
491 __printf_buffer_puts (buf
, sign_string
);
493 /* Print the number. */
494 memset (&info
, '\0', sizeof (info
));
495 info
.prec
= right_prec
;
496 info
.width
= left_prec
+ (right_prec
? (right_prec
+ 1) : 0);
498 info
.is_long_double
= is_long_double
;
499 info
.is_binary128
= is_binary128
;
502 info
.extra
= 1; /* This means use values from LC_MONETARY. */
505 __printf_fp_l_buffer (buf
, loc
, &info
, &ptr
);
506 if (__printf_buffer_has_failed (buf
))
513 if (sep_by_space
== 1)
514 __printf_buffer_putc (buf
, ' ');
515 __printf_buffer_puts (buf
, sign_string
);
518 if (print_curr_symbol
)
520 if ((sign_posn
== 3 && sep_by_space
== 2)
521 || (sign_posn
== 4 && sep_by_space
== 1)
522 || (sign_posn
== 2 && sep_by_space
== 1)
523 || (sign_posn
== 1 && sep_by_space
== 1)
524 || (sign_posn
== 0 && sep_by_space
== 1))
525 __printf_buffer_putc (buf
, space_char
);
526 __printf_buffer_write (buf
, currency_symbol
,
527 __strnlen (currency_symbol
,
528 currency_symbol_len
));
533 if (sep_by_space
== 2)
534 __printf_buffer_putc (buf
, ' ');
535 __printf_buffer_puts (buf
, sign_string
);
541 if (sep_by_space
== 2)
542 __printf_buffer_putc (buf
, ' ');
543 __printf_buffer_puts (buf
, sign_string
);
546 if (sign_posn
== 0 && is_negative
)
547 __printf_buffer_putc (buf
, right_paren
);
549 /* Now test whether the output width is filled. */
550 if (buf
->write_ptr
- startp
< width
)
552 size_t pad_width
= width
- (buf
->write_ptr
- startp
);
553 __printf_buffer_pad (buf
, ' ', pad_width
);
554 if (__printf_buffer_has_failed (buf
))
555 /* Implies length check. */
557 /* Left padding is already in the correct position.
558 Otherwise move the field contents in place. */
561 memmove (startp
+ pad_width
, startp
, buf
->write_ptr
- startp
);
562 memset (startp
, ' ', pad_width
);
569 __vstrfmon_l_internal (char *s
, size_t maxsize
, locale_t loc
,
570 const char *format
, va_list ap
, unsigned int flags
)
572 struct __printf_buffer buf
;
573 __printf_buffer_init (&buf
, s
, maxsize
, __printf_buffer_mode_strfmon
);
574 __vstrfmon_l_buffer (&buf
, loc
, format
, ap
, flags
);
575 __printf_buffer_putc (&buf
, '\0'); /* Terminate the string. */
576 if (__printf_buffer_has_failed (&buf
))
579 return buf
.write_ptr
- buf
.write_base
- 1; /* Exclude NUL byte. */
583 ___strfmon_l (char *s
, size_t maxsize
, locale_t loc
, const char *format
, ...)
587 va_start (ap
, format
);
589 ssize_t res
= __vstrfmon_l_internal (s
, maxsize
, loc
, format
, ap
, 0);
595 ldbl_strong_alias (___strfmon_l
, __strfmon_l
)
596 ldbl_weak_alias (___strfmon_l
, strfmon_l
)