1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2022 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
33
34 #define out_char(Ch) \
35 do { \
36 if (dest >= s + maxsize - 1) \
37 { \
38 __set_errno (E2BIG); \
39 va_end (ap); \
40 return -1; \
41 } \
42 *dest++ = (Ch); \
43 } while (0)
44
45 #define out_string(String) \
46 do { \
47 const char *_s = (String); \
48 while (*_s) \
49 out_char (*_s++); \
50 } while (0)
51
52 #define out_nstring(String, N) \
53 do { \
54 int _n = (N); \
55 const char *_s = (String); \
56 while (_n-- > 0) \
57 out_char (*_s++); \
58 } while (0)
59
60 #define to_digit(Ch) ((Ch) - '0')
61
62
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
66 macro. */
67 #undef _NL_CURRENT
68 #define _NL_CURRENT(category, item) \
69 (current->values[_NL_ITEM_INDEX (item)].string)
70
71
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. */
78 ssize_t
__vstrfmon_l_internal(char * s,size_t maxsize,locale_t loc,const char * format,va_list ap,unsigned int flags)79 __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
80 const char *format, va_list ap, unsigned int flags)
81 {
82 struct __locale_data *current = loc->__locales[LC_MONETARY];
83 _IO_strfile f;
84 struct printf_info info;
85 char *dest; /* Pointer so copy the output. */
86 const char *fmt; /* Pointer that walks through format. */
87
88 dest = s;
89 fmt = format;
90
91 /* Loop through the format-string. */
92 while (*fmt != '\0')
93 {
94 /* The floating-point value to output. */
95 union
96 {
97 double dbl;
98 long double ldbl;
99 #if __HAVE_DISTINCT_FLOAT128
100 _Float128 f128;
101 #endif
102 }
103 fpnum;
104 int int_format;
105 int print_curr_symbol;
106 int left_prec;
107 int left_pad;
108 int right_prec;
109 int group;
110 char pad;
111 int is_long_double;
112 int is_binary128;
113 int p_sign_posn;
114 int n_sign_posn;
115 int sign_posn;
116 int other_sign_posn;
117 int left;
118 int is_negative;
119 int sep_by_space;
120 int other_sep_by_space;
121 int cs_precedes;
122 int other_cs_precedes;
123 const char *sign_string;
124 const char *other_sign_string;
125 int done;
126 const char *currency_symbol;
127 size_t currency_symbol_len;
128 long int width;
129 char *startp;
130 const void *ptr;
131 char space_char;
132
133 /* Process all character which do not introduce a format
134 specification. */
135 if (*fmt != '%')
136 {
137 out_char (*fmt++);
138 continue;
139 }
140
141 /* "%%" means a single '%' character. */
142 if (fmt[1] == '%')
143 {
144 out_char (*++fmt);
145 ++fmt;
146 continue;
147 }
148
149 /* Defaults for formatting. */
150 int_format = 0; /* Use international curr. symbol */
151 print_curr_symbol = 1; /* Print the currency symbol. */
152 left_prec = -1; /* No left precision specified. */
153 right_prec = -1; /* No right precision specified. */
154 group = 1; /* Print digits grouped. */
155 pad = ' '; /* Fill character is <SP>. */
156 is_long_double = 0; /* Double argument by default. */
157 is_binary128 = 0; /* Long double argument by default. */
158 p_sign_posn = -2; /* This indicates whether the */
159 n_sign_posn = -2; /* '(' flag is given. */
160 width = -1; /* No width specified so far. */
161 left = 0; /* Right justified by default. */
162
163 /* Parse group characters. */
164 while (1)
165 {
166 switch (*++fmt)
167 {
168 case '=': /* Set fill character. */
169 pad = *++fmt;
170 if (pad == '\0')
171 {
172 /* Premature EOS. */
173 __set_errno (EINVAL);
174 return -1;
175 }
176 continue;
177 case '^': /* Don't group digits. */
178 group = 0;
179 continue;
180 case '+': /* Use +/- for sign of number. */
181 if (n_sign_posn != -2)
182 {
183 __set_errno (EINVAL);
184 return -1;
185 }
186 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
187 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
188 continue;
189 case '(': /* Use ( ) for negative sign. */
190 if (n_sign_posn != -2)
191 {
192 __set_errno (EINVAL);
193 return -1;
194 }
195 p_sign_posn = 0;
196 n_sign_posn = 0;
197 continue;
198 case '!': /* Don't print the currency symbol. */
199 print_curr_symbol = 0;
200 continue;
201 case '-': /* Print left justified. */
202 left = 1;
203 continue;
204 default:
205 /* Will stop the loop. */;
206 }
207 break;
208 }
209
210 if (isdigit (*fmt))
211 {
212 /* Parse field width. */
213 width = to_digit (*fmt);
214
215 while (isdigit (*++fmt))
216 {
217 int val = to_digit (*fmt);
218
219 if (width > LONG_MAX / 10
220 || (width == LONG_MAX && val > LONG_MAX % 10))
221 {
222 __set_errno (E2BIG);
223 return -1;
224 }
225
226 width = width * 10 + val;
227 }
228
229 /* If we don't have enough room for the demanded width we
230 can stop now and return an error. */
231 if (width >= maxsize - (dest - s))
232 {
233 __set_errno (E2BIG);
234 return -1;
235 }
236 }
237
238 /* Recognize left precision. */
239 if (*fmt == '#')
240 {
241 if (!isdigit (*++fmt))
242 {
243 __set_errno (EINVAL);
244 return -1;
245 }
246 left_prec = to_digit (*fmt);
247
248 while (isdigit (*++fmt))
249 {
250 left_prec *= 10;
251 left_prec += to_digit (*fmt);
252 }
253 }
254
255 /* Recognize right precision. */
256 if (*fmt == '.')
257 {
258 if (!isdigit (*++fmt))
259 {
260 __set_errno (EINVAL);
261 return -1;
262 }
263 right_prec = to_digit (*fmt);
264
265 while (isdigit (*++fmt))
266 {
267 right_prec *= 10;
268 right_prec += to_digit (*fmt);
269 }
270 }
271
272 /* Handle modifier. This is an extension. */
273 if (*fmt == 'L')
274 {
275 ++fmt;
276 if (__glibc_likely ((flags & STRFMON_LDBL_IS_DBL) == 0))
277 is_long_double = 1;
278 #if __HAVE_DISTINCT_FLOAT128
279 if (__glibc_likely ((flags & STRFMON_LDBL_USES_FLOAT128) != 0))
280 is_binary128 = is_long_double;
281 #endif
282 }
283
284 /* Handle format specifier. */
285 char int_symbol[4];
286 switch (*fmt++)
287 {
288 case 'i': { /* Use international currency symbol. */
289 const char *int_curr_symbol;
290
291 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
292 strncpy(int_symbol, int_curr_symbol, 3);
293 int_symbol[3] = '\0';
294
295 currency_symbol_len = 3;
296 currency_symbol = &int_symbol[0];
297 space_char = int_curr_symbol[3];
298 int_format = 1;
299 break;
300 }
301 case 'n': /* Use national currency symbol. */
302 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
303 currency_symbol_len = strlen (currency_symbol);
304 space_char = ' ';
305 int_format = 0;
306 break;
307 default: /* Any unrecognized format is an error. */
308 __set_errno (EINVAL);
309 return -1;
310 }
311
312 /* If not specified by the format string now find the values for
313 the format specification. */
314 if (p_sign_posn == -2)
315 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
316 if (n_sign_posn == -2)
317 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
318
319 if (right_prec == -1)
320 {
321 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
322
323 if (right_prec == '\377')
324 right_prec = 2;
325 }
326
327 /* If we have to print the digits grouped determine how many
328 extra characters this means. */
329 if (group && left_prec != -1)
330 left_prec += __guess_grouping (left_prec,
331 _NL_CURRENT (LC_MONETARY, MON_GROUPING));
332
333 /* Now it's time to get the value. */
334 if (is_long_double == 1)
335 {
336 #if __HAVE_DISTINCT_FLOAT128
337 if (is_binary128 == 1)
338 {
339 fpnum.f128 = va_arg (ap, _Float128);
340 is_negative = fpnum.f128 < 0;
341 if (is_negative)
342 fpnum.f128 = -fpnum.f128;
343 }
344 else
345 #endif
346 {
347 fpnum.ldbl = va_arg (ap, long double);
348 is_negative = fpnum.ldbl < 0;
349 if (is_negative)
350 fpnum.ldbl = -fpnum.ldbl;
351 }
352 }
353 else
354 {
355 fpnum.dbl = va_arg (ap, double);
356 is_negative = fpnum.dbl < 0;
357 if (is_negative)
358 fpnum.dbl = -fpnum.dbl;
359 }
360
361 /* We now know the sign of the value and can determine the format. */
362 if (is_negative)
363 {
364 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
365 /* If the locale does not specify a character for the
366 negative sign we use a '-'. */
367 if (*sign_string == '\0')
368 sign_string = (const char *) "-";
369 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
370 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
371 sign_posn = n_sign_posn;
372
373 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
374 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
375 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
376 other_sign_posn = p_sign_posn;
377 }
378 else
379 {
380 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
381 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
382 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
383 sign_posn = p_sign_posn;
384
385 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
386 if (*other_sign_string == '\0')
387 other_sign_string = (const char *) "-";
388 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
389 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
390 other_sign_posn = n_sign_posn;
391 }
392
393 /* Set default values for unspecified information. */
394 if (cs_precedes != 0)
395 cs_precedes = 1;
396 if (other_cs_precedes != 0)
397 other_cs_precedes = 1;
398 if (sep_by_space == '\377')
399 sep_by_space = 0;
400 if (other_sep_by_space == '\377')
401 other_sep_by_space = 0;
402 if (sign_posn == '\377')
403 sign_posn = 1;
404 if (other_sign_posn == '\377')
405 other_sign_posn = 1;
406
407 /* Check for degenerate cases */
408 if (sep_by_space == 2)
409 {
410 if (sign_posn == 0
411 || (sign_posn == 1 && !cs_precedes)
412 || (sign_posn == 2 && cs_precedes))
413 /* sign and symbol are not adjacent, so no separator */
414 sep_by_space = 0;
415 }
416 if (other_sep_by_space == 2)
417 {
418 if (other_sign_posn == 0
419 || (other_sign_posn == 1 && !other_cs_precedes)
420 || (other_sign_posn == 2 && other_cs_precedes))
421 /* sign and symbol are not adjacent, so no separator */
422 other_sep_by_space = 0;
423 }
424
425 /* Set the left precision and padding needed for alignment */
426 if (left_prec == -1)
427 {
428 left_prec = 0;
429 left_pad = 0;
430 }
431 else
432 {
433 /* Set left_pad to number of spaces needed to align positive
434 and negative formats */
435
436 int left_bytes = 0;
437 int other_left_bytes = 0;
438
439 /* Work out number of bytes for currency string and separator
440 preceding the value */
441 if (cs_precedes)
442 {
443 left_bytes += currency_symbol_len;
444 if (sep_by_space != 0)
445 ++left_bytes;
446 }
447
448 if (other_cs_precedes)
449 {
450 other_left_bytes += currency_symbol_len;
451 if (other_sep_by_space != 0)
452 ++other_left_bytes;
453 }
454
455 /* Work out number of bytes for the sign (or left parenthesis)
456 preceding the value */
457 if (sign_posn == 0 && is_negative)
458 ++left_bytes;
459 else if (sign_posn == 1)
460 left_bytes += strlen (sign_string);
461 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
462 left_bytes += strlen (sign_string);
463
464 if (other_sign_posn == 0 && !is_negative)
465 ++other_left_bytes;
466 else if (other_sign_posn == 1)
467 other_left_bytes += strlen (other_sign_string);
468 else if (other_cs_precedes
469 && (other_sign_posn == 3 || other_sign_posn == 4))
470 other_left_bytes += strlen (other_sign_string);
471
472 /* Compare the number of bytes preceding the value for
473 each format, and set the padding accordingly */
474 if (other_left_bytes > left_bytes)
475 left_pad = other_left_bytes - left_bytes;
476 else
477 left_pad = 0;
478 }
479
480 /* Perhaps we'll someday make these things configurable so
481 better start using symbolic names now. */
482 #define left_paren '('
483 #define right_paren ')'
484
485 startp = dest; /* Remember start so we can compute length. */
486
487 while (left_pad-- > 0)
488 out_char (' ');
489
490 if (sign_posn == 0 && is_negative)
491 out_char (left_paren);
492
493 if (cs_precedes)
494 {
495 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
496 && sign_posn != 5)
497 {
498 out_string (sign_string);
499 if (sep_by_space == 2)
500 out_char (' ');
501 }
502
503 if (print_curr_symbol)
504 out_string (currency_symbol);
505
506 if (sign_posn == 4)
507 {
508 if (print_curr_symbol && sep_by_space == 2)
509 out_char (space_char);
510 out_string (sign_string);
511 if (sep_by_space == 1)
512 /* POSIX.2 and SUS are not clear on this case, but C99
513 says a space follows the adjacent-symbol-and-sign */
514 out_char (' ');
515 }
516 else
517 if (print_curr_symbol && sep_by_space == 1)
518 out_char (space_char);
519 }
520 else
521 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
522 && sign_posn != 4 && sign_posn != 5)
523 out_string (sign_string);
524
525 /* Print the number. */
526 #ifdef _IO_MTSAFE_IO
527 f._sbf._f._lock = NULL;
528 #endif
529 _IO_init_internal (&f._sbf._f, 0);
530 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
531 _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
532 /* We clear the last available byte so we can find out whether
533 the numeric representation is too long. */
534 s[maxsize - 1] = '\0';
535
536 memset (&info, '\0', sizeof (info));
537 info.prec = right_prec;
538 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
539 info.spec = 'f';
540 info.is_long_double = is_long_double;
541 info.is_binary128 = is_binary128;
542 info.group = group;
543 info.pad = pad;
544 info.extra = 1; /* This means use values from LC_MONETARY. */
545
546 ptr = &fpnum;
547 done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
548 if (done < 0)
549 return -1;
550
551 if (s[maxsize - 1] != '\0')
552 {
553 __set_errno (E2BIG);
554 return -1;
555 }
556
557 dest += done;
558
559 if (!cs_precedes)
560 {
561 if (sign_posn == 3)
562 {
563 if (sep_by_space == 1)
564 out_char (' ');
565 out_string (sign_string);
566 }
567
568 if (print_curr_symbol)
569 {
570 if ((sign_posn == 3 && sep_by_space == 2)
571 || (sign_posn == 4 && sep_by_space == 1)
572 || (sign_posn == 2 && sep_by_space == 1)
573 || (sign_posn == 1 && sep_by_space == 1)
574 || (sign_posn == 0 && sep_by_space == 1))
575 out_char (space_char);
576 out_nstring (currency_symbol, currency_symbol_len);
577 }
578
579 if (sign_posn == 4)
580 {
581 if (sep_by_space == 2)
582 out_char (' ');
583 out_string (sign_string);
584 }
585 }
586
587 if (sign_posn == 2)
588 {
589 if (sep_by_space == 2)
590 out_char (' ');
591 out_string (sign_string);
592 }
593
594 if (sign_posn == 0 && is_negative)
595 out_char (right_paren);
596
597 /* Now test whether the output width is filled. */
598 if (dest - startp < width)
599 {
600 if (left)
601 /* We simply have to fill using spaces. */
602 do
603 out_char (' ');
604 while (dest - startp < width);
605 else
606 {
607 long int dist = width - (dest - startp);
608 for (char *cp = dest - 1; cp >= startp; --cp)
609 cp[dist] = cp[0];
610
611 dest += dist;
612
613 do
614 startp[--dist] = ' ';
615 while (dist > 0);
616 }
617 }
618 }
619
620 /* Terminate the string. */
621 *dest = '\0';
622
623 return dest - s;
624 }
625
626 ssize_t
___strfmon_l(char * s,size_t maxsize,locale_t loc,const char * format,...)627 ___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
628 {
629 va_list ap;
630
631 va_start (ap, format);
632
633 ssize_t res = __vstrfmon_l_internal (s, maxsize, loc, format, ap, 0);
634
635 va_end (ap);
636
637 return res;
638 }
639 ldbl_strong_alias (___strfmon_l, __strfmon_l)
640 ldbl_weak_alias (___strfmon_l, strfmon_l)
641