Increase internal precision of ldbl-128ibm decimal printf [BZ #19853]
[glibc.git] / stdlib / strfmon_l.c
blobb3570209fa9f77a066df5f4b2ef3527b72064f7c
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2016 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 <http://www.gnu.org/licenses/>. */
20 #include <ctype.h>
21 #include <errno.h>
22 #include <langinfo.h>
23 #include <locale.h>
24 #include <monetary.h>
25 #include "../libio/libioP.h"
26 #include "../libio/strfile.h"
27 #include <printf.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include "../locale/localeinfo.h"
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)
45 #define out_string(String) \
46 do { \
47 const char *_s = (String); \
48 while (*_s) \
49 out_char (*_s++); \
50 } while (0)
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)
60 #define to_digit(Ch) ((Ch) - '0')
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)
71 extern int __printf_fp (FILE *, const struct printf_info *,
72 const void *const *);
73 libc_hidden_proto (__printf_fp)
74 /* This function determines the number of digit groups in the output.
75 The definition is in printf_fp.c. */
76 extern unsigned int __guess_grouping (unsigned int intdig_max,
77 const char *grouping, wchar_t sepchar);
80 /* We have to overcome some problems with this implementation. On the
81 one hand the strfmon() function is specified in XPG4 and of course
82 it has to follow this. But on the other hand POSIX.2 specifies
83 some information in the LC_MONETARY category which should be used,
84 too. Some of the information contradicts the information which can
85 be specified in format string. */
86 ssize_t
87 __vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
88 va_list ap)
90 struct __locale_data *current = loc->__locales[LC_MONETARY];
91 _IO_strfile f;
92 struct printf_info info;
93 char *dest; /* Pointer so copy the output. */
94 const char *fmt; /* Pointer that walks through format. */
96 dest = s;
97 fmt = format;
99 /* Loop through the format-string. */
100 while (*fmt != '\0')
102 /* The floating-point value to output. */
103 union
105 double dbl;
106 __long_double_t ldbl;
108 fpnum;
109 int int_format;
110 int print_curr_symbol;
111 int left_prec;
112 int left_pad;
113 int right_prec;
114 int group;
115 char pad;
116 int is_long_double;
117 int p_sign_posn;
118 int n_sign_posn;
119 int sign_posn;
120 int other_sign_posn;
121 int left;
122 int is_negative;
123 int sep_by_space;
124 int other_sep_by_space;
125 int cs_precedes;
126 int other_cs_precedes;
127 const char *sign_string;
128 const char *other_sign_string;
129 int done;
130 const char *currency_symbol;
131 size_t currency_symbol_len;
132 long int width;
133 char *startp;
134 const void *ptr;
135 char space_char;
137 /* Process all character which do not introduce a format
138 specification. */
139 if (*fmt != '%')
141 out_char (*fmt++);
142 continue;
145 /* "%%" means a single '%' character. */
146 if (fmt[1] == '%')
148 out_char (*++fmt);
149 ++fmt;
150 continue;
153 /* Defaults for formatting. */
154 int_format = 0; /* Use international curr. symbol */
155 print_curr_symbol = 1; /* Print the currency symbol. */
156 left_prec = -1; /* No left precision specified. */
157 right_prec = -1; /* No right precision specified. */
158 group = 1; /* Print digits grouped. */
159 pad = ' '; /* Fill character is <SP>. */
160 is_long_double = 0; /* Double argument by default. */
161 p_sign_posn = -2; /* This indicates whether the */
162 n_sign_posn = -2; /* '(' flag is given. */
163 width = -1; /* No width specified so far. */
164 left = 0; /* Right justified by default. */
166 /* Parse group characters. */
167 while (1)
169 switch (*++fmt)
171 case '=': /* Set fill character. */
172 pad = *++fmt;
173 if (pad == '\0')
175 /* Premature EOS. */
176 __set_errno (EINVAL);
177 return -1;
179 continue;
180 case '^': /* Don't group digits. */
181 group = 0;
182 continue;
183 case '+': /* Use +/- for sign of number. */
184 if (n_sign_posn != -2)
186 __set_errno (EINVAL);
187 return -1;
189 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
190 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
191 continue;
192 case '(': /* Use ( ) for negative sign. */
193 if (n_sign_posn != -2)
195 __set_errno (EINVAL);
196 return -1;
198 p_sign_posn = 0;
199 n_sign_posn = 0;
200 continue;
201 case '!': /* Don't print the currency symbol. */
202 print_curr_symbol = 0;
203 continue;
204 case '-': /* Print left justified. */
205 left = 1;
206 continue;
207 default:
208 /* Will stop the loop. */;
210 break;
213 if (isdigit (*fmt))
215 /* Parse field width. */
216 width = to_digit (*fmt);
218 while (isdigit (*++fmt))
220 int val = to_digit (*fmt);
222 if (width > LONG_MAX / 10
223 || (width == LONG_MAX && val > LONG_MAX % 10))
225 __set_errno (E2BIG);
226 return -1;
229 width = width * 10 + val;
232 /* If we don't have enough room for the demanded width we
233 can stop now and return an error. */
234 if (width >= maxsize - (dest - s))
236 __set_errno (E2BIG);
237 return -1;
241 /* Recognize left precision. */
242 if (*fmt == '#')
244 if (!isdigit (*++fmt))
246 __set_errno (EINVAL);
247 return -1;
249 left_prec = to_digit (*fmt);
251 while (isdigit (*++fmt))
253 left_prec *= 10;
254 left_prec += to_digit (*fmt);
258 /* Recognize right precision. */
259 if (*fmt == '.')
261 if (!isdigit (*++fmt))
263 __set_errno (EINVAL);
264 return -1;
266 right_prec = to_digit (*fmt);
268 while (isdigit (*++fmt))
270 right_prec *= 10;
271 right_prec += to_digit (*fmt);
275 /* Handle modifier. This is an extension. */
276 if (*fmt == 'L')
278 ++fmt;
279 if (!__ldbl_is_dbl)
280 is_long_double = 1;
283 /* Handle format specifier. */
284 char int_symbol[4];
285 switch (*fmt++)
287 case 'i': { /* Use international currency symbol. */
288 const char *int_curr_symbol;
290 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
291 strncpy(int_symbol, int_curr_symbol, 3);
292 int_symbol[3] = '\0';
294 currency_symbol_len = 3;
295 currency_symbol = &int_symbol[0];
296 space_char = int_curr_symbol[3];
297 int_format = 1;
298 break;
300 case 'n': /* Use national currency symbol. */
301 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
302 currency_symbol_len = strlen (currency_symbol);
303 space_char = ' ';
304 int_format = 0;
305 break;
306 default: /* Any unrecognized format is an error. */
307 __set_errno (EINVAL);
308 return -1;
311 /* If not specified by the format string now find the values for
312 the format specification. */
313 if (p_sign_posn == -2)
314 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
315 if (n_sign_posn == -2)
316 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
318 if (right_prec == -1)
320 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
322 if (right_prec == '\377')
323 right_prec = 2;
326 /* If we have to print the digits grouped determine how many
327 extra characters this means. */
328 if (group && left_prec != -1)
329 left_prec += __guess_grouping (left_prec,
330 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
331 *_NL_CURRENT (LC_MONETARY,
332 MON_THOUSANDS_SEP));
334 /* Now it's time to get the value. */
335 if (is_long_double == 1)
337 fpnum.ldbl = va_arg (ap, long double);
338 is_negative = fpnum.ldbl < 0;
339 if (is_negative)
340 fpnum.ldbl = -fpnum.ldbl;
342 else
344 fpnum.dbl = va_arg (ap, double);
345 is_negative = fpnum.dbl < 0;
346 if (is_negative)
347 fpnum.dbl = -fpnum.dbl;
350 /* We now know the sign of the value and can determine the format. */
351 if (is_negative)
353 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
354 /* If the locale does not specify a character for the
355 negative sign we use a '-'. */
356 if (*sign_string == '\0')
357 sign_string = (const char *) "-";
358 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
359 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
360 sign_posn = n_sign_posn;
362 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
363 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
364 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
365 other_sign_posn = p_sign_posn;
367 else
369 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
370 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
371 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
372 sign_posn = p_sign_posn;
374 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
375 if (*other_sign_string == '\0')
376 other_sign_string = (const char *) "-";
377 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
378 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
379 other_sign_posn = n_sign_posn;
382 /* Set default values for unspecified information. */
383 if (cs_precedes != 0)
384 cs_precedes = 1;
385 if (other_cs_precedes != 0)
386 other_cs_precedes = 1;
387 if (sep_by_space == '\377')
388 sep_by_space = 0;
389 if (other_sep_by_space == '\377')
390 other_sep_by_space = 0;
391 if (sign_posn == '\377')
392 sign_posn = 1;
393 if (other_sign_posn == '\377')
394 other_sign_posn = 1;
396 /* Check for degenerate cases */
397 if (sep_by_space == 2)
399 if (sign_posn == 0 ||
400 (sign_posn == 1 && !cs_precedes) ||
401 (sign_posn == 2 && cs_precedes))
402 /* sign and symbol are not adjacent, so no separator */
403 sep_by_space = 0;
405 if (other_sep_by_space == 2)
407 if (other_sign_posn == 0 ||
408 (other_sign_posn == 1 && !other_cs_precedes) ||
409 (other_sign_posn == 2 && other_cs_precedes))
410 /* sign and symbol are not adjacent, so no separator */
411 other_sep_by_space = 0;
414 /* Set the left precision and padding needed for alignment */
415 if (left_prec == -1)
417 left_prec = 0;
418 left_pad = 0;
420 else
422 /* Set left_pad to number of spaces needed to align positive
423 and negative formats */
425 int left_bytes = 0;
426 int other_left_bytes = 0;
428 /* Work out number of bytes for currency string and separator
429 preceding the value */
430 if (cs_precedes)
432 left_bytes += currency_symbol_len;
433 if (sep_by_space != 0)
434 ++left_bytes;
437 if (other_cs_precedes)
439 other_left_bytes += currency_symbol_len;
440 if (other_sep_by_space != 0)
441 ++other_left_bytes;
444 /* Work out number of bytes for the sign (or left parenthesis)
445 preceding the value */
446 if (sign_posn == 0 && is_negative)
447 ++left_bytes;
448 else if (sign_posn == 1)
449 left_bytes += strlen (sign_string);
450 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
451 left_bytes += strlen (sign_string);
453 if (other_sign_posn == 0 && !is_negative)
454 ++other_left_bytes;
455 else if (other_sign_posn == 1)
456 other_left_bytes += strlen (other_sign_string);
457 else if (other_cs_precedes &&
458 (other_sign_posn == 3 || other_sign_posn == 4))
459 other_left_bytes += strlen (other_sign_string);
461 /* Compare the number of bytes preceding the value for
462 each format, and set the padding accordingly */
463 if (other_left_bytes > left_bytes)
464 left_pad = other_left_bytes - left_bytes;
465 else
466 left_pad = 0;
469 /* Perhaps we'll someday make these things configurable so
470 better start using symbolic names now. */
471 #define left_paren '('
472 #define right_paren ')'
474 startp = dest; /* Remember start so we can compute length. */
476 while (left_pad-- > 0)
477 out_char (' ');
479 if (sign_posn == 0 && is_negative)
480 out_char (left_paren);
482 if (cs_precedes)
484 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
485 && sign_posn != 5)
487 out_string (sign_string);
488 if (sep_by_space == 2)
489 out_char (' ');
492 if (print_curr_symbol)
493 out_string (currency_symbol);
495 if (sign_posn == 4)
497 if (print_curr_symbol && sep_by_space == 2)
498 out_char (space_char);
499 out_string (sign_string);
500 if (sep_by_space == 1)
501 /* POSIX.2 and SUS are not clear on this case, but C99
502 says a space follows the adjacent-symbol-and-sign */
503 out_char (' ');
505 else
506 if (print_curr_symbol && sep_by_space == 1)
507 out_char (space_char);
509 else
510 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
511 && sign_posn != 4 && sign_posn != 5)
512 out_string (sign_string);
514 /* Print the number. */
515 #ifdef _IO_MTSAFE_IO
516 f._sbf._f._lock = NULL;
517 #endif
518 _IO_init (&f._sbf._f, 0);
519 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
520 _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
521 /* We clear the last available byte so we can find out whether
522 the numeric representation is too long. */
523 s[maxsize - 1] = '\0';
525 memset (&info, '\0', sizeof (info));
526 info.prec = right_prec;
527 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
528 info.spec = 'f';
529 info.is_long_double = is_long_double;
530 info.group = group;
531 info.pad = pad;
532 info.extra = 1; /* This means use values from LC_MONETARY. */
534 ptr = &fpnum;
535 done = __printf_fp (&f._sbf._f, &info, &ptr);
536 if (done < 0)
537 return -1;
539 if (s[maxsize - 1] != '\0')
541 __set_errno (E2BIG);
542 return -1;
545 dest += done;
547 if (!cs_precedes)
549 if (sign_posn == 3)
551 if (sep_by_space == 1)
552 out_char (' ');
553 out_string (sign_string);
556 if (print_curr_symbol)
558 if ((sign_posn == 3 && sep_by_space == 2)
559 || (sign_posn == 4 && sep_by_space == 1)
560 || (sign_posn == 2 && sep_by_space == 1)
561 || (sign_posn == 1 && sep_by_space == 1)
562 || (sign_posn == 0 && sep_by_space == 1))
563 out_char (space_char);
564 out_nstring (currency_symbol, currency_symbol_len);
567 if (sign_posn == 4)
569 if (sep_by_space == 2)
570 out_char (' ');
571 out_string (sign_string);
575 if (sign_posn == 2)
577 if (sep_by_space == 2)
578 out_char (' ');
579 out_string (sign_string);
582 if (sign_posn == 0 && is_negative)
583 out_char (right_paren);
585 /* Now test whether the output width is filled. */
586 if (dest - startp < width)
588 if (left)
589 /* We simply have to fill using spaces. */
591 out_char (' ');
592 while (dest - startp < width);
593 else
595 long int dist = width - (dest - startp);
596 for (char *cp = dest - 1; cp >= startp; --cp)
597 cp[dist] = cp[0];
599 dest += dist;
602 startp[--dist] = ' ';
603 while (dist > 0);
608 /* Terminate the string. */
609 *dest = '\0';
611 return dest - s;
614 ssize_t
615 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
617 va_list ap;
619 va_start (ap, format);
621 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
623 va_end (ap);
625 return res;
627 ldbl_strong_alias (___strfmon_l, __strfmon_l)
628 ldbl_weak_alias (___strfmon_l, strfmon_l)