1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996-2017 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/>. */
25 #include "../libio/libioP.h"
26 #include "../libio/strfile.h"
31 #include "../locale/localeinfo.h"
34 #define out_char(Ch) \
36 if (dest >= s + maxsize - 1) \
38 __set_errno (E2BIG); \
45 #define out_string(String) \
47 const char *_s = (String); \
52 #define out_nstring(String, N) \
55 const char *_s = (String); \
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
68 #define _NL_CURRENT(category, item) \
69 (current->values[_NL_ITEM_INDEX (item)].string)
71 /* This function determines the number of digit groups in the output.
72 The definition is in printf_fp.c. */
73 extern unsigned int __guess_grouping (unsigned int intdig_max,
74 const char *grouping, wchar_t sepchar);
77 /* We have to overcome some problems with this implementation. On the
78 one hand the strfmon() function is specified in XPG4 and of course
79 it has to follow this. But on the other hand POSIX.2 specifies
80 some information in the LC_MONETARY category which should be used,
81 too. Some of the information contradicts the information which can
82 be specified in format string. */
84 __vstrfmon_l (char *s, size_t maxsize, locale_t loc, const char *format,
87 struct __locale_data *current = loc->__locales[LC_MONETARY];
89 struct printf_info info;
90 char *dest; /* Pointer so copy the output. */
91 const char *fmt; /* Pointer that walks through format. */
96 /* Loop through the format-string. */
99 /* The floating-point value to output. */
107 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 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. */
163 /* Parse group characters. */
168 case '=': /* Set fill character. */
173 __set_errno (EINVAL);
177 case '^': /* Don't group digits. */
180 case '+': /* Use +/- for sign of number. */
181 if (n_sign_posn != -2)
183 __set_errno (EINVAL);
186 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
187 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
189 case '(': /* Use ( ) for negative sign. */
190 if (n_sign_posn != -2)
192 __set_errno (EINVAL);
198 case '!': /* Don't print the currency symbol. */
199 print_curr_symbol = 0;
201 case '-': /* Print left justified. */
205 /* Will stop the loop. */;
212 /* Parse field width. */
213 width = to_digit (*fmt);
215 while (isdigit (*++fmt))
217 int val = to_digit (*fmt);
219 if (width > LONG_MAX / 10
220 || (width == LONG_MAX && val > LONG_MAX % 10))
226 width = width * 10 + val;
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))
238 /* Recognize left precision. */
241 if (!isdigit (*++fmt))
243 __set_errno (EINVAL);
246 left_prec = to_digit (*fmt);
248 while (isdigit (*++fmt))
251 left_prec += to_digit (*fmt);
255 /* Recognize right precision. */
258 if (!isdigit (*++fmt))
260 __set_errno (EINVAL);
263 right_prec = to_digit (*fmt);
265 while (isdigit (*++fmt))
268 right_prec += to_digit (*fmt);
272 /* Handle modifier. This is an extension. */
280 /* Handle format specifier. */
284 case 'i': { /* Use international currency symbol. */
285 const char *int_curr_symbol;
287 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
288 strncpy(int_symbol, int_curr_symbol, 3);
289 int_symbol[3] = '\0';
291 currency_symbol_len = 3;
292 currency_symbol = &int_symbol[0];
293 space_char = int_curr_symbol[3];
297 case 'n': /* Use national currency symbol. */
298 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
299 currency_symbol_len = strlen (currency_symbol);
303 default: /* Any unrecognized format is an error. */
304 __set_errno (EINVAL);
308 /* If not specified by the format string now find the values for
309 the format specification. */
310 if (p_sign_posn == -2)
311 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
312 if (n_sign_posn == -2)
313 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
315 if (right_prec == -1)
317 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
319 if (right_prec == '\377')
323 /* If we have to print the digits grouped determine how many
324 extra characters this means. */
325 if (group && left_prec != -1)
326 left_prec += __guess_grouping (left_prec,
327 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
328 *_NL_CURRENT (LC_MONETARY,
331 /* Now it's time to get the value. */
332 if (is_long_double == 1)
334 fpnum.ldbl = va_arg (ap, long double);
335 is_negative = fpnum.ldbl < 0;
337 fpnum.ldbl = -fpnum.ldbl;
341 fpnum.dbl = va_arg (ap, double);
342 is_negative = fpnum.dbl < 0;
344 fpnum.dbl = -fpnum.dbl;
347 /* We now know the sign of the value and can determine the format. */
350 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
351 /* If the locale does not specify a character for the
352 negative sign we use a '-'. */
353 if (*sign_string == '\0')
354 sign_string = (const char *) "-";
355 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
356 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
357 sign_posn = n_sign_posn;
359 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
360 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
361 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
362 other_sign_posn = p_sign_posn;
366 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
367 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
368 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
369 sign_posn = p_sign_posn;
371 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
372 if (*other_sign_string == '\0')
373 other_sign_string = (const char *) "-";
374 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
375 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
376 other_sign_posn = n_sign_posn;
379 /* Set default values for unspecified information. */
380 if (cs_precedes != 0)
382 if (other_cs_precedes != 0)
383 other_cs_precedes = 1;
384 if (sep_by_space == '\377')
386 if (other_sep_by_space == '\377')
387 other_sep_by_space = 0;
388 if (sign_posn == '\377')
390 if (other_sign_posn == '\377')
393 /* Check for degenerate cases */
394 if (sep_by_space == 2)
396 if (sign_posn == 0 ||
397 (sign_posn == 1 && !cs_precedes) ||
398 (sign_posn == 2 && cs_precedes))
399 /* sign and symbol are not adjacent, so no separator */
402 if (other_sep_by_space == 2)
404 if (other_sign_posn == 0 ||
405 (other_sign_posn == 1 && !other_cs_precedes) ||
406 (other_sign_posn == 2 && other_cs_precedes))
407 /* sign and symbol are not adjacent, so no separator */
408 other_sep_by_space = 0;
411 /* Set the left precision and padding needed for alignment */
419 /* Set left_pad to number of spaces needed to align positive
420 and negative formats */
423 int other_left_bytes = 0;
425 /* Work out number of bytes for currency string and separator
426 preceding the value */
429 left_bytes += currency_symbol_len;
430 if (sep_by_space != 0)
434 if (other_cs_precedes)
436 other_left_bytes += currency_symbol_len;
437 if (other_sep_by_space != 0)
441 /* Work out number of bytes for the sign (or left parenthesis)
442 preceding the value */
443 if (sign_posn == 0 && is_negative)
445 else if (sign_posn == 1)
446 left_bytes += strlen (sign_string);
447 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
448 left_bytes += strlen (sign_string);
450 if (other_sign_posn == 0 && !is_negative)
452 else if (other_sign_posn == 1)
453 other_left_bytes += strlen (other_sign_string);
454 else if (other_cs_precedes &&
455 (other_sign_posn == 3 || other_sign_posn == 4))
456 other_left_bytes += strlen (other_sign_string);
458 /* Compare the number of bytes preceding the value for
459 each format, and set the padding accordingly */
460 if (other_left_bytes > left_bytes)
461 left_pad = other_left_bytes - left_bytes;
466 /* Perhaps we'll someday make these things configurable so
467 better start using symbolic names now. */
468 #define left_paren '('
469 #define right_paren ')'
471 startp = dest; /* Remember start so we can compute length. */
473 while (left_pad-- > 0)
476 if (sign_posn == 0 && is_negative)
477 out_char (left_paren);
481 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
484 out_string (sign_string);
485 if (sep_by_space == 2)
489 if (print_curr_symbol)
490 out_string (currency_symbol);
494 if (print_curr_symbol && sep_by_space == 2)
495 out_char (space_char);
496 out_string (sign_string);
497 if (sep_by_space == 1)
498 /* POSIX.2 and SUS are not clear on this case, but C99
499 says a space follows the adjacent-symbol-and-sign */
503 if (print_curr_symbol && sep_by_space == 1)
504 out_char (space_char);
507 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
508 && sign_posn != 4 && sign_posn != 5)
509 out_string (sign_string);
511 /* Print the number. */
513 f._sbf._f._lock = NULL;
515 _IO_init_internal (&f._sbf._f, 0);
516 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
517 _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
518 /* We clear the last available byte so we can find out whether
519 the numeric representation is too long. */
520 s[maxsize - 1] = '\0';
522 memset (&info, '\0', sizeof (info));
523 info.prec = right_prec;
524 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
526 info.is_long_double = is_long_double;
529 info.extra = 1; /* This means use values from LC_MONETARY. */
532 done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
536 if (s[maxsize - 1] != '\0')
548 if (sep_by_space == 1)
550 out_string (sign_string);
553 if (print_curr_symbol)
555 if ((sign_posn == 3 && sep_by_space == 2)
556 || (sign_posn == 4 && sep_by_space == 1)
557 || (sign_posn == 2 && sep_by_space == 1)
558 || (sign_posn == 1 && sep_by_space == 1)
559 || (sign_posn == 0 && sep_by_space == 1))
560 out_char (space_char);
561 out_nstring (currency_symbol, currency_symbol_len);
566 if (sep_by_space == 2)
568 out_string (sign_string);
574 if (sep_by_space == 2)
576 out_string (sign_string);
579 if (sign_posn == 0 && is_negative)
580 out_char (right_paren);
582 /* Now test whether the output width is filled. */
583 if (dest - startp < width)
586 /* We simply have to fill using spaces. */
589 while (dest - startp < width);
592 long int dist = width - (dest - startp);
593 for (char *cp = dest - 1; cp >= startp; --cp)
599 startp[--dist] = ' ';
605 /* Terminate the string. */
612 ___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
616 va_start (ap, format);
618 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
624 ldbl_strong_alias (___strfmon_l, __strfmon_l)
625 ldbl_weak_alias (___strfmon_l, strfmon_l)