]> git.ipfire.org Git - thirdparty/glibc.git/blame - stdlib/strfmon_l.c
Update copyright dates with scripts/update-copyrights
[thirdparty/glibc.git] / stdlib / strfmon_l.c
CommitLineData
c84142e8 1/* Formatting a monetary value according to the given locale.
2b778ceb 2 Copyright (C) 1996-2021 Free Software Foundation, Inc.
c84142e8
UD
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
5
6 The GNU C Library is free software; you can redistribute it and/or
41bdb6e2
AJ
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.
c84142e8
UD
10
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
41bdb6e2 14 Lesser General Public License for more details.
c84142e8 15
41bdb6e2 16 You should have received a copy of the GNU Lesser General Public
59ba27a6 17 License along with the GNU C Library; if not, see
5a82c748 18 <https://www.gnu.org/licenses/>. */
c84142e8 19
ccadf7b5
UD
20#include <ctype.h>
21#include <errno.h>
22#include <langinfo.h>
23#include <locale.h>
24#include <monetary.h>
c6251f03
RM
25#include "../libio/libioP.h"
26#include "../libio/strfile.h"
ccadf7b5
UD
27#include <printf.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <string.h>
31#include "../locale/localeinfo.h"
66fa3082 32#include <bits/floatn.h>
da4cfe38 33
ccadf7b5
UD
34
35#define out_char(Ch) \
36 do { \
37 if (dest >= s + maxsize - 1) \
38 { \
39 __set_errno (E2BIG); \
40 va_end (ap); \
41 return -1; \
42 } \
43 *dest++ = (Ch); \
44 } while (0)
45
46#define out_string(String) \
47 do { \
48 const char *_s = (String); \
49 while (*_s) \
50 out_char (*_s++); \
51 } while (0)
52
53#define out_nstring(String, N) \
54 do { \
55 int _n = (N); \
56 const char *_s = (String); \
57 while (_n-- > 0) \
58 out_char (*_s++); \
59 } while (0)
60
61#define to_digit(Ch) ((Ch) - '0')
62
63
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
67 macro. */
68#undef _NL_CURRENT
69#define _NL_CURRENT(category, item) \
70 (current->values[_NL_ITEM_INDEX (item)].string)
71
ccadf7b5
UD
72
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. */
79ssize_t
c75772e3
ZW
80__vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
81 const char *format, va_list ap, unsigned int flags)
ccadf7b5 82{
f095bb72 83 struct __locale_data *current = loc->__locales[LC_MONETARY];
ccadf7b5 84 _IO_strfile f;
ccadf7b5
UD
85 struct printf_info info;
86 char *dest; /* Pointer so copy the output. */
87 const char *fmt; /* Pointer that walks through format. */
88
89 dest = s;
90 fmt = format;
91
92 /* Loop through the format-string. */
93 while (*fmt != '\0')
94 {
95 /* The floating-point value to output. */
96 union
97 {
98 double dbl;
8b164787 99 long double ldbl;
66fa3082
RS
100#if __HAVE_DISTINCT_FLOAT128
101 _Float128 f128;
102#endif
ccadf7b5
UD
103 }
104 fpnum;
105 int int_format;
106 int print_curr_symbol;
107 int left_prec;
108 int left_pad;
109 int right_prec;
110 int group;
111 char pad;
112 int is_long_double;
66fa3082 113 int is_binary128;
ccadf7b5
UD
114 int p_sign_posn;
115 int n_sign_posn;
116 int sign_posn;
117 int other_sign_posn;
118 int left;
119 int is_negative;
120 int sep_by_space;
121 int other_sep_by_space;
122 int cs_precedes;
123 int other_cs_precedes;
124 const char *sign_string;
125 const char *other_sign_string;
126 int done;
127 const char *currency_symbol;
128 size_t currency_symbol_len;
153aa31b 129 long int width;
ccadf7b5
UD
130 char *startp;
131 const void *ptr;
132 char space_char;
133
134 /* Process all character which do not introduce a format
135 specification. */
136 if (*fmt != '%')
137 {
138 out_char (*fmt++);
139 continue;
140 }
141
142 /* "%%" means a single '%' character. */
143 if (fmt[1] == '%')
144 {
145 out_char (*++fmt);
146 ++fmt;
147 continue;
148 }
149
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. */
66fa3082 158 is_binary128 = 0; /* Long double argument by default. */
a7931fcf
AS
159 p_sign_posn = -2; /* This indicates whether the */
160 n_sign_posn = -2; /* '(' flag is given. */
ccadf7b5
UD
161 width = -1; /* No width specified so far. */
162 left = 0; /* Right justified by default. */
163
164 /* Parse group characters. */
165 while (1)
166 {
167 switch (*++fmt)
168 {
169 case '=': /* Set fill character. */
170 pad = *++fmt;
171 if (pad == '\0')
172 {
173 /* Premature EOS. */
174 __set_errno (EINVAL);
175 return -1;
176 }
177 continue;
178 case '^': /* Don't group digits. */
179 group = 0;
180 continue;
181 case '+': /* Use +/- for sign of number. */
a7931fcf 182 if (n_sign_posn != -2)
ccadf7b5
UD
183 {
184 __set_errno (EINVAL);
185 return -1;
186 }
187 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
188 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
189 continue;
190 case '(': /* Use ( ) for negative sign. */
a7931fcf 191 if (n_sign_posn != -2)
ccadf7b5
UD
192 {
193 __set_errno (EINVAL);
194 return -1;
195 }
196 p_sign_posn = 0;
197 n_sign_posn = 0;
198 continue;
199 case '!': /* Don't print the currency symbol. */
200 print_curr_symbol = 0;
201 continue;
202 case '-': /* Print left justified. */
203 left = 1;
204 continue;
205 default:
206 /* Will stop the loop. */;
207 }
208 break;
209 }
210
211 if (isdigit (*fmt))
212 {
213 /* Parse field width. */
214 width = to_digit (*fmt);
215
216 while (isdigit (*++fmt))
217 {
153aa31b
UD
218 int val = to_digit (*fmt);
219
220 if (width > LONG_MAX / 10
221 || (width == LONG_MAX && val > LONG_MAX % 10))
222 {
223 __set_errno (E2BIG);
224 return -1;
225 }
226
227 width = width * 10 + val;
ccadf7b5
UD
228 }
229
230 /* If we don't have enough room for the demanded width we
231 can stop now and return an error. */
153aa31b 232 if (width >= maxsize - (dest - s))
ccadf7b5
UD
233 {
234 __set_errno (E2BIG);
235 return -1;
236 }
237 }
238
239 /* Recognize left precision. */
240 if (*fmt == '#')
241 {
242 if (!isdigit (*++fmt))
243 {
244 __set_errno (EINVAL);
245 return -1;
246 }
247 left_prec = to_digit (*fmt);
248
249 while (isdigit (*++fmt))
250 {
251 left_prec *= 10;
252 left_prec += to_digit (*fmt);
253 }
254 }
255
256 /* Recognize right precision. */
257 if (*fmt == '.')
258 {
259 if (!isdigit (*++fmt))
260 {
261 __set_errno (EINVAL);
262 return -1;
263 }
264 right_prec = to_digit (*fmt);
265
266 while (isdigit (*++fmt))
267 {
268 right_prec *= 10;
269 right_prec += to_digit (*fmt);
270 }
271 }
272
273 /* Handle modifier. This is an extension. */
274 if (*fmt == 'L')
275 {
276 ++fmt;
c75772e3 277 if (__glibc_likely ((flags & STRFMON_LDBL_IS_DBL) == 0))
c6251f03 278 is_long_double = 1;
66fa3082
RS
279#if __HAVE_DISTINCT_FLOAT128
280 if (__glibc_likely ((flags & STRFMON_LDBL_USES_FLOAT128) != 0))
281 is_binary128 = is_long_double;
282#endif
ccadf7b5
UD
283 }
284
285 /* Handle format specifier. */
286 char int_symbol[4];
287 switch (*fmt++)
288 {
289 case 'i': { /* Use international currency symbol. */
290 const char *int_curr_symbol;
291
292 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
293 strncpy(int_symbol, int_curr_symbol, 3);
294 int_symbol[3] = '\0';
295
296 currency_symbol_len = 3;
297 currency_symbol = &int_symbol[0];
298 space_char = int_curr_symbol[3];
299 int_format = 1;
300 break;
301 }
302 case 'n': /* Use national currency symbol. */
303 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
304 currency_symbol_len = strlen (currency_symbol);
305 space_char = ' ';
306 int_format = 0;
307 break;
308 default: /* Any unrecognized format is an error. */
309 __set_errno (EINVAL);
310 return -1;
311 }
312
313 /* If not specified by the format string now find the values for
314 the format specification. */
a7931fcf 315 if (p_sign_posn == -2)
ccadf7b5 316 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
a7931fcf 317 if (n_sign_posn == -2)
ccadf7b5
UD
318 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
319
320 if (right_prec == -1)
321 {
322 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
323
a7931fcf 324 if (right_prec == '\377')
ccadf7b5
UD
325 right_prec = 2;
326 }
327
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,
20962acb 332 _NL_CURRENT (LC_MONETARY, MON_GROUPING));
ccadf7b5
UD
333
334 /* Now it's time to get the value. */
335 if (is_long_double == 1)
336 {
66fa3082
RS
337#if __HAVE_DISTINCT_FLOAT128
338 if (is_binary128 == 1)
339 {
340 fpnum.f128 = va_arg (ap, _Float128);
341 is_negative = fpnum.f128 < 0;
342 if (is_negative)
343 fpnum.f128 = -fpnum.f128;
344 }
345 else
346#endif
347 {
348 fpnum.ldbl = va_arg (ap, long double);
349 is_negative = fpnum.ldbl < 0;
350 if (is_negative)
351 fpnum.ldbl = -fpnum.ldbl;
352 }
ccadf7b5
UD
353 }
354 else
355 {
356 fpnum.dbl = va_arg (ap, double);
357 is_negative = fpnum.dbl < 0;
358 if (is_negative)
359 fpnum.dbl = -fpnum.dbl;
360 }
361
362 /* We now know the sign of the value and can determine the format. */
363 if (is_negative)
364 {
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;
373
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;
378 }
379 else
380 {
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;
385
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;
392 }
393
394 /* Set default values for unspecified information. */
395 if (cs_precedes != 0)
396 cs_precedes = 1;
397 if (other_cs_precedes != 0)
398 other_cs_precedes = 1;
a7931fcf 399 if (sep_by_space == '\377')
ccadf7b5 400 sep_by_space = 0;
a7931fcf 401 if (other_sep_by_space == '\377')
ccadf7b5 402 other_sep_by_space = 0;
a7931fcf 403 if (sign_posn == '\377')
ccadf7b5 404 sign_posn = 1;
a7931fcf 405 if (other_sign_posn == '\377')
ccadf7b5
UD
406 other_sign_posn = 1;
407
408 /* Check for degenerate cases */
409 if (sep_by_space == 2)
410 {
34a5a146
JM
411 if (sign_posn == 0
412 || (sign_posn == 1 && !cs_precedes)
413 || (sign_posn == 2 && cs_precedes))
ccadf7b5
UD
414 /* sign and symbol are not adjacent, so no separator */
415 sep_by_space = 0;
416 }
417 if (other_sep_by_space == 2)
418 {
34a5a146
JM
419 if (other_sign_posn == 0
420 || (other_sign_posn == 1 && !other_cs_precedes)
421 || (other_sign_posn == 2 && other_cs_precedes))
ccadf7b5
UD
422 /* sign and symbol are not adjacent, so no separator */
423 other_sep_by_space = 0;
424 }
425
426 /* Set the left precision and padding needed for alignment */
427 if (left_prec == -1)
428 {
429 left_prec = 0;
430 left_pad = 0;
431 }
432 else
433 {
434 /* Set left_pad to number of spaces needed to align positive
435 and negative formats */
436
437 int left_bytes = 0;
438 int other_left_bytes = 0;
439
440 /* Work out number of bytes for currency string and separator
441 preceding the value */
442 if (cs_precedes)
443 {
444 left_bytes += currency_symbol_len;
445 if (sep_by_space != 0)
446 ++left_bytes;
447 }
448
449 if (other_cs_precedes)
450 {
451 other_left_bytes += currency_symbol_len;
452 if (other_sep_by_space != 0)
453 ++other_left_bytes;
454 }
455
456 /* Work out number of bytes for the sign (or left parenthesis)
457 preceding the value */
458 if (sign_posn == 0 && is_negative)
459 ++left_bytes;
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);
464
465 if (other_sign_posn == 0 && !is_negative)
466 ++other_left_bytes;
467 else if (other_sign_posn == 1)
468 other_left_bytes += strlen (other_sign_string);
34a5a146
JM
469 else if (other_cs_precedes
470 && (other_sign_posn == 3 || other_sign_posn == 4))
ccadf7b5
UD
471 other_left_bytes += strlen (other_sign_string);
472
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;
477 else
478 left_pad = 0;
479 }
480
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 ')'
485
486 startp = dest; /* Remember start so we can compute length. */
487
488 while (left_pad-- > 0)
489 out_char (' ');
490
491 if (sign_posn == 0 && is_negative)
492 out_char (left_paren);
493
494 if (cs_precedes)
495 {
496 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
497 && sign_posn != 5)
498 {
499 out_string (sign_string);
500 if (sep_by_space == 2)
501 out_char (' ');
502 }
503
504 if (print_curr_symbol)
c61a9cfb 505 out_string (currency_symbol);
a334319f 506
c61a9cfb
UD
507 if (sign_posn == 4)
508 {
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 */
515 out_char (' ');
ccadf7b5 516 }
c61a9cfb
UD
517 else
518 if (print_curr_symbol && sep_by_space == 1)
519 out_char (space_char);
ccadf7b5
UD
520 }
521 else
522 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
523 && sign_posn != 4 && sign_posn != 5)
524 out_string (sign_string);
525
526 /* Print the number. */
c6251f03 527#ifdef _IO_MTSAFE_IO
f521be31 528 f._sbf._f._lock = NULL;
c6251f03 529#endif
db3476af 530 _IO_init_internal (&f._sbf._f, 0);
f521be31 531 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
d18ea0c5 532 _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
ccadf7b5
UD
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';
536
01f7e928 537 memset (&info, '\0', sizeof (info));
ccadf7b5
UD
538 info.prec = right_prec;
539 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
540 info.spec = 'f';
541 info.is_long_double = is_long_double;
66fa3082 542 info.is_binary128 = is_binary128;
ccadf7b5
UD
543 info.group = group;
544 info.pad = pad;
545 info.extra = 1; /* This means use values from LC_MONETARY. */
ccadf7b5
UD
546
547 ptr = &fpnum;
985fc132 548 done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
ccadf7b5
UD
549 if (done < 0)
550 return -1;
551
552 if (s[maxsize - 1] != '\0')
553 {
554 __set_errno (E2BIG);
555 return -1;
556 }
557
558 dest += done;
559
560 if (!cs_precedes)
561 {
562 if (sign_posn == 3)
563 {
564 if (sep_by_space == 1)
565 out_char (' ');
566 out_string (sign_string);
567 }
568
569 if (print_curr_symbol)
570 {
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);
c61a9cfb 578 }
153aa31b 579
c61a9cfb
UD
580 if (sign_posn == 4)
581 {
582 if (sep_by_space == 2)
583 out_char (' ');
584 out_string (sign_string);
ccadf7b5
UD
585 }
586 }
587
588 if (sign_posn == 2)
589 {
590 if (sep_by_space == 2)
591 out_char (' ');
592 out_string (sign_string);
593 }
594
595 if (sign_posn == 0 && is_negative)
596 out_char (right_paren);
597
598 /* Now test whether the output width is filled. */
599 if (dest - startp < width)
600 {
601 if (left)
602 /* We simply have to fill using spaces. */
603 do
604 out_char (' ');
605 while (dest - startp < width);
606 else
607 {
153aa31b
UD
608 long int dist = width - (dest - startp);
609 for (char *cp = dest - 1; cp >= startp; --cp)
ccadf7b5
UD
610 cp[dist] = cp[0];
611
612 dest += dist;
613
614 do
615 startp[--dist] = ' ';
616 while (dist > 0);
617 }
618 }
619 }
620
621 /* Terminate the string. */
622 *dest = '\0';
623
624 return dest - s;
625}
626
627ssize_t
af85385f 628___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
ccadf7b5
UD
629{
630 va_list ap;
631
632 va_start (ap, format);
633
c75772e3 634 ssize_t res = __vstrfmon_l_internal (s, maxsize, loc, format, ap, 0);
ccadf7b5
UD
635
636 va_end (ap);
637
638 return res;
639}
c6251f03
RM
640ldbl_strong_alias (___strfmon_l, __strfmon_l)
641ldbl_weak_alias (___strfmon_l, strfmon_l)