__aeabi_ldivmod: fix sign logic
[minix.git] / lib / libc / stdlib / strfmon.c
blobe92053cb84874cc7bbc9ff695474c12922a805a8
1 /* $NetBSD: strfmon.c,v 1.10 2012/03/21 14:19:15 christos Exp $ */
3 /*-
4 * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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
26 * SUCH DAMAGE.
30 #include <sys/cdefs.h>
31 #if defined(LIBC_SCCS) && !defined(lint)
32 #if 0
33 __FBSDID("$FreeBSD: src/lib/libc/stdlib/strfmon.c,v 1.14 2003/03/20 08:18:55 ache Exp $");
34 #else
35 __RCSID("$NetBSD: strfmon.c,v 1.10 2012/03/21 14:19:15 christos Exp $");
36 #endif
37 #endif /* LIBC_SCCS and not lint */
39 #if defined(__NetBSD__) || defined(__minix)
40 #include "namespace.h"
41 #include <monetary.h>
42 #endif
44 #include <sys/types.h>
45 #include <assert.h>
46 #include <ctype.h>
47 #include <errno.h>
48 #include <limits.h>
49 #include <locale.h>
50 #include <stdarg.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <stddef.h>
56 /* internal flags */
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 ? */
66 #ifndef NBCHAR_MAX
67 #define NBCHAR_MAX ((unsigned char)CHAR_MAX)
68 #endif
70 /* internal macros */
71 #define PRINT(CH) do { \
72 if (dst >= s + maxsize) \
73 goto e2big_error; \
74 *dst++ = CH; \
75 } while (/* CONSTCOND */ 0)
77 #define PRINTS(STR) do { \
78 const char *tmps = STR; \
79 while (*tmps != '\0') \
80 PRINT(*tmps++); \
81 } while (/* CONSTCOND */ 0)
83 #define GET_NUMBER(VAR) do { \
84 VAR = 0; \
85 while (isdigit((unsigned char)*fmt)) { \
86 VAR *= 10; \
87 VAR += *fmt - '0'; \
88 if (VAR > 0x00ffffff) \
89 goto e2big_error; \
90 fmt++; \
91 } \
92 } while (/* CONSTCOND */ 0)
94 #define GRPCPY(howmany) do { \
95 int i = howmany; \
96 while (i-- > 0) { \
97 avalue_size--; \
98 *--bufend = *(avalue+avalue_size+padded); \
99 } \
100 } while (/* CONSTCOND */ 0)
102 #define GRPSEP do { \
103 *--bufend = thousands_sep; \
104 groups++; \
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);
111 ssize_t
112 strfmon(char * __restrict s, size_t maxsize, const char * __restrict format,
113 ...)
115 va_list ap;
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 */
131 sep_by_space,
132 sign_posn,
133 *currency_symbol;
134 const char *signstr;
136 char *tmpptr; /* temporary vars */
137 int sverrno;
139 va_start(ap, format);
141 lc = localeconv();
142 dst = s;
143 fmt = format;
144 asciivalue = NULL;
145 currency_symbol = NULL;
146 pad_size = 0;
148 while (*fmt) {
149 /* pass nonformating characters AS IS */
150 if (*fmt != '%')
151 goto literal;
153 /* '%' found ! */
155 /* "%%" mean just '%' */
156 if (*(fmt+1) == '%') {
157 fmt++;
158 literal:
159 PRINT(*fmt++);
160 continue;
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 */
171 /* Flags */
172 while (/* CONSTCOND */ 1) {
173 switch (*++fmt) {
174 case '=': /* fill character */
175 pad_char = *++fmt;
176 if (pad_char == '\0')
177 goto format_error;
178 continue;
179 case '^': /* not group currency */
180 flags &= ~(NEED_GROUPING);
181 continue;
182 case '+': /* use locale defined signs */
183 if (flags & SIGN_POSN_USED)
184 goto format_error;
185 flags |= (SIGN_POSN_USED|LOCALE_POSN);
186 continue;
187 case '(': /* enclose negatives with () */
188 if (flags & SIGN_POSN_USED)
189 goto format_error;
190 flags |= (SIGN_POSN_USED|PARENTH_POSN);
191 continue;
192 case '!': /* suppress currency symbol */
193 flags |= SUPRESS_CURR_SYMBOL;
194 continue;
195 case '-': /* alignment (left) */
196 flags |= LEFT_JUSTIFY;
197 continue;
198 default:
199 break;
201 break;
204 /* field Width */
205 if (isdigit((unsigned char)*fmt)) {
206 ptrdiff_t d = dst - s;
207 GET_NUMBER(width);
208 /* Do we have enough space to put number with
209 * required width ?
212 if ((size_t)(d + width) >= maxsize)
213 goto e2big_error;
216 /* Left precision */
217 if (*fmt == '#') {
218 if (!isdigit((unsigned char)*++fmt))
219 goto format_error;
220 GET_NUMBER(left_prec);
223 /* Right precision */
224 if (*fmt == '.') {
225 if (!isdigit((unsigned char)*++fmt))
226 goto format_error;
227 GET_NUMBER(right_prec);
230 /* Conversion Characters */
231 switch (*fmt++) {
232 case 'i': /* use internaltion currency format */
233 flags |= USE_INTL_CURRENCY;
234 break;
235 case 'n': /* use national currency format */
236 flags &= ~(USE_INTL_CURRENCY);
237 break;
238 default: /* required character is missing or
239 premature EOS */
240 goto format_error;
243 if (currency_symbol)
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);
249 } else
250 currency_symbol = strdup(lc->currency_symbol);
252 if (currency_symbol == NULL)
253 goto end_error; /* ENOMEM. */
255 /* value itself */
256 value = va_arg(ap, double);
258 /* detect sign */
259 if (value < 0) {
260 flags |= IS_NEGATIVE;
261 value = -value;
264 /* fill left_prec with amount of padding chars */
265 if (left_prec >= 0) {
266 pad_size = __calc_left_pad((flags ^ IS_NEGATIVE),
267 currency_symbol) -
268 __calc_left_pad(flags, currency_symbol);
269 if (pad_size < 0)
270 pad_size = 0;
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
296 * non-negative value
297 * = 1 - space separates the symbol from the value
298 * = 2 - space separates the symbol and the sign string,
299 * if adjacent.
301 * p_sign_posn & n_sign_posn
303 * = 0 - parentheses enclose the quantity and the
304 * $currency_symbol
305 * = 1 - the sign string precedes the quantity and the
306 * $currency_symbol
307 * = 2 - the sign string succeeds the quantity and the
308 * $currency_symbol
309 * = 3 - the sign string precedes the $currency_symbol
310 * = 4 - the sign string succeeds the $currency_symbol
314 tmpptr = dst;
316 while (pad_size-- > 0)
317 PRINT(' ');
319 if (sign_posn == 0 && (flags & IS_NEGATIVE))
320 PRINT('(');
322 if (cs_precedes == 1) {
323 if (sign_posn == 1 || sign_posn == 3) {
324 PRINTS(signstr);
325 if (sep_by_space == 2) /* XXX: ? */
326 PRINT(' ');
329 if (!(flags & SUPRESS_CURR_SYMBOL)) {
330 PRINTS(currency_symbol);
332 if (sign_posn == 4) {
333 if (sep_by_space == 2)
334 PRINT(space_char);
335 PRINTS(signstr);
336 if (sep_by_space == 1)
337 PRINT(' ');
338 } else if (sep_by_space == 1)
339 PRINT(space_char);
341 } else if (sign_posn == 1)
342 PRINTS(signstr);
344 PRINTS(asciivalue);
346 if (cs_precedes == 0) {
347 if (sign_posn == 3) {
348 if (sep_by_space == 1)
349 PRINT(' ');
350 PRINTS(signstr);
353 if (!(flags & SUPRESS_CURR_SYMBOL)) {
354 if ((sign_posn == 3 && sep_by_space == 2)
355 || (sep_by_space == 1
356 && (sign_posn == 0
357 || sign_posn == 1
358 || sign_posn == 2
359 || sign_posn == 4)))
360 PRINT(space_char);
361 PRINTS(currency_symbol); /* XXX: len */
362 if (sign_posn == 4) {
363 if (sep_by_space == 2)
364 PRINT(' ');
365 PRINTS(signstr);
370 if (sign_posn == 2) {
371 if (sep_by_space == 2)
372 PRINT(' ');
373 PRINTS(signstr);
376 if (sign_posn == 0 && (flags & IS_NEGATIVE))
377 PRINT(')');
379 if (dst - tmpptr < width) {
380 if (flags & LEFT_JUSTIFY) {
381 while (dst - tmpptr < width)
382 PRINT(' ');
383 } else {
384 _DIAGASSERT(__type_fit(int, dst - tmpptr));
385 pad_size = dst - tmpptr;
386 memmove(tmpptr + width-pad_size, tmpptr,
387 (size_t) pad_size);
388 memset(tmpptr, ' ', (size_t) width-pad_size);
389 dst += width-pad_size;
394 PRINT('\0');
395 va_end(ap);
396 free(asciivalue);
397 free(currency_symbol);
398 return (dst - s - 1); /* return size of put data except trailing '\0' */
400 e2big_error:
401 errno = E2BIG;
402 goto end_error;
404 format_error:
405 errno = EINVAL;
407 end_error:
408 sverrno = errno;
409 if (asciivalue != NULL)
410 free(asciivalue);
411 if (currency_symbol != NULL)
412 free(currency_symbol);
413 errno = sverrno;
414 va_end(ap);
415 return (-1);
418 static void
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') ? "-"
428 : lc->negative_sign;
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') ? "-"
439 : lc->negative_sign;
440 } else {
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)
449 *cs_precedes = 1;
450 if ((unsigned char)*sep_by_space == NBCHAR_MAX)
451 *sep_by_space = 0;
452 if ((unsigned char)*sign_posn == NBCHAR_MAX)
453 *sign_posn = 0;
456 static int
457 __calc_left_pad(int flags, char *cur_symb) {
459 char cs_precedes, sep_by_space, sign_posn;
460 const char *signstr;
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)
468 left_chars++;
471 switch (sign_posn) {
472 case 1:
473 left_chars += strlen(signstr);
474 break;
475 case 3:
476 case 4:
477 if (cs_precedes != 0)
478 left_chars += strlen(signstr);
480 _DIAGASSERT(__type_fit(int, left_chars));
481 return (int)left_chars;
484 static int
485 get_groups(int size, char *grouping) {
487 int chars = 0;
489 if ((unsigned char)*grouping == NBCHAR_MAX || *grouping <= 0) /* no grouping ? */
490 return (0);
492 while (size > (int)*grouping) {
493 chars++;
494 size -= (int)*grouping++;
495 /* no more grouping ? */
496 if ((unsigned char)*grouping == NBCHAR_MAX)
497 break;
498 /* rest grouping with same value ? */
499 if (*grouping == 0) {
500 chars += (size - 1) / *(grouping - 1);
501 break;
504 return (chars);
507 /* convert double to ASCII */
508 static char *
509 __format_grouped_double(double value, int *flags,
510 int left_prec, int right_prec, int pad_char) {
512 char *rslt;
513 char *avalue;
514 int avalue_size;
516 size_t bufsize;
517 char *bufend;
519 int padded;
521 struct lconv *lc = localeconv();
522 char *grouping;
523 char decimal_point;
524 char thousands_sep;
526 int groups = 0;
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 */
537 if (left_prec == -1)
538 left_prec = 0;
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;
544 else
545 right_prec = lc->frac_digits;
547 if (right_prec == CHAR_MAX) /* POSIX locale ? */
548 right_prec = 2;
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,
556 right_prec, value);
557 if (avalue_size < 0)
558 return (NULL);
560 /* make sure that we've enough space for result string */
561 bufsize = avalue_size * 2 + 1;
562 rslt = malloc(bufsize);
563 if (rslt == NULL) {
564 free(avalue);
565 return (NULL);
567 memset(rslt, 0, bufsize);
568 bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */
570 /* skip spaces at beggining */
571 padded = 0;
572 while (avalue[padded] == ' ') {
573 padded++;
574 avalue_size--;
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 &&
589 *grouping > 0) {
590 while (avalue_size > (int)*grouping) {
591 GRPCPY(*grouping);
592 GRPSEP;
593 grouping++;
595 /* no more grouping ? */
596 if ((unsigned char)*grouping == NBCHAR_MAX)
597 break;
599 /* rest grouping with same value ? */
600 if (*grouping == 0) {
601 grouping--;
602 while (avalue_size > *grouping) {
603 GRPCPY(*grouping);
604 GRPSEP;
608 if (avalue_size != 0)
609 GRPCPY(avalue_size);
610 padded -= groups;
612 } else {
613 bufend -= avalue_size;
614 memcpy(bufend, avalue+padded, (size_t) avalue_size);
615 if (right_prec == 0)
616 padded--; /* decrease assumed $decimal_point */
619 /* do padding with pad_char */
620 if (padded > 0) {
621 bufend -= padded;
622 memset(bufend, pad_char, (size_t) padded);
625 bufsize = bufsize - (bufend - rslt) + 1;
626 memmove(rslt, bufend, bufsize);
627 free(avalue);
628 return (rslt);