1 /* $NetBSD: strfmon.c,v 1.10 2012/03/21 14:19:15 christos Exp $ */
4 * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 #include <sys/cdefs.h>
31 #if defined(LIBC_SCCS) && !defined(lint)
33 __FBSDID("$FreeBSD: src/lib/libc/stdlib/strfmon.c,v 1.14 2003/03/20 08:18:55 ache Exp $");
35 __RCSID("$NetBSD: strfmon.c,v 1.10 2012/03/21 14:19:15 christos Exp $");
37 #endif /* LIBC_SCCS and not lint */
39 #if defined(__NetBSD__) || defined(__minix)
40 #include "namespace.h"
44 #include <sys/types.h>
57 #define NEED_GROUPING 0x01 /* print digits grouped (default) */
58 #define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */
59 #define LOCALE_POSN 0x04 /* use locale defined +/- (default) */
60 #define PARENTH_POSN 0x08 /* enclose negative amount in () */
61 #define SUPRESS_CURR_SYMBOL 0x10 /* supress the currency from output */
62 #define LEFT_JUSTIFY 0x20 /* left justify */
63 #define USE_INTL_CURRENCY 0x40 /* use international currency symbol */
64 #define IS_NEGATIVE 0x80 /* is argument value negative ? */
67 #define NBCHAR_MAX ((unsigned char)CHAR_MAX)
71 #define PRINT(CH) do { \
72 if (dst >= s + maxsize) \
75 } while (/* CONSTCOND */ 0)
77 #define PRINTS(STR) do { \
78 const char *tmps = STR; \
79 while (*tmps != '\0') \
81 } while (/* CONSTCOND */ 0)
83 #define GET_NUMBER(VAR) do { \
85 while (isdigit((unsigned char)*fmt)) { \
88 if (VAR > 0x00ffffff) \
92 } while (/* CONSTCOND */ 0)
94 #define GRPCPY(howmany) do { \
98 *--bufend = *(avalue+avalue_size+padded); \
100 } while (/* CONSTCOND */ 0)
102 #define GRPSEP do { \
103 *--bufend = thousands_sep; \
105 } while (/* CONSTCOND */ 0)
107 static void __setup_vars(int, char *, char *, char *, const char **);
108 static int __calc_left_pad(int, char *);
109 static char *__format_grouped_double(double, int *, int, int, int);
112 strfmon(char * __restrict s
, size_t maxsize
, const char * __restrict format
,
116 char *dst
; /* output destination pointer */
117 const char *fmt
; /* current format poistion pointer */
118 struct lconv
*lc
; /* pointer to lconv structure */
119 char *asciivalue
; /* formatted double pointer */
121 int flags
; /* formatting options */
122 int pad_char
; /* padding character */
123 int pad_size
; /* pad size */
124 int width
; /* field width */
125 int left_prec
; /* left precision */
126 int right_prec
; /* right precision */
127 double value
; /* just value */
128 char space_char
= ' '; /* space after currency */
130 char cs_precedes
, /* values gathered from struct lconv */
136 char *tmpptr
; /* temporary vars */
139 va_start(ap
, format
);
145 currency_symbol
= NULL
;
149 /* pass nonformating characters AS IS */
155 /* "%%" mean just '%' */
156 if (*(fmt
+1) == '%') {
163 /* set up initial values */
164 flags
= (NEED_GROUPING
|LOCALE_POSN
);
165 pad_char
= ' '; /* padding character is "space" */
166 left_prec
= -1; /* no left precision specified */
167 right_prec
= -1; /* no right precision specified */
168 width
= -1; /* no width specified */
169 value
= 0; /* we have no value to print now */
172 while (/* CONSTCOND */ 1) {
174 case '=': /* fill character */
176 if (pad_char
== '\0')
179 case '^': /* not group currency */
180 flags
&= ~(NEED_GROUPING
);
182 case '+': /* use locale defined signs */
183 if (flags
& SIGN_POSN_USED
)
185 flags
|= (SIGN_POSN_USED
|LOCALE_POSN
);
187 case '(': /* enclose negatives with () */
188 if (flags
& SIGN_POSN_USED
)
190 flags
|= (SIGN_POSN_USED
|PARENTH_POSN
);
192 case '!': /* suppress currency symbol */
193 flags
|= SUPRESS_CURR_SYMBOL
;
195 case '-': /* alignment (left) */
196 flags
|= LEFT_JUSTIFY
;
205 if (isdigit((unsigned char)*fmt
)) {
206 ptrdiff_t d
= dst
- s
;
208 /* Do we have enough space to put number with
212 if ((size_t)(d
+ width
) >= maxsize
)
218 if (!isdigit((unsigned char)*++fmt
))
220 GET_NUMBER(left_prec
);
223 /* Right precision */
225 if (!isdigit((unsigned char)*++fmt
))
227 GET_NUMBER(right_prec
);
230 /* Conversion Characters */
232 case 'i': /* use internaltion currency format */
233 flags
|= USE_INTL_CURRENCY
;
235 case 'n': /* use national currency format */
236 flags
&= ~(USE_INTL_CURRENCY
);
238 default: /* required character is missing or
244 free(currency_symbol
);
245 if (flags
& USE_INTL_CURRENCY
) {
246 currency_symbol
= strdup(lc
->int_curr_symbol
);
247 if (currency_symbol
!= NULL
)
248 space_char
= *(currency_symbol
+3);
250 currency_symbol
= strdup(lc
->currency_symbol
);
252 if (currency_symbol
== NULL
)
253 goto end_error
; /* ENOMEM. */
256 value
= va_arg(ap
, double);
260 flags
|= IS_NEGATIVE
;
264 /* fill left_prec with amount of padding chars */
265 if (left_prec
>= 0) {
266 pad_size
= __calc_left_pad((flags
^ IS_NEGATIVE
),
268 __calc_left_pad(flags
, currency_symbol
);
273 asciivalue
= __format_grouped_double(value
, &flags
,
274 left_prec
, right_prec
, pad_char
);
275 if (asciivalue
== NULL
)
276 goto end_error
; /* errno already set */
277 /* to ENOMEM by malloc() */
279 /* set some variables for later use */
280 __setup_vars(flags
, &cs_precedes
, &sep_by_space
,
281 &sign_posn
, &signstr
);
284 * Description of some LC_MONETARY's values:
286 * p_cs_precedes & n_cs_precedes
288 * = 1 - $currency_symbol precedes the value
289 * for a monetary quantity with a non-negative value
290 * = 0 - symbol succeeds the value
292 * p_sep_by_space & n_sep_by_space
294 * = 0 - no space separates $currency_symbol
295 * from the value for a monetary quantity with a
297 * = 1 - space separates the symbol from the value
298 * = 2 - space separates the symbol and the sign string,
301 * p_sign_posn & n_sign_posn
303 * = 0 - parentheses enclose the quantity and the
305 * = 1 - the sign string precedes the quantity and the
307 * = 2 - the sign string succeeds the quantity and the
309 * = 3 - the sign string precedes the $currency_symbol
310 * = 4 - the sign string succeeds the $currency_symbol
316 while (pad_size
-- > 0)
319 if (sign_posn
== 0 && (flags
& IS_NEGATIVE
))
322 if (cs_precedes
== 1) {
323 if (sign_posn
== 1 || sign_posn
== 3) {
325 if (sep_by_space
== 2) /* XXX: ? */
329 if (!(flags
& SUPRESS_CURR_SYMBOL
)) {
330 PRINTS(currency_symbol
);
332 if (sign_posn
== 4) {
333 if (sep_by_space
== 2)
336 if (sep_by_space
== 1)
338 } else if (sep_by_space
== 1)
341 } else if (sign_posn
== 1)
346 if (cs_precedes
== 0) {
347 if (sign_posn
== 3) {
348 if (sep_by_space
== 1)
353 if (!(flags
& SUPRESS_CURR_SYMBOL
)) {
354 if ((sign_posn
== 3 && sep_by_space
== 2)
355 || (sep_by_space
== 1
361 PRINTS(currency_symbol
); /* XXX: len */
362 if (sign_posn
== 4) {
363 if (sep_by_space
== 2)
370 if (sign_posn
== 2) {
371 if (sep_by_space
== 2)
376 if (sign_posn
== 0 && (flags
& IS_NEGATIVE
))
379 if (dst
- tmpptr
< width
) {
380 if (flags
& LEFT_JUSTIFY
) {
381 while (dst
- tmpptr
< width
)
384 _DIAGASSERT(__type_fit(int, dst
- tmpptr
));
385 pad_size
= dst
- tmpptr
;
386 memmove(tmpptr
+ width
-pad_size
, tmpptr
,
388 memset(tmpptr
, ' ', (size_t) width
-pad_size
);
389 dst
+= width
-pad_size
;
397 free(currency_symbol
);
398 return (dst
- s
- 1); /* return size of put data except trailing '\0' */
409 if (asciivalue
!= NULL
)
411 if (currency_symbol
!= NULL
)
412 free(currency_symbol
);
419 __setup_vars(int flags
, char *cs_precedes
, char *sep_by_space
,
420 char *sign_posn
, const char **signstr
) {
421 struct lconv
*lc
= localeconv();
423 if ((flags
& IS_NEGATIVE
) && (flags
& USE_INTL_CURRENCY
)) {
424 *cs_precedes
= lc
->int_n_cs_precedes
;
425 *sep_by_space
= lc
->int_n_sep_by_space
;
426 *sign_posn
= (flags
& PARENTH_POSN
) ? 0 : lc
->int_n_sign_posn
;
427 *signstr
= (lc
->negative_sign
== '\0') ? "-"
429 } else if (flags
& USE_INTL_CURRENCY
) {
430 *cs_precedes
= lc
->int_p_cs_precedes
;
431 *sep_by_space
= lc
->int_p_sep_by_space
;
432 *sign_posn
= (flags
& PARENTH_POSN
) ? 0 : lc
->int_p_sign_posn
;
433 *signstr
= lc
->positive_sign
;
434 } else if (flags
& IS_NEGATIVE
) {
435 *cs_precedes
= lc
->n_cs_precedes
;
436 *sep_by_space
= lc
->n_sep_by_space
;
437 *sign_posn
= (flags
& PARENTH_POSN
) ? 0 : lc
->n_sign_posn
;
438 *signstr
= (lc
->negative_sign
== '\0') ? "-"
441 *cs_precedes
= lc
->p_cs_precedes
;
442 *sep_by_space
= lc
->p_sep_by_space
;
443 *sign_posn
= (flags
& PARENTH_POSN
) ? 0 : lc
->p_sign_posn
;
444 *signstr
= lc
->positive_sign
;
447 /* Set defult values for unspecified information. */
448 if (*cs_precedes
!= 0)
450 if ((unsigned char)*sep_by_space
== NBCHAR_MAX
)
452 if ((unsigned char)*sign_posn
== NBCHAR_MAX
)
457 __calc_left_pad(int flags
, char *cur_symb
) {
459 char cs_precedes
, sep_by_space
, sign_posn
;
461 size_t left_chars
= 0;
463 __setup_vars(flags
, &cs_precedes
, &sep_by_space
, &sign_posn
, &signstr
);
465 if (cs_precedes
!= 0) {
466 left_chars
+= strlen(cur_symb
);
467 if (sep_by_space
!= 0)
473 left_chars
+= strlen(signstr
);
477 if (cs_precedes
!= 0)
478 left_chars
+= strlen(signstr
);
480 _DIAGASSERT(__type_fit(int, left_chars
));
481 return (int)left_chars
;
485 get_groups(int size
, char *grouping
) {
489 if ((unsigned char)*grouping
== NBCHAR_MAX
|| *grouping
<= 0) /* no grouping ? */
492 while (size
> (int)*grouping
) {
494 size
-= (int)*grouping
++;
495 /* no more grouping ? */
496 if ((unsigned char)*grouping
== NBCHAR_MAX
)
498 /* rest grouping with same value ? */
499 if (*grouping
== 0) {
500 chars
+= (size
- 1) / *(grouping
- 1);
507 /* convert double to ASCII */
509 __format_grouped_double(double value
, int *flags
,
510 int left_prec
, int right_prec
, int pad_char
) {
521 struct lconv
*lc
= localeconv();
528 grouping
= lc
->mon_grouping
;
529 decimal_point
= *lc
->mon_decimal_point
;
530 if (decimal_point
== '\0')
531 decimal_point
= *lc
->decimal_point
;
532 thousands_sep
= *lc
->mon_thousands_sep
;
533 if (thousands_sep
== '\0')
534 thousands_sep
= *lc
->thousands_sep
;
536 /* fill left_prec with default value */
540 /* fill right_prec with default value */
541 if (right_prec
== -1) {
542 if (*flags
& USE_INTL_CURRENCY
)
543 right_prec
= lc
->int_frac_digits
;
545 right_prec
= lc
->frac_digits
;
547 if (right_prec
== CHAR_MAX
) /* POSIX locale ? */
551 if (*flags
& NEED_GROUPING
)
552 left_prec
+= get_groups(left_prec
, grouping
);
554 /* convert to string */
555 avalue_size
= asprintf(&avalue
, "%*.*f", left_prec
+ right_prec
+ 1,
560 /* make sure that we've enough space for result string */
561 bufsize
= avalue_size
* 2 + 1;
562 rslt
= malloc(bufsize
);
567 memset(rslt
, 0, bufsize
);
568 bufend
= rslt
+ bufsize
- 1; /* reserve space for trailing '\0' */
570 /* skip spaces at beggining */
572 while (avalue
[padded
] == ' ') {
577 if (right_prec
> 0) {
578 bufend
-= right_prec
;
579 memcpy(bufend
, avalue
+ avalue_size
+padded
-right_prec
,
580 (size_t) right_prec
);
581 *--bufend
= decimal_point
;
582 avalue_size
-= (right_prec
+ 1);
585 /* XXX: Why not use %' instead? */
586 if ((*flags
& NEED_GROUPING
) &&
587 thousands_sep
!= '\0' && /* XXX: need investigation */
588 (unsigned char)*grouping
!= NBCHAR_MAX
&&
590 while (avalue_size
> (int)*grouping
) {
595 /* no more grouping ? */
596 if ((unsigned char)*grouping
== NBCHAR_MAX
)
599 /* rest grouping with same value ? */
600 if (*grouping
== 0) {
602 while (avalue_size
> *grouping
) {
608 if (avalue_size
!= 0)
613 bufend
-= avalue_size
;
614 memcpy(bufend
, avalue
+padded
, (size_t) avalue_size
);
616 padded
--; /* decrease assumed $decimal_point */
619 /* do padding with pad_char */
622 memset(bufend
, pad_char
, (size_t) padded
);
625 bufsize
= bufsize
- (bufend
- rslt
) + 1;
626 memmove(rslt
, bufend
, bufsize
);