xref: /freebsd/lib/libc/stdlib/strfmon.c (revision eff93c8073fac6bbea85067fd1bd804982770c76)
19d430a59SAlexey Zelkin /*-
252d6b430SAlexey Zelkin  * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
39d430a59SAlexey Zelkin  * All rights reserved.
49d430a59SAlexey Zelkin  *
59d430a59SAlexey Zelkin  * Redistribution and use in source and binary forms, with or without
69d430a59SAlexey Zelkin  * modification, are permitted provided that the following conditions
79d430a59SAlexey Zelkin  * are met:
89d430a59SAlexey Zelkin  * 1. Redistributions of source code must retain the above copyright
99d430a59SAlexey Zelkin  *    notice, this list of conditions and the following disclaimer.
109d430a59SAlexey Zelkin  * 2. Redistributions in binary form must reproduce the above copyright
119d430a59SAlexey Zelkin  *    notice, this list of conditions and the following disclaimer in the
129d430a59SAlexey Zelkin  *    documentation and/or other materials provided with the distribution.
139d430a59SAlexey Zelkin  *
149d430a59SAlexey Zelkin  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
159d430a59SAlexey Zelkin  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
169d430a59SAlexey Zelkin  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
179d430a59SAlexey Zelkin  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
189d430a59SAlexey Zelkin  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
199d430a59SAlexey Zelkin  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
209d430a59SAlexey Zelkin  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
219d430a59SAlexey Zelkin  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
229d430a59SAlexey Zelkin  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
239d430a59SAlexey Zelkin  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
249d430a59SAlexey Zelkin  * SUCH DAMAGE.
259d430a59SAlexey Zelkin  *
269d430a59SAlexey Zelkin  */
279d430a59SAlexey Zelkin 
28333fc21eSDavid E. O'Brien #include <sys/cdefs.h>
29333fc21eSDavid E. O'Brien __FBSDID("$FreeBSD$");
309d430a59SAlexey Zelkin 
319d430a59SAlexey Zelkin #include <sys/types.h>
329d430a59SAlexey Zelkin #include <ctype.h>
339d430a59SAlexey Zelkin #include <errno.h>
349d430a59SAlexey Zelkin #include <limits.h>
359d430a59SAlexey Zelkin #include <locale.h>
3661310091SStefan Farfeleder #include <monetary.h>
379d430a59SAlexey Zelkin #include <stdarg.h>
389d430a59SAlexey Zelkin #include <stdio.h>
399d430a59SAlexey Zelkin #include <stdlib.h>
409d430a59SAlexey Zelkin #include <string.h>
419d430a59SAlexey Zelkin 
429d430a59SAlexey Zelkin /* internal flags */
439d430a59SAlexey Zelkin #define	NEED_GROUPING		0x01	/* print digits grouped (default) */
449d430a59SAlexey Zelkin #define	SIGN_POSN_USED		0x02	/* '+' or '(' usage flag */
459d430a59SAlexey Zelkin #define	LOCALE_POSN		0x04	/* use locale defined +/- (default) */
469d430a59SAlexey Zelkin #define	PARENTH_POSN		0x08	/* enclose negative amount in () */
479d430a59SAlexey Zelkin #define	SUPRESS_CURR_SYMBOL	0x10	/* supress the currency from output */
489d430a59SAlexey Zelkin #define	LEFT_JUSTIFY		0x20	/* left justify */
499d430a59SAlexey Zelkin #define	USE_INTL_CURRENCY	0x40	/* use international currency symbol */
509d430a59SAlexey Zelkin #define IS_NEGATIVE		0x80	/* is argument value negative ? */
519d430a59SAlexey Zelkin 
529d430a59SAlexey Zelkin /* internal macros */
53a5aecc77SMike Barcroft #define PRINT(CH) do {						\
549d430a59SAlexey Zelkin 	if (dst >= s + maxsize) 				\
559d430a59SAlexey Zelkin 		goto e2big_error;				\
569d430a59SAlexey Zelkin 	*dst++ = CH;						\
57a5aecc77SMike Barcroft } while (0)
589d430a59SAlexey Zelkin 
59a5aecc77SMike Barcroft #define PRINTS(STR) do {					\
609d430a59SAlexey Zelkin 	char *tmps = STR;					\
619d430a59SAlexey Zelkin 	while (*tmps != '\0')					\
629d430a59SAlexey Zelkin 		PRINT(*tmps++);					\
63a5aecc77SMike Barcroft } while (0)
649d430a59SAlexey Zelkin 
65a5aecc77SMike Barcroft #define GET_NUMBER(VAR)	do {					\
669d430a59SAlexey Zelkin 	VAR = 0;						\
679d430a59SAlexey Zelkin 	while (isdigit((unsigned char)*fmt)) {			\
68eff93c80SRuslan Ermilov 		if (VAR > INT_MAX / 10)				\
69eff93c80SRuslan Ermilov 			goto e2big_error;			\
709d430a59SAlexey Zelkin 		VAR *= 10;					\
719d430a59SAlexey Zelkin 		VAR += *fmt - '0';				\
723890416fSRuslan Ermilov 		if (VAR < 0)					\
733890416fSRuslan Ermilov 			goto e2big_error;			\
749d430a59SAlexey Zelkin 		fmt++;						\
759d430a59SAlexey Zelkin 	}							\
76a5aecc77SMike Barcroft } while (0)
77a5aecc77SMike Barcroft 
78a5aecc77SMike Barcroft #define GRPCPY(howmany) do {					\
79a5aecc77SMike Barcroft 	int i = howmany;					\
80a5aecc77SMike Barcroft 	while (i-- > 0) {					\
81a5aecc77SMike Barcroft 		avalue_size--;					\
82a5aecc77SMike Barcroft 		*--bufend = *(avalue+avalue_size+padded);	\
83a5aecc77SMike Barcroft 	}							\
84a5aecc77SMike Barcroft } while (0)
85a5aecc77SMike Barcroft 
86a5aecc77SMike Barcroft #define GRPSEP do {						\
87a5aecc77SMike Barcroft 	*--bufend = thousands_sep;				\
88a5aecc77SMike Barcroft 	groups++;						\
89a5aecc77SMike Barcroft } while (0)
909d430a59SAlexey Zelkin 
919d430a59SAlexey Zelkin static void __setup_vars(int, char *, char *, char *, char **);
929d430a59SAlexey Zelkin static int __calc_left_pad(int, char *);
939d430a59SAlexey Zelkin static char *__format_grouped_double(double, int *, int, int, int);
949d430a59SAlexey Zelkin 
959d430a59SAlexey Zelkin ssize_t
96883738f2SMike Barcroft strfmon(char * __restrict s, size_t maxsize, const char * __restrict format,
97883738f2SMike Barcroft     ...)
989d430a59SAlexey Zelkin {
999d430a59SAlexey Zelkin 	va_list		ap;
1009d430a59SAlexey Zelkin 	char 		*dst;		/* output destination pointer */
1019d430a59SAlexey Zelkin 	const char 	*fmt;		/* current format poistion pointer */
1029d430a59SAlexey Zelkin 	struct lconv 	*lc;		/* pointer to lconv structure */
1039d430a59SAlexey Zelkin 	char		*asciivalue;	/* formatted double pointer */
1049d430a59SAlexey Zelkin 
1059d430a59SAlexey Zelkin 	int		flags;		/* formatting options */
1069d430a59SAlexey Zelkin 	int		pad_char;	/* padding character */
1079d430a59SAlexey Zelkin 	int		pad_size;	/* pad size */
1089d430a59SAlexey Zelkin 	int		width;		/* field width */
1099d430a59SAlexey Zelkin 	int		left_prec;	/* left precision */
1109d430a59SAlexey Zelkin 	int		right_prec;	/* right precision */
1119d430a59SAlexey Zelkin 	double		value;		/* just value */
1129d430a59SAlexey Zelkin 	char		space_char = ' '; /* space after currency */
1139d430a59SAlexey Zelkin 
1149d430a59SAlexey Zelkin 	char		cs_precedes,	/* values gathered from struct lconv */
1159d430a59SAlexey Zelkin 			sep_by_space,
1169d430a59SAlexey Zelkin 			sign_posn,
1179d430a59SAlexey Zelkin 			*signstr,
1189d430a59SAlexey Zelkin 			*currency_symbol;
1199d430a59SAlexey Zelkin 
1209d430a59SAlexey Zelkin 	char		*tmpptr;	/* temporary vars */
1212e9212d9STim J. Robbins 	int		sverrno;
1229d430a59SAlexey Zelkin 
1239d430a59SAlexey Zelkin         va_start(ap, format);
1249d430a59SAlexey Zelkin 
1259d430a59SAlexey Zelkin 	lc = localeconv();
1269d430a59SAlexey Zelkin 	dst = s;
1279d430a59SAlexey Zelkin 	fmt = format;
1289d430a59SAlexey Zelkin 	asciivalue = NULL;
1299d430a59SAlexey Zelkin 	currency_symbol = NULL;
1309d430a59SAlexey Zelkin 	pad_size = 0;
1319d430a59SAlexey Zelkin 
1329d430a59SAlexey Zelkin 	while (*fmt) {
1339d430a59SAlexey Zelkin 		/* pass nonformating characters AS IS */
134a5aecc77SMike Barcroft 		if (*fmt != '%')
1359d430a59SAlexey Zelkin 			goto literal;
1369d430a59SAlexey Zelkin 
1379d430a59SAlexey Zelkin 		/* '%' found ! */
1389d430a59SAlexey Zelkin 
1399d430a59SAlexey Zelkin 		/* "%%" mean just '%' */
1409d430a59SAlexey Zelkin 		if (*(fmt+1) == '%') {
1419d430a59SAlexey Zelkin 			fmt++;
1429d430a59SAlexey Zelkin 	literal:
1439d430a59SAlexey Zelkin 			PRINT(*fmt++);
1449d430a59SAlexey Zelkin 			continue;
1459d430a59SAlexey Zelkin 		}
1469d430a59SAlexey Zelkin 
1479d430a59SAlexey Zelkin 		/* set up initial values */
1489d430a59SAlexey Zelkin 		flags = (NEED_GROUPING|LOCALE_POSN);
1499d430a59SAlexey Zelkin 		pad_char = ' ';		/* padding character is "space" */
1509d430a59SAlexey Zelkin 		left_prec = -1;		/* no left precision specified */
1519d430a59SAlexey Zelkin 		right_prec = -1;	/* no right precision specified */
1529d430a59SAlexey Zelkin 		width = -1;		/* no width specified */
1539d430a59SAlexey Zelkin 		value = 0;		/* we have no value to print now */
1549d430a59SAlexey Zelkin 
1559d430a59SAlexey Zelkin 		/* Flags */
1569d430a59SAlexey Zelkin 		while (1) {
1579d430a59SAlexey Zelkin 			switch (*++fmt) {
1589d430a59SAlexey Zelkin 				case '=':	/* fill character */
1599d430a59SAlexey Zelkin 					pad_char = *++fmt;
1609d430a59SAlexey Zelkin 					if (pad_char == '\0')
1619d430a59SAlexey Zelkin 						goto format_error;
1629d430a59SAlexey Zelkin 					continue;
1639d430a59SAlexey Zelkin 				case '^':	/* not group currency  */
1649d430a59SAlexey Zelkin 					flags &= ~(NEED_GROUPING);
1659d430a59SAlexey Zelkin 					continue;
1669d430a59SAlexey Zelkin 				case '+':	/* use locale defined signs */
1679d430a59SAlexey Zelkin 					if (flags & SIGN_POSN_USED)
1689d430a59SAlexey Zelkin 						goto format_error;
1699d430a59SAlexey Zelkin 					flags |= (SIGN_POSN_USED|LOCALE_POSN);
1709d430a59SAlexey Zelkin 					continue;
1719d430a59SAlexey Zelkin 				case '(':	/* enclose negatives with () */
1729d430a59SAlexey Zelkin 					if (flags & SIGN_POSN_USED)
1739d430a59SAlexey Zelkin 						goto format_error;
1749d430a59SAlexey Zelkin 					flags |= (SIGN_POSN_USED|PARENTH_POSN);
1759d430a59SAlexey Zelkin 					continue;
1769d430a59SAlexey Zelkin 				case '!':	/* suppress currency symbol */
1779d430a59SAlexey Zelkin 					flags |= SUPRESS_CURR_SYMBOL;
1789d430a59SAlexey Zelkin 					continue;
1799d430a59SAlexey Zelkin 				case '-':	/* alignment (left)  */
1809d430a59SAlexey Zelkin 					flags |= LEFT_JUSTIFY;
1819d430a59SAlexey Zelkin 					continue;
1829d430a59SAlexey Zelkin 				default:
1839d430a59SAlexey Zelkin 					break;
1849d430a59SAlexey Zelkin 			}
1859d430a59SAlexey Zelkin 			break;
1869d430a59SAlexey Zelkin 		}
1879d430a59SAlexey Zelkin 
1889d430a59SAlexey Zelkin 		/* field Width */
1899d430a59SAlexey Zelkin 		if (isdigit((unsigned char)*fmt)) {
1909d430a59SAlexey Zelkin 			GET_NUMBER(width);
1919d430a59SAlexey Zelkin 			/* Do we have enough space to put number with
1929d430a59SAlexey Zelkin 			 * required width ?
1939d430a59SAlexey Zelkin 			 */
1943890416fSRuslan Ermilov 			if ((unsigned int)width >= maxsize - (dst - s))
1959d430a59SAlexey Zelkin 				goto e2big_error;
1969d430a59SAlexey Zelkin 		}
1979d430a59SAlexey Zelkin 
198284d5622STim J. Robbins 		/* Left precision */
199284d5622STim J. Robbins 		if (*fmt == '#') {
200284d5622STim J. Robbins 			if (!isdigit((unsigned char)*++fmt))
201284d5622STim J. Robbins 				goto format_error;
202284d5622STim J. Robbins 			GET_NUMBER(left_prec);
2033890416fSRuslan Ermilov 			if ((unsigned int)left_prec >= maxsize - (dst - s))
2043890416fSRuslan Ermilov 				goto e2big_error;
205284d5622STim J. Robbins 		}
206284d5622STim J. Robbins 
207284d5622STim J. Robbins 		/* Right precision */
208284d5622STim J. Robbins 		if (*fmt == '.') {
209284d5622STim J. Robbins 			if (!isdigit((unsigned char)*++fmt))
210284d5622STim J. Robbins 				goto format_error;
211284d5622STim J. Robbins 			GET_NUMBER(right_prec);
2123890416fSRuslan Ermilov 			if ((unsigned int)right_prec >= maxsize - (dst - s) -
2133890416fSRuslan Ermilov 			    left_prec)
2143890416fSRuslan Ermilov 				goto e2big_error;
215284d5622STim J. Robbins 		}
216284d5622STim J. Robbins 
2179d430a59SAlexey Zelkin 		/* Conversion Characters */
2189d430a59SAlexey Zelkin 		switch (*fmt++) {
2199d430a59SAlexey Zelkin 			case 'i':	/* use internaltion currency format */
2209d430a59SAlexey Zelkin 				flags |= USE_INTL_CURRENCY;
2219d430a59SAlexey Zelkin 				break;
2229d430a59SAlexey Zelkin 			case 'n':	/* use national currency format */
2239d430a59SAlexey Zelkin 				flags &= ~(USE_INTL_CURRENCY);
2249d430a59SAlexey Zelkin 				break;
2259d430a59SAlexey Zelkin 			default:	/* required character is missing or
2269d430a59SAlexey Zelkin 					   premature EOS */
2279d430a59SAlexey Zelkin 				goto format_error;
2289d430a59SAlexey Zelkin 		}
2299d430a59SAlexey Zelkin 
2305b30d6caSRuslan Ermilov 		if (currency_symbol != NULL)
2315b30d6caSRuslan Ermilov 			free(currency_symbol);
2329d430a59SAlexey Zelkin 		if (flags & USE_INTL_CURRENCY) {
2339d430a59SAlexey Zelkin 			currency_symbol = strdup(lc->int_curr_symbol);
2349d430a59SAlexey Zelkin 			if (currency_symbol != NULL)
2359d430a59SAlexey Zelkin 				space_char = *(currency_symbol+3);
236a5aecc77SMike Barcroft 		} else
2379d430a59SAlexey Zelkin 			currency_symbol = strdup(lc->currency_symbol);
2389d430a59SAlexey Zelkin 
2399d430a59SAlexey Zelkin 		if (currency_symbol == NULL)
2409d430a59SAlexey Zelkin 			goto end_error;			/* ENOMEM. */
2419d430a59SAlexey Zelkin 
2429d430a59SAlexey Zelkin 		/* value itself */
2439d430a59SAlexey Zelkin 		value = va_arg(ap, double);
2449d430a59SAlexey Zelkin 
2459d430a59SAlexey Zelkin 		/* detect sign */
2469d430a59SAlexey Zelkin 		if (value < 0) {
2479d430a59SAlexey Zelkin 			flags |= IS_NEGATIVE;
2489d430a59SAlexey Zelkin 			value = -value;
2499d430a59SAlexey Zelkin 		}
2509d430a59SAlexey Zelkin 
2519d430a59SAlexey Zelkin 		/* fill left_prec with amount of padding chars */
2529d430a59SAlexey Zelkin 		if (left_prec >= 0) {
2539d430a59SAlexey Zelkin 			pad_size = __calc_left_pad((flags ^ IS_NEGATIVE),
2549d430a59SAlexey Zelkin 							currency_symbol) -
2559d430a59SAlexey Zelkin 				   __calc_left_pad(flags, currency_symbol);
2569d430a59SAlexey Zelkin 			if (pad_size < 0)
2579d430a59SAlexey Zelkin 				pad_size = 0;
2589d430a59SAlexey Zelkin 		}
2599d430a59SAlexey Zelkin 
2605b30d6caSRuslan Ermilov 		if (asciivalue != NULL)
2615b30d6caSRuslan Ermilov 			free(asciivalue);
2629d430a59SAlexey Zelkin 		asciivalue = __format_grouped_double(value, &flags,
2639d430a59SAlexey Zelkin 				left_prec, right_prec, pad_char);
2649d430a59SAlexey Zelkin 		if (asciivalue == NULL)
2659d430a59SAlexey Zelkin 			goto end_error;		/* errno already set     */
2669d430a59SAlexey Zelkin 						/* to ENOMEM by malloc() */
2679d430a59SAlexey Zelkin 
2689d430a59SAlexey Zelkin 		/* set some variables for later use */
2699d430a59SAlexey Zelkin 		__setup_vars(flags, &cs_precedes, &sep_by_space,
2709d430a59SAlexey Zelkin 				&sign_posn, &signstr);
2719d430a59SAlexey Zelkin 
2729d430a59SAlexey Zelkin 		/*
2739d430a59SAlexey Zelkin 		 * Description of some LC_MONETARY's values:
2749d430a59SAlexey Zelkin 		 *
2759d430a59SAlexey Zelkin 		 * p_cs_precedes & n_cs_precedes
2769d430a59SAlexey Zelkin 		 *
2779d430a59SAlexey Zelkin 		 * = 1 - $currency_symbol precedes the value
2789d430a59SAlexey Zelkin 		 *       for a monetary quantity with a non-negative value
2799d430a59SAlexey Zelkin 		 * = 0 - symbol succeeds the value
2809d430a59SAlexey Zelkin 		 *
2819d430a59SAlexey Zelkin 		 * p_sep_by_space & n_sep_by_space
2829d430a59SAlexey Zelkin                  *
2839d430a59SAlexey Zelkin 		 * = 0 - no space separates $currency_symbol
2849d430a59SAlexey Zelkin 		 *       from the value for a monetary quantity with a
2859d430a59SAlexey Zelkin 		 *	 non-negative value
2869d430a59SAlexey Zelkin 		 * = 1 - space separates the symbol from the value
2879d430a59SAlexey Zelkin 		 * = 2 - space separates the symbol and the sign string,
2889d430a59SAlexey Zelkin 		 *       if adjacent.
2899d430a59SAlexey Zelkin                  *
2909d430a59SAlexey Zelkin 		 * p_sign_posn & n_sign_posn
2919d430a59SAlexey Zelkin                  *
2929d430a59SAlexey Zelkin 		 * = 0 - parentheses enclose the quantity and the
2939d430a59SAlexey Zelkin 		 *	 $currency_symbol
2949d430a59SAlexey Zelkin 		 * = 1 - the sign string precedes the quantity and the
2959d430a59SAlexey Zelkin 		 *       $currency_symbol
2969d430a59SAlexey Zelkin 		 * = 2 - the sign string succeeds the quantity and the
2979d430a59SAlexey Zelkin 		 *       $currency_symbol
2989d430a59SAlexey Zelkin 		 * = 3 - the sign string precedes the $currency_symbol
2999d430a59SAlexey Zelkin 		 * = 4 - the sign string succeeds the $currency_symbol
3009d430a59SAlexey Zelkin                  *
3019d430a59SAlexey Zelkin 		 */
3029d430a59SAlexey Zelkin 
3039d430a59SAlexey Zelkin 		tmpptr = dst;
3049d430a59SAlexey Zelkin 
3059d430a59SAlexey Zelkin 		while (pad_size-- > 0)
3069d430a59SAlexey Zelkin 			PRINT(' ');
3079d430a59SAlexey Zelkin 
30840a48101STim J. Robbins 		if (sign_posn == 0 && (flags & IS_NEGATIVE))
3099d430a59SAlexey Zelkin 			PRINT('(');
3109d430a59SAlexey Zelkin 
3119d430a59SAlexey Zelkin 		if (cs_precedes == 1) {
3129d430a59SAlexey Zelkin 			if (sign_posn == 1 || sign_posn == 3) {
3139d430a59SAlexey Zelkin 				PRINTS(signstr);
3149d430a59SAlexey Zelkin 				if (sep_by_space == 2)		/* XXX: ? */
3159d430a59SAlexey Zelkin 					PRINT(' ');
3169d430a59SAlexey Zelkin 			}
3179d430a59SAlexey Zelkin 
3189d430a59SAlexey Zelkin 			if (!(flags & SUPRESS_CURR_SYMBOL)) {
3199d430a59SAlexey Zelkin 				PRINTS(currency_symbol);
3209d430a59SAlexey Zelkin 
3219d430a59SAlexey Zelkin 				if (sign_posn == 4) {
3229d430a59SAlexey Zelkin 					if (sep_by_space == 2)
3239d430a59SAlexey Zelkin 						PRINT(space_char);
3249d430a59SAlexey Zelkin 					PRINTS(signstr);
3259d430a59SAlexey Zelkin 					if (sep_by_space == 1)
3269d430a59SAlexey Zelkin 						PRINT(' ');
327a5aecc77SMike Barcroft 				} else if (sep_by_space == 1)
3289d430a59SAlexey Zelkin 					PRINT(space_char);
3299d430a59SAlexey Zelkin 			}
330a5aecc77SMike Barcroft 		} else if (sign_posn == 1)
3319d430a59SAlexey Zelkin 			PRINTS(signstr);
3329d430a59SAlexey Zelkin 
3339d430a59SAlexey Zelkin 		PRINTS(asciivalue);
3349d430a59SAlexey Zelkin 
3359d430a59SAlexey Zelkin 		if (cs_precedes == 0) {
3369d430a59SAlexey Zelkin 			if (sign_posn == 3) {
3379d430a59SAlexey Zelkin 				if (sep_by_space == 1)
3389d430a59SAlexey Zelkin 					PRINT(' ');
3399d430a59SAlexey Zelkin 				PRINTS(signstr);
3409d430a59SAlexey Zelkin 			}
3419d430a59SAlexey Zelkin 
3429d430a59SAlexey Zelkin 			if (!(flags & SUPRESS_CURR_SYMBOL)) {
3439d430a59SAlexey Zelkin 				if ((sign_posn == 3 && sep_by_space == 2)
3449d430a59SAlexey Zelkin 				    || (sep_by_space == 1
345bd26dcd1STim J. Robbins 				    && (sign_posn == 0
3469d430a59SAlexey Zelkin 				    || sign_posn == 1
3479d430a59SAlexey Zelkin 				    || sign_posn == 2
3489d430a59SAlexey Zelkin 				    || sign_posn == 4)))
3499d430a59SAlexey Zelkin 					PRINT(space_char);
3509d430a59SAlexey Zelkin 				PRINTS(currency_symbol); /* XXX: len */
3519d430a59SAlexey Zelkin 				if (sign_posn == 4) {
3529d430a59SAlexey Zelkin 					if (sep_by_space == 2)
3539d430a59SAlexey Zelkin 						PRINT(' ');
3549d430a59SAlexey Zelkin 					PRINTS(signstr);
3559d430a59SAlexey Zelkin 				}
3569d430a59SAlexey Zelkin 			}
3579d430a59SAlexey Zelkin 		}
3589d430a59SAlexey Zelkin 
3599d430a59SAlexey Zelkin 		if (sign_posn == 2) {
3609d430a59SAlexey Zelkin 			if (sep_by_space == 2)
3619d430a59SAlexey Zelkin 				PRINT(' ');
3629d430a59SAlexey Zelkin 			PRINTS(signstr);
3639d430a59SAlexey Zelkin 		}
3649d430a59SAlexey Zelkin 
3659d430a59SAlexey Zelkin 		if (sign_posn == 0 && (flags & IS_NEGATIVE))
3669d430a59SAlexey Zelkin 			PRINT(')');
3679d430a59SAlexey Zelkin 
3689d430a59SAlexey Zelkin 		if (dst - tmpptr < width) {
3699d430a59SAlexey Zelkin 			if (flags & LEFT_JUSTIFY) {
370bd26dcd1STim J. Robbins 				while (dst - tmpptr < width)
3719d430a59SAlexey Zelkin 					PRINT(' ');
3729d430a59SAlexey Zelkin 			} else {
3739d430a59SAlexey Zelkin 				pad_size = dst-tmpptr;
374a5aecc77SMike Barcroft 				memmove(tmpptr + width-pad_size, tmpptr,
375a5aecc77SMike Barcroft 				    pad_size);
3769d430a59SAlexey Zelkin 				memset(tmpptr, ' ', width-pad_size);
3779d430a59SAlexey Zelkin 				dst += width-pad_size;
3789d430a59SAlexey Zelkin 			}
3799d430a59SAlexey Zelkin 		}
3809d430a59SAlexey Zelkin 	}
3819d430a59SAlexey Zelkin 
3829d430a59SAlexey Zelkin 	PRINT('\0');
3839d430a59SAlexey Zelkin 	va_end(ap);
384bd26dcd1STim J. Robbins 	free(asciivalue);
385bd26dcd1STim J. Robbins 	free(currency_symbol);
3869d430a59SAlexey Zelkin 	return (dst - s - 1);	/* return size of put data except trailing '\0' */
3879d430a59SAlexey Zelkin 
3889d430a59SAlexey Zelkin e2big_error:
3899d430a59SAlexey Zelkin 	errno = E2BIG;
3909d430a59SAlexey Zelkin 	goto end_error;
3919d430a59SAlexey Zelkin 
3929d430a59SAlexey Zelkin format_error:
3939d430a59SAlexey Zelkin 	errno = EINVAL;
3949d430a59SAlexey Zelkin 
3959d430a59SAlexey Zelkin end_error:
3962e9212d9STim J. Robbins 	sverrno = errno;
3979d430a59SAlexey Zelkin 	if (asciivalue != NULL)
3989d430a59SAlexey Zelkin 		free(asciivalue);
3999d430a59SAlexey Zelkin 	if (currency_symbol != NULL)
4009d430a59SAlexey Zelkin 		free(currency_symbol);
4012e9212d9STim J. Robbins 	errno = sverrno;
4029d430a59SAlexey Zelkin 	va_end(ap);
4039d430a59SAlexey Zelkin 	return (-1);
4049d430a59SAlexey Zelkin }
4059d430a59SAlexey Zelkin 
4069d430a59SAlexey Zelkin static void
4079d430a59SAlexey Zelkin __setup_vars(int flags, char *cs_precedes, char *sep_by_space,
4089d430a59SAlexey Zelkin 		char *sign_posn, char **signstr) {
4099d430a59SAlexey Zelkin 
4109d430a59SAlexey Zelkin 	struct lconv *lc = localeconv();
4119d430a59SAlexey Zelkin 
4122621915fSTim J. Robbins 	if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) {
4132621915fSTim J. Robbins 		*cs_precedes = lc->int_n_cs_precedes;
4142621915fSTim J. Robbins 		*sep_by_space = lc->int_n_sep_by_space;
4152621915fSTim J. Robbins 		*sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_n_sign_posn;
4162621915fSTim J. Robbins 		*signstr = (lc->negative_sign == '\0') ? "-"
4172621915fSTim J. Robbins 		    : lc->negative_sign;
4182621915fSTim J. Robbins 	} else if (flags & USE_INTL_CURRENCY) {
4192621915fSTim J. Robbins 		*cs_precedes = lc->int_p_cs_precedes;
4202621915fSTim J. Robbins 		*sep_by_space = lc->int_p_sep_by_space;
4212621915fSTim J. Robbins 		*sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_p_sign_posn;
4222621915fSTim J. Robbins 		*signstr = lc->positive_sign;
4232621915fSTim J. Robbins 	} else if (flags & IS_NEGATIVE) {
4249d430a59SAlexey Zelkin 		*cs_precedes = lc->n_cs_precedes;
4259d430a59SAlexey Zelkin 		*sep_by_space = lc->n_sep_by_space;
4269d430a59SAlexey Zelkin 		*sign_posn = (flags & PARENTH_POSN) ? 0 : lc->n_sign_posn;
427a5aecc77SMike Barcroft 		*signstr = (lc->negative_sign == '\0') ? "-"
428a5aecc77SMike Barcroft 		    : lc->negative_sign;
4299d430a59SAlexey Zelkin 	} else {
4309d430a59SAlexey Zelkin 		*cs_precedes = lc->p_cs_precedes;
4319d430a59SAlexey Zelkin 		*sep_by_space = lc->p_sep_by_space;
4329d430a59SAlexey Zelkin 		*sign_posn = (flags & PARENTH_POSN) ? 0 : lc->p_sign_posn;
4339d430a59SAlexey Zelkin 		*signstr = lc->positive_sign;
4349d430a59SAlexey Zelkin 	}
4359d430a59SAlexey Zelkin 
4369d430a59SAlexey Zelkin 	/* Set defult values for unspecified information. */
4379d430a59SAlexey Zelkin 	if (*cs_precedes != 0)
4389d430a59SAlexey Zelkin 		*cs_precedes = 1;
4399d430a59SAlexey Zelkin 	if (*sep_by_space == CHAR_MAX)
4409d430a59SAlexey Zelkin 		*sep_by_space = 0;
4419d430a59SAlexey Zelkin 	if (*sign_posn == CHAR_MAX)
4429d430a59SAlexey Zelkin 		*sign_posn = 0;
4439d430a59SAlexey Zelkin }
4449d430a59SAlexey Zelkin 
4459d430a59SAlexey Zelkin static int
4469d430a59SAlexey Zelkin __calc_left_pad(int flags, char *cur_symb) {
4479d430a59SAlexey Zelkin 
4489d430a59SAlexey Zelkin 	char cs_precedes, sep_by_space, sign_posn, *signstr;
4499d430a59SAlexey Zelkin 	int left_chars = 0;
4509d430a59SAlexey Zelkin 
4519d430a59SAlexey Zelkin 	__setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, &signstr);
4529d430a59SAlexey Zelkin 
4539d430a59SAlexey Zelkin 	if (cs_precedes != 0) {
4549d430a59SAlexey Zelkin 		left_chars += strlen(cur_symb);
4559d430a59SAlexey Zelkin 		if (sep_by_space != 0)
4569d430a59SAlexey Zelkin 			left_chars++;
4579d430a59SAlexey Zelkin 	}
4589d430a59SAlexey Zelkin 
4599d430a59SAlexey Zelkin 	switch (sign_posn) {
4609d430a59SAlexey Zelkin 		case 1:
4619d430a59SAlexey Zelkin 			left_chars += strlen(signstr);
4629d430a59SAlexey Zelkin 			break;
4639d430a59SAlexey Zelkin 		case 3:
4649d430a59SAlexey Zelkin 		case 4:
4659d430a59SAlexey Zelkin 			if (cs_precedes != 0)
4669d430a59SAlexey Zelkin 				left_chars += strlen(signstr);
4679d430a59SAlexey Zelkin 	}
468a5aecc77SMike Barcroft 	return (left_chars);
4699d430a59SAlexey Zelkin }
4709d430a59SAlexey Zelkin 
4719d430a59SAlexey Zelkin static int
4729d430a59SAlexey Zelkin get_groups(int size, char *grouping) {
4739d430a59SAlexey Zelkin 
4749d430a59SAlexey Zelkin 	int	chars = 0;
4759d430a59SAlexey Zelkin 
4769d430a59SAlexey Zelkin 	if (*grouping == CHAR_MAX || *grouping <= 0)	/* no grouping ? */
477a5aecc77SMike Barcroft 		return (0);
4789d430a59SAlexey Zelkin 
4799d430a59SAlexey Zelkin 	while (size > (int)*grouping) {
4809d430a59SAlexey Zelkin 		chars++;
4819d430a59SAlexey Zelkin 		size -= (int)*grouping++;
4829d430a59SAlexey Zelkin 		/* no more grouping ? */
4839d430a59SAlexey Zelkin 		if (*grouping == CHAR_MAX)
4849d430a59SAlexey Zelkin 			break;
4859d430a59SAlexey Zelkin 		/* rest grouping with same value ? */
4869d430a59SAlexey Zelkin 		if (*grouping == 0) {
4879d430a59SAlexey Zelkin 			chars += (size - 1) / *(grouping - 1);
4889d430a59SAlexey Zelkin 			break;
4899d430a59SAlexey Zelkin 		}
4909d430a59SAlexey Zelkin 	}
491a5aecc77SMike Barcroft 	return (chars);
4929d430a59SAlexey Zelkin }
4939d430a59SAlexey Zelkin 
4949d430a59SAlexey Zelkin /* convert double to ASCII */
4959d430a59SAlexey Zelkin static char *
4969d430a59SAlexey Zelkin __format_grouped_double(double value, int *flags,
4979d430a59SAlexey Zelkin 			int left_prec, int right_prec, int pad_char) {
4989d430a59SAlexey Zelkin 
4999d430a59SAlexey Zelkin 	char		*rslt;
5009d430a59SAlexey Zelkin 	char		*avalue;
5019d430a59SAlexey Zelkin 	int		avalue_size;
5029d430a59SAlexey Zelkin 	char		fmt[32];
5039d430a59SAlexey Zelkin 
5049d430a59SAlexey Zelkin 	size_t		bufsize;
5059d430a59SAlexey Zelkin 	char		*bufend;
5069d430a59SAlexey Zelkin 
5079d430a59SAlexey Zelkin 	int		padded;
5089d430a59SAlexey Zelkin 
5099d430a59SAlexey Zelkin 	struct lconv	*lc = localeconv();
5109d430a59SAlexey Zelkin 	char		*grouping;
5119d430a59SAlexey Zelkin 	char		decimal_point;
5129d430a59SAlexey Zelkin 	char		thousands_sep;
5139d430a59SAlexey Zelkin 
5149d430a59SAlexey Zelkin 	int groups = 0;
5159d430a59SAlexey Zelkin 
5169d430a59SAlexey Zelkin 	grouping = lc->mon_grouping;
5179d430a59SAlexey Zelkin 	decimal_point = *lc->mon_decimal_point;
5189d430a59SAlexey Zelkin 	if (decimal_point == '\0')
51985bebbc1SAndrey A. Chernov 		decimal_point = *lc->decimal_point;
5209d430a59SAlexey Zelkin 	thousands_sep = *lc->mon_thousands_sep;
521a5aecc77SMike Barcroft 	if (thousands_sep == '\0')
5229d430a59SAlexey Zelkin 		thousands_sep = *lc->thousands_sep;
5239d430a59SAlexey Zelkin 
5249d430a59SAlexey Zelkin 	/* fill left_prec with default value */
5259d430a59SAlexey Zelkin 	if (left_prec == -1)
5269d430a59SAlexey Zelkin 		left_prec = 0;
5279d430a59SAlexey Zelkin 
5289d430a59SAlexey Zelkin 	/* fill right_prec with default value */
5299d430a59SAlexey Zelkin 	if (right_prec == -1) {
5309d430a59SAlexey Zelkin                 if (*flags & USE_INTL_CURRENCY)
5319d430a59SAlexey Zelkin                         right_prec = lc->int_frac_digits;
5329d430a59SAlexey Zelkin                 else
5339d430a59SAlexey Zelkin                         right_prec = lc->frac_digits;
5349d430a59SAlexey Zelkin 
5359d430a59SAlexey Zelkin 		if (right_prec == CHAR_MAX)	/* POSIX locale ? */
5369d430a59SAlexey Zelkin 			right_prec = 2;
5379d430a59SAlexey Zelkin 	}
5389d430a59SAlexey Zelkin 
5399d430a59SAlexey Zelkin 	if (*flags & NEED_GROUPING)
5409d430a59SAlexey Zelkin 		left_prec += get_groups(left_prec, grouping);
5419d430a59SAlexey Zelkin 
5429d430a59SAlexey Zelkin 	/* convert to string */
543a5aecc77SMike Barcroft 	snprintf(fmt, sizeof(fmt), "%%%d.%df", left_prec + right_prec + 1,
544a5aecc77SMike Barcroft 	    right_prec);
5459d430a59SAlexey Zelkin 	avalue_size = asprintf(&avalue, fmt, value);
5469d430a59SAlexey Zelkin 	if (avalue_size < 0)
547a5aecc77SMike Barcroft 		return (NULL);
5489d430a59SAlexey Zelkin 
5499d430a59SAlexey Zelkin 	/* make sure that we've enough space for result string */
5509d430a59SAlexey Zelkin 	bufsize = strlen(avalue)*2+1;
55192226c92SXin LI 	rslt = calloc(1, bufsize);
5529d430a59SAlexey Zelkin 	if (rslt == NULL) {
5539d430a59SAlexey Zelkin 		free(avalue);
554a5aecc77SMike Barcroft 		return (NULL);
5559d430a59SAlexey Zelkin 	}
5569d430a59SAlexey Zelkin 	bufend = rslt + bufsize - 1;	/* reserve space for trailing '\0' */
5579d430a59SAlexey Zelkin 
5589d430a59SAlexey Zelkin 	/* skip spaces at beggining */
5599d430a59SAlexey Zelkin 	padded = 0;
5609d430a59SAlexey Zelkin 	while (avalue[padded] == ' ') {
5619d430a59SAlexey Zelkin 		padded++;
5629d430a59SAlexey Zelkin 		avalue_size--;
5639d430a59SAlexey Zelkin 	}
5649d430a59SAlexey Zelkin 
5659d430a59SAlexey Zelkin 	if (right_prec > 0) {
5669d430a59SAlexey Zelkin 		bufend -= right_prec;
567a5aecc77SMike Barcroft 		memcpy(bufend, avalue + avalue_size+padded-right_prec,
568a5aecc77SMike Barcroft 		    right_prec);
5699d430a59SAlexey Zelkin 		*--bufend = decimal_point;
5709d430a59SAlexey Zelkin 		avalue_size -= (right_prec + 1);
5719d430a59SAlexey Zelkin 	}
5729d430a59SAlexey Zelkin 
5739d430a59SAlexey Zelkin 	if ((*flags & NEED_GROUPING) &&
5749d430a59SAlexey Zelkin 	    thousands_sep != '\0' &&	/* XXX: need investigation */
5759d430a59SAlexey Zelkin 	    *grouping != CHAR_MAX &&
5769d430a59SAlexey Zelkin 	    *grouping > 0) {
5779d430a59SAlexey Zelkin 		while (avalue_size > (int)*grouping) {
5789d430a59SAlexey Zelkin 			GRPCPY(*grouping);
5799d430a59SAlexey Zelkin 			GRPSEP;
5809d430a59SAlexey Zelkin 			grouping++;
5819d430a59SAlexey Zelkin 
5829d430a59SAlexey Zelkin 			/* no more grouping ? */
5839d430a59SAlexey Zelkin 			if (*grouping == CHAR_MAX)
5849d430a59SAlexey Zelkin 				break;
5859d430a59SAlexey Zelkin 
5869d430a59SAlexey Zelkin 			/* rest grouping with same value ? */
5879d430a59SAlexey Zelkin 			if (*grouping == 0) {
5889d430a59SAlexey Zelkin 				grouping--;
5899d430a59SAlexey Zelkin 				while (avalue_size > *grouping) {
5909d430a59SAlexey Zelkin 					GRPCPY(*grouping);
5919d430a59SAlexey Zelkin 					GRPSEP;
5929d430a59SAlexey Zelkin 				}
5939d430a59SAlexey Zelkin 			}
5949d430a59SAlexey Zelkin 		}
5959d430a59SAlexey Zelkin 		if (avalue_size != 0)
5969d430a59SAlexey Zelkin 			GRPCPY(avalue_size);
5979d430a59SAlexey Zelkin 		padded -= groups;
5989d430a59SAlexey Zelkin 
5999d430a59SAlexey Zelkin 	} else {
6009d430a59SAlexey Zelkin 		bufend -= avalue_size;
6019d430a59SAlexey Zelkin 		memcpy(bufend, avalue+padded, avalue_size);
6029d430a59SAlexey Zelkin 		if (right_prec == 0)
6039d430a59SAlexey Zelkin 			padded--;	/* decrease assumed $decimal_point */
6049d430a59SAlexey Zelkin 	}
6059d430a59SAlexey Zelkin 
6069d430a59SAlexey Zelkin 	/* do padding with pad_char */
6079d430a59SAlexey Zelkin 	if (padded > 0) {
6089d430a59SAlexey Zelkin 		bufend -= padded;
6099d430a59SAlexey Zelkin 		memset(bufend, pad_char, padded);
6109d430a59SAlexey Zelkin 	}
6119d430a59SAlexey Zelkin 
612bd26dcd1STim J. Robbins 	bufsize = bufsize - (bufend - rslt) + 1;
6139d430a59SAlexey Zelkin 	memmove(rslt, bufend, bufsize);
6149d430a59SAlexey Zelkin 	free(avalue);
615a5aecc77SMike Barcroft 	return (rslt);
6169d430a59SAlexey Zelkin }
617