1/* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
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.
9
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.
14
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/>. */
18
19#include <ctype.h>
20#include <errno.h>
21#include <langinfo.h>
22#include <locale.h>
23#include <monetary.h>
24#include "../libio/libioP.h"
25#include "../libio/strfile.h"
26#include <printf.h>
27#include <stdarg.h>
28#include <stdio.h>
29#include <string.h>
30#include "../locale/localeinfo.h"
31#include <bits/floatn.h>
32#include <stdio-common/grouping_iterator.h>
33#include <printf_buffer.h>
34
35#define to_digit(Ch) ((Ch) - '0')
36
37
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
41 macro. */
42#undef _NL_CURRENT
43#define _NL_CURRENT(category, item) \
44 (current->values[_NL_ITEM_INDEX (item)].string)
45
46
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. */
53static void
54__vstrfmon_l_buffer (struct __printf_buffer *buf, locale_t loc,
55 const char *fmt, va_list ap, unsigned int flags)
56{
57 struct __locale_data *current = loc->__locales[LC_MONETARY];
58 struct printf_info info;
59
60 /* Loop through the format-string. */
61 while (*fmt != '\0' && !__printf_buffer_has_failed (buf))
62 {
63 /* The floating-point value to output. */
64 union
65 {
66 double dbl;
67 long double ldbl;
68#if __HAVE_DISTINCT_FLOAT128
69 _Float128 f128;
70#endif
71 }
72 fpnum;
73 int int_format;
74 int print_curr_symbol;
75 int left_prec;
76 int left_pad;
77 int right_prec;
78 int group;
79 char pad;
80 int is_long_double;
81 int is_binary128;
82 int p_sign_posn;
83 int n_sign_posn;
84 int sign_posn;
85 int other_sign_posn;
86 int left;
87 int is_negative;
88 int sep_by_space;
89 int other_sep_by_space;
90 int cs_precedes;
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;
96 long int width;
97 const void *ptr;
98 char space_char;
99
100 /* Process all character which do not introduce a format
101 specification. */
102 if (*fmt != '%')
103 {
104 __printf_buffer_putc (buf, ch: *fmt++);
105 continue;
106 }
107
108 /* "%%" means a single '%' character. */
109 if (fmt[1] == '%')
110 {
111 __printf_buffer_putc (buf, ch: *++fmt);
112 ++fmt;
113 continue;
114 }
115
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. */
129
130 /* Parse group characters. */
131 while (1)
132 {
133 switch (*++fmt)
134 {
135 case '=': /* Set fill character. */
136 pad = *++fmt;
137 if (pad == '\0')
138 {
139 /* Premature EOS. */
140 __set_errno (EINVAL);
141 __printf_buffer_mark_failed (buf);
142 return;
143 }
144 continue;
145 case '^': /* Don't group digits. */
146 group = 0;
147 continue;
148 case '+': /* Use +/- for sign of number. */
149 if (n_sign_posn != -2)
150 {
151 __set_errno (EINVAL);
152 __printf_buffer_mark_failed (buf);
153 return;
154 }
155 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
156 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
157 continue;
158 case '(': /* Use ( ) for negative sign. */
159 if (n_sign_posn != -2)
160 {
161 __set_errno (EINVAL);
162 __printf_buffer_mark_failed (buf);
163 return;
164 }
165 p_sign_posn = 0;
166 n_sign_posn = 0;
167 continue;
168 case '!': /* Don't print the currency symbol. */
169 print_curr_symbol = 0;
170 continue;
171 case '-': /* Print left justified. */
172 left = 1;
173 continue;
174 default:
175 /* Will stop the loop. */;
176 }
177 break;
178 }
179
180 if (isdigit (*fmt))
181 {
182 /* Parse field width. */
183 width = to_digit (*fmt);
184
185 while (isdigit (*++fmt))
186 {
187 int val = to_digit (*fmt);
188
189 if (width > LONG_MAX / 10
190 || (width == LONG_MAX && val > LONG_MAX % 10))
191 {
192 __set_errno (E2BIG);
193 __printf_buffer_mark_failed (buf);
194 return;
195 }
196
197 width = width * 10 + val;
198 }
199 }
200
201 /* Recognize left precision. */
202 if (*fmt == '#')
203 {
204 if (!isdigit (*++fmt))
205 {
206 __set_errno (EINVAL);
207 __printf_buffer_mark_failed (buf);
208 return;
209 }
210 left_prec = to_digit (*fmt);
211
212 while (isdigit (*++fmt))
213 {
214 left_prec *= 10;
215 left_prec += to_digit (*fmt);
216 }
217 }
218
219 /* Recognize right precision. */
220 if (*fmt == '.')
221 {
222 if (!isdigit (*++fmt))
223 {
224 __set_errno (EINVAL);
225 __printf_buffer_mark_failed (buf);
226 return;
227 }
228 right_prec = to_digit (*fmt);
229
230 while (isdigit (*++fmt))
231 {
232 right_prec *= 10;
233 right_prec += to_digit (*fmt);
234 }
235 }
236
237 /* Handle modifier. This is an extension. */
238 if (*fmt == 'L')
239 {
240 ++fmt;
241 if (__glibc_likely ((flags & STRFMON_LDBL_IS_DBL) == 0))
242 is_long_double = 1;
243#if __HAVE_DISTINCT_FLOAT128
244 if (__glibc_likely ((flags & STRFMON_LDBL_USES_FLOAT128) != 0))
245 is_binary128 = is_long_double;
246#endif
247 }
248
249 /* Handle format specifier. */
250 char int_symbol[4];
251 switch (*fmt++)
252 {
253 case 'i': { /* Use international currency symbol. */
254 const char *int_curr_symbol;
255
256 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
257 strncpy(int_symbol, int_curr_symbol, 3);
258 int_symbol[3] = '\0';
259
260 currency_symbol_len = 3;
261 currency_symbol = &int_symbol[0];
262 space_char = int_curr_symbol[3];
263 int_format = 1;
264 break;
265 }
266 case 'n': /* Use national currency symbol. */
267 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
268 currency_symbol_len = strlen (currency_symbol);
269 space_char = ' ';
270 int_format = 0;
271 break;
272 default: /* Any unrecognized format is an error. */
273 __set_errno (EINVAL);
274 __printf_buffer_mark_failed (buf);
275 return;
276 }
277
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);
284
285 if (right_prec == -1)
286 {
287 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
288
289 if (right_prec == '\377')
290 right_prec = 2;
291 }
292
293 /* If we have to print the digits grouped determine how many
294 extra characters this means. */
295 if (group && left_prec != -1)
296 {
297 struct grouping_iterator it;
298 __grouping_iterator_init (it: &it, LC_MONETARY, loc, digits: left_prec);
299 left_prec += it.separators;
300 }
301
302 /* Now it's time to get the value. */
303 if (is_long_double == 1)
304 {
305#if __HAVE_DISTINCT_FLOAT128
306 if (is_binary128 == 1)
307 {
308 fpnum.f128 = va_arg (ap, _Float128);
309 is_negative = fpnum.f128 < 0;
310 if (is_negative)
311 fpnum.f128 = -fpnum.f128;
312 }
313 else
314#endif
315 {
316 fpnum.ldbl = va_arg (ap, long double);
317 is_negative = fpnum.ldbl < 0;
318 if (is_negative)
319 fpnum.ldbl = -fpnum.ldbl;
320 }
321 }
322 else
323 {
324 fpnum.dbl = va_arg (ap, double);
325 is_negative = fpnum.dbl < 0;
326 if (is_negative)
327 fpnum.dbl = -fpnum.dbl;
328 }
329
330 /* We now know the sign of the value and can determine the format. */
331 if (is_negative)
332 {
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;
341
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;
346 }
347 else
348 {
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;
353
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;
360 }
361
362 /* Set default values for unspecified information. */
363 if (cs_precedes != 0)
364 cs_precedes = 1;
365 if (other_cs_precedes != 0)
366 other_cs_precedes = 1;
367 if (sep_by_space == '\377')
368 sep_by_space = 0;
369 if (other_sep_by_space == '\377')
370 other_sep_by_space = 0;
371 if (sign_posn == '\377')
372 sign_posn = 1;
373 if (other_sign_posn == '\377')
374 other_sign_posn = 1;
375
376 /* Check for degenerate cases */
377 if (sep_by_space == 2)
378 {
379 if (sign_posn == 0
380 || (sign_posn == 1 && !cs_precedes)
381 || (sign_posn == 2 && cs_precedes))
382 /* sign and symbol are not adjacent, so no separator */
383 sep_by_space = 0;
384 }
385 if (other_sep_by_space == 2)
386 {
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;
392 }
393
394 /* Set the left precision and padding needed for alignment */
395 if (left_prec == -1)
396 {
397 left_prec = 0;
398 left_pad = 0;
399 }
400 else
401 {
402 /* Set left_pad to number of spaces needed to align positive
403 and negative formats */
404
405 int left_bytes = 0;
406 int other_left_bytes = 0;
407
408 /* Work out number of bytes for currency string and separator
409 preceding the value */
410 if (cs_precedes)
411 {
412 left_bytes += currency_symbol_len;
413 if (sep_by_space != 0)
414 ++left_bytes;
415 }
416
417 if (other_cs_precedes)
418 {
419 other_left_bytes += currency_symbol_len;
420 if (other_sep_by_space != 0)
421 ++other_left_bytes;
422 }
423
424 /* Work out number of bytes for the sign (or left parenthesis)
425 preceding the value */
426 if (sign_posn == 0 && is_negative)
427 ++left_bytes;
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);
432
433 if (other_sign_posn == 0 && !is_negative)
434 ++other_left_bytes;
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);
440
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;
445 else
446 left_pad = 0;
447 }
448
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 ')'
453
454 char *startp = buf->write_ptr;
455
456 __printf_buffer_pad (buf, ch: ' ', count: left_pad);
457
458 if (sign_posn == 0 && is_negative)
459 __printf_buffer_putc (buf, left_paren);
460
461 if (cs_precedes)
462 {
463 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
464 && sign_posn != 5)
465 {
466 __printf_buffer_puts (buf, s: sign_string);
467 if (sep_by_space == 2)
468 __printf_buffer_putc (buf, ch: ' ');
469 }
470
471 if (print_curr_symbol)
472 __printf_buffer_puts (buf, s: currency_symbol);
473
474 if (sign_posn == 4)
475 {
476 if (print_curr_symbol && sep_by_space == 2)
477 __printf_buffer_putc (buf, ch: space_char);
478 __printf_buffer_puts (buf, s: 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, ch: ' ');
483 }
484 else
485 if (print_curr_symbol && sep_by_space == 1)
486 __printf_buffer_putc (buf, ch: space_char);
487 }
488 else
489 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
490 && sign_posn != 4 && sign_posn != 5)
491 __printf_buffer_puts (buf, s: sign_string);
492
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);
497 info.spec = 'f';
498 info.is_long_double = is_long_double;
499 info.is_binary128 = is_binary128;
500 info.group = group;
501 info.pad = pad;
502 info.extra = 1; /* This means use values from LC_MONETARY. */
503
504 ptr = &fpnum;
505 __printf_fp_l_buffer (buf, loc, &info, &ptr);
506 if (__printf_buffer_has_failed (buf))
507 return;
508
509 if (!cs_precedes)
510 {
511 if (sign_posn == 3)
512 {
513 if (sep_by_space == 1)
514 __printf_buffer_putc (buf, ch: ' ');
515 __printf_buffer_puts (buf, s: sign_string);
516 }
517
518 if (print_curr_symbol)
519 {
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, ch: space_char);
526 __printf_buffer_write (buf, s: currency_symbol,
527 count: __strnlen (currency_symbol,
528 currency_symbol_len));
529 }
530
531 if (sign_posn == 4)
532 {
533 if (sep_by_space == 2)
534 __printf_buffer_putc (buf, ch: ' ');
535 __printf_buffer_puts (buf, s: sign_string);
536 }
537 }
538
539 if (sign_posn == 2)
540 {
541 if (sep_by_space == 2)
542 __printf_buffer_putc (buf, ch: ' ');
543 __printf_buffer_puts (buf, s: sign_string);
544 }
545
546 if (sign_posn == 0 && is_negative)
547 __printf_buffer_putc (buf, right_paren);
548
549 /* Now test whether the output width is filled. */
550 if (buf->write_ptr - startp < width)
551 {
552 size_t pad_width = width - (buf->write_ptr - startp);
553 __printf_buffer_pad (buf, ch: ' ', count: pad_width);
554 if (__printf_buffer_has_failed (buf))
555 /* Implies length check. */
556 return;
557 /* Left padding is already in the correct position.
558 Otherwise move the field contents in place. */
559 if (!left)
560 {
561 memmove (startp + pad_width, startp, buf->write_ptr - startp);
562 memset (startp, ' ', pad_width);
563 }
564 }
565 }
566}
567
568ssize_t
569__vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
570 const char *format, va_list ap, unsigned int flags)
571{
572 struct __printf_buffer buf;
573 __printf_buffer_init (buf: &buf, base: s, len: maxsize, mode: __printf_buffer_mode_strfmon);
574 __vstrfmon_l_buffer (buf: &buf, loc, fmt: format, ap, flags);
575 __printf_buffer_putc (buf: &buf, ch: '\0'); /* Terminate the string. */
576 if (__printf_buffer_has_failed (buf: &buf))
577 return -1;
578 else
579 return buf.write_ptr - buf.write_base - 1; /* Exclude NUL byte. */
580}
581
582ssize_t
583___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
584{
585 va_list ap;
586
587 va_start (ap, format);
588
589 ssize_t res = __vstrfmon_l_internal (s, maxsize, loc, format, ap, flags: 0);
590
591 va_end (ap);
592
593 return res;
594}
595ldbl_strong_alias (___strfmon_l, __strfmon_l)
596ldbl_weak_alias (___strfmon_l, strfmon_l)
597

source code of glibc/stdlib/strfmon_l.c