1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2021 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 <https://www.gnu.org/licenses/>. */
25 #include "../libio/libioP.h"
26 #include "../libio/strfile.h"
31 #include "../locale/localeinfo.h"
32 #include <bits/floatn.h>
35 #define out_char(Ch) \
37 if (dest >= s + maxsize - 1) \
39 __set_errno (E2BIG); \
46 #define out_string(String) \
48 const char *_s = (String); \
53 #define out_nstring(String, N) \
56 const char *_s = (String); \
61 #define to_digit(Ch) ((Ch) - '0')
64 /* We use this code also for the extended locale handling where the
65 function gets as an additional argument the locale which has to be
66 used. To access the values we have to redefine the _NL_CURRENT
69 #define _NL_CURRENT(category, item) \
70 (current->values[_NL_ITEM_INDEX (item)].string)
73 /* We have to overcome some problems with this implementation. On the
74 one hand the strfmon() function is specified in XPG4 and of course
75 it has to follow this. But on the other hand POSIX.2 specifies
76 some information in the LC_MONETARY category which should be used,
77 too. Some of the information contradicts the information which can
78 be specified in format string. */
80 __vstrfmon_l_internal (char *s
, size_t maxsize
, locale_t loc
,
81 const char *format
, va_list ap
, unsigned int flags
)
83 struct __locale_data
*current
= loc
->__locales
[LC_MONETARY
];
85 struct printf_info info
;
86 char *dest
; /* Pointer so copy the output. */
87 const char *fmt
; /* Pointer that walks through format. */
92 /* Loop through the format-string. */
95 /* The floating-point value to output. */
100 #if __HAVE_DISTINCT_FLOAT128
106 int print_curr_symbol
;
121 int other_sep_by_space
;
123 int other_cs_precedes
;
124 const char *sign_string
;
125 const char *other_sign_string
;
127 const char *currency_symbol
;
128 size_t currency_symbol_len
;
134 /* Process all character which do not introduce a format
142 /* "%%" means a single '%' character. */
150 /* Defaults for formatting. */
151 int_format
= 0; /* Use international curr. symbol */
152 print_curr_symbol
= 1; /* Print the currency symbol. */
153 left_prec
= -1; /* No left precision specified. */
154 right_prec
= -1; /* No right precision specified. */
155 group
= 1; /* Print digits grouped. */
156 pad
= ' '; /* Fill character is <SP>. */
157 is_long_double
= 0; /* Double argument by default. */
158 is_binary128
= 0; /* Long double argument by default. */
159 p_sign_posn
= -2; /* This indicates whether the */
160 n_sign_posn
= -2; /* '(' flag is given. */
161 width
= -1; /* No width specified so far. */
162 left
= 0; /* Right justified by default. */
164 /* Parse group characters. */
169 case '=': /* Set fill character. */
174 __set_errno (EINVAL
);
178 case '^': /* Don't group digits. */
181 case '+': /* Use +/- for sign of number. */
182 if (n_sign_posn
!= -2)
184 __set_errno (EINVAL
);
187 p_sign_posn
= *_NL_CURRENT (LC_MONETARY
, P_SIGN_POSN
);
188 n_sign_posn
= *_NL_CURRENT (LC_MONETARY
, N_SIGN_POSN
);
190 case '(': /* Use ( ) for negative sign. */
191 if (n_sign_posn
!= -2)
193 __set_errno (EINVAL
);
199 case '!': /* Don't print the currency symbol. */
200 print_curr_symbol
= 0;
202 case '-': /* Print left justified. */
206 /* Will stop the loop. */;
213 /* Parse field width. */
214 width
= to_digit (*fmt
);
216 while (isdigit (*++fmt
))
218 int val
= to_digit (*fmt
);
220 if (width
> LONG_MAX
/ 10
221 || (width
== LONG_MAX
&& val
> LONG_MAX
% 10))
227 width
= width
* 10 + val
;
230 /* If we don't have enough room for the demanded width we
231 can stop now and return an error. */
232 if (width
>= maxsize
- (dest
- s
))
239 /* Recognize left precision. */
242 if (!isdigit (*++fmt
))
244 __set_errno (EINVAL
);
247 left_prec
= to_digit (*fmt
);
249 while (isdigit (*++fmt
))
252 left_prec
+= to_digit (*fmt
);
256 /* Recognize right precision. */
259 if (!isdigit (*++fmt
))
261 __set_errno (EINVAL
);
264 right_prec
= to_digit (*fmt
);
266 while (isdigit (*++fmt
))
269 right_prec
+= to_digit (*fmt
);
273 /* Handle modifier. This is an extension. */
277 if (__glibc_likely ((flags
& STRFMON_LDBL_IS_DBL
) == 0))
279 #if __HAVE_DISTINCT_FLOAT128
280 if (__glibc_likely ((flags
& STRFMON_LDBL_USES_FLOAT128
) != 0))
281 is_binary128
= is_long_double
;
285 /* Handle format specifier. */
289 case 'i': { /* Use international currency symbol. */
290 const char *int_curr_symbol
;
292 int_curr_symbol
= _NL_CURRENT (LC_MONETARY
, INT_CURR_SYMBOL
);
293 strncpy(int_symbol
, int_curr_symbol
, 3);
294 int_symbol
[3] = '\0';
296 currency_symbol_len
= 3;
297 currency_symbol
= &int_symbol
[0];
298 space_char
= int_curr_symbol
[3];
302 case 'n': /* Use national currency symbol. */
303 currency_symbol
= _NL_CURRENT (LC_MONETARY
, CURRENCY_SYMBOL
);
304 currency_symbol_len
= strlen (currency_symbol
);
308 default: /* Any unrecognized format is an error. */
309 __set_errno (EINVAL
);
313 /* If not specified by the format string now find the values for
314 the format specification. */
315 if (p_sign_posn
== -2)
316 p_sign_posn
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SIGN_POSN
: P_SIGN_POSN
);
317 if (n_sign_posn
== -2)
318 n_sign_posn
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SIGN_POSN
: N_SIGN_POSN
);
320 if (right_prec
== -1)
322 right_prec
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_FRAC_DIGITS
: FRAC_DIGITS
);
324 if (right_prec
== '\377')
328 /* If we have to print the digits grouped determine how many
329 extra characters this means. */
330 if (group
&& left_prec
!= -1)
331 left_prec
+= __guess_grouping (left_prec
,
332 _NL_CURRENT (LC_MONETARY
, MON_GROUPING
));
334 /* Now it's time to get the value. */
335 if (is_long_double
== 1)
337 #if __HAVE_DISTINCT_FLOAT128
338 if (is_binary128
== 1)
340 fpnum
.f128
= va_arg (ap
, _Float128
);
341 is_negative
= fpnum
.f128
< 0;
343 fpnum
.f128
= -fpnum
.f128
;
348 fpnum
.ldbl
= va_arg (ap
, long double);
349 is_negative
= fpnum
.ldbl
< 0;
351 fpnum
.ldbl
= -fpnum
.ldbl
;
356 fpnum
.dbl
= va_arg (ap
, double);
357 is_negative
= fpnum
.dbl
< 0;
359 fpnum
.dbl
= -fpnum
.dbl
;
362 /* We now know the sign of the value and can determine the format. */
365 sign_string
= _NL_CURRENT (LC_MONETARY
, NEGATIVE_SIGN
);
366 /* If the locale does not specify a character for the
367 negative sign we use a '-'. */
368 if (*sign_string
== '\0')
369 sign_string
= (const char *) "-";
370 cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_CS_PRECEDES
: N_CS_PRECEDES
);
371 sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SEP_BY_SPACE
: N_SEP_BY_SPACE
);
372 sign_posn
= n_sign_posn
;
374 other_sign_string
= _NL_CURRENT (LC_MONETARY
, POSITIVE_SIGN
);
375 other_cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_CS_PRECEDES
: P_CS_PRECEDES
);
376 other_sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SEP_BY_SPACE
: P_SEP_BY_SPACE
);
377 other_sign_posn
= p_sign_posn
;
381 sign_string
= _NL_CURRENT (LC_MONETARY
, POSITIVE_SIGN
);
382 cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_CS_PRECEDES
: P_CS_PRECEDES
);
383 sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_P_SEP_BY_SPACE
: P_SEP_BY_SPACE
);
384 sign_posn
= p_sign_posn
;
386 other_sign_string
= _NL_CURRENT (LC_MONETARY
, NEGATIVE_SIGN
);
387 if (*other_sign_string
== '\0')
388 other_sign_string
= (const char *) "-";
389 other_cs_precedes
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_CS_PRECEDES
: N_CS_PRECEDES
);
390 other_sep_by_space
= *_NL_CURRENT (LC_MONETARY
, int_format
? INT_N_SEP_BY_SPACE
: N_SEP_BY_SPACE
);
391 other_sign_posn
= n_sign_posn
;
394 /* Set default values for unspecified information. */
395 if (cs_precedes
!= 0)
397 if (other_cs_precedes
!= 0)
398 other_cs_precedes
= 1;
399 if (sep_by_space
== '\377')
401 if (other_sep_by_space
== '\377')
402 other_sep_by_space
= 0;
403 if (sign_posn
== '\377')
405 if (other_sign_posn
== '\377')
408 /* Check for degenerate cases */
409 if (sep_by_space
== 2)
412 || (sign_posn
== 1 && !cs_precedes
)
413 || (sign_posn
== 2 && cs_precedes
))
414 /* sign and symbol are not adjacent, so no separator */
417 if (other_sep_by_space
== 2)
419 if (other_sign_posn
== 0
420 || (other_sign_posn
== 1 && !other_cs_precedes
)
421 || (other_sign_posn
== 2 && other_cs_precedes
))
422 /* sign and symbol are not adjacent, so no separator */
423 other_sep_by_space
= 0;
426 /* Set the left precision and padding needed for alignment */
434 /* Set left_pad to number of spaces needed to align positive
435 and negative formats */
438 int other_left_bytes
= 0;
440 /* Work out number of bytes for currency string and separator
441 preceding the value */
444 left_bytes
+= currency_symbol_len
;
445 if (sep_by_space
!= 0)
449 if (other_cs_precedes
)
451 other_left_bytes
+= currency_symbol_len
;
452 if (other_sep_by_space
!= 0)
456 /* Work out number of bytes for the sign (or left parenthesis)
457 preceding the value */
458 if (sign_posn
== 0 && is_negative
)
460 else if (sign_posn
== 1)
461 left_bytes
+= strlen (sign_string
);
462 else if (cs_precedes
&& (sign_posn
== 3 || sign_posn
== 4))
463 left_bytes
+= strlen (sign_string
);
465 if (other_sign_posn
== 0 && !is_negative
)
467 else if (other_sign_posn
== 1)
468 other_left_bytes
+= strlen (other_sign_string
);
469 else if (other_cs_precedes
470 && (other_sign_posn
== 3 || other_sign_posn
== 4))
471 other_left_bytes
+= strlen (other_sign_string
);
473 /* Compare the number of bytes preceding the value for
474 each format, and set the padding accordingly */
475 if (other_left_bytes
> left_bytes
)
476 left_pad
= other_left_bytes
- left_bytes
;
481 /* Perhaps we'll someday make these things configurable so
482 better start using symbolic names now. */
483 #define left_paren '('
484 #define right_paren ')'
486 startp
= dest
; /* Remember start so we can compute length. */
488 while (left_pad
-- > 0)
491 if (sign_posn
== 0 && is_negative
)
492 out_char (left_paren
);
496 if (sign_posn
!= 0 && sign_posn
!= 2 && sign_posn
!= 4
499 out_string (sign_string
);
500 if (sep_by_space
== 2)
504 if (print_curr_symbol
)
505 out_string (currency_symbol
);
509 if (print_curr_symbol
&& sep_by_space
== 2)
510 out_char (space_char
);
511 out_string (sign_string
);
512 if (sep_by_space
== 1)
513 /* POSIX.2 and SUS are not clear on this case, but C99
514 says a space follows the adjacent-symbol-and-sign */
518 if (print_curr_symbol
&& sep_by_space
== 1)
519 out_char (space_char
);
522 if (sign_posn
!= 0 && sign_posn
!= 2 && sign_posn
!= 3
523 && sign_posn
!= 4 && sign_posn
!= 5)
524 out_string (sign_string
);
526 /* Print the number. */
528 f
._sbf
._f
._lock
= NULL
;
530 _IO_init_internal (&f
._sbf
._f
, 0);
531 _IO_JUMPS (&f
._sbf
) = &_IO_str_jumps
;
532 _IO_str_init_static_internal (&f
, dest
, (s
+ maxsize
) - dest
, dest
);
533 /* We clear the last available byte so we can find out whether
534 the numeric representation is too long. */
535 s
[maxsize
- 1] = '\0';
537 memset (&info
, '\0', sizeof (info
));
538 info
.prec
= right_prec
;
539 info
.width
= left_prec
+ (right_prec
? (right_prec
+ 1) : 0);
541 info
.is_long_double
= is_long_double
;
542 info
.is_binary128
= is_binary128
;
545 info
.extra
= 1; /* This means use values from LC_MONETARY. */
548 done
= __printf_fp_l (&f
._sbf
._f
, loc
, &info
, &ptr
);
552 if (s
[maxsize
- 1] != '\0')
564 if (sep_by_space
== 1)
566 out_string (sign_string
);
569 if (print_curr_symbol
)
571 if ((sign_posn
== 3 && sep_by_space
== 2)
572 || (sign_posn
== 4 && sep_by_space
== 1)
573 || (sign_posn
== 2 && sep_by_space
== 1)
574 || (sign_posn
== 1 && sep_by_space
== 1)
575 || (sign_posn
== 0 && sep_by_space
== 1))
576 out_char (space_char
);
577 out_nstring (currency_symbol
, currency_symbol_len
);
582 if (sep_by_space
== 2)
584 out_string (sign_string
);
590 if (sep_by_space
== 2)
592 out_string (sign_string
);
595 if (sign_posn
== 0 && is_negative
)
596 out_char (right_paren
);
598 /* Now test whether the output width is filled. */
599 if (dest
- startp
< width
)
602 /* We simply have to fill using spaces. */
605 while (dest
- startp
< width
);
608 long int dist
= width
- (dest
- startp
);
609 for (char *cp
= dest
- 1; cp
>= startp
; --cp
)
615 startp
[--dist
] = ' ';
621 /* Terminate the string. */
628 ___strfmon_l (char *s
, size_t maxsize
, locale_t loc
, const char *format
, ...)
632 va_start (ap
, format
);
634 ssize_t res
= __vstrfmon_l_internal (s
, maxsize
, loc
, format
, ap
, 0);
640 ldbl_strong_alias (___strfmon_l
, __strfmon_l
)
641 ldbl_weak_alias (___strfmon_l
, strfmon_l
)