| /* GCC Quad-Precision Math Library | |
| Copyright (C) 2011 Free Software Foundation, Inc. | |
| Written by Jakub Jelinek <jakub@redhat.com> | |
| This file is part of the libquadmath library. | |
| Libquadmath is free software; you can redistribute it and/or | |
| modify it under the terms of the GNU Library General Public | |
| License as published by the Free Software Foundation; either | |
| version 2 of the License, or (at your option) any later version. | |
| Libquadmath is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| Library General Public License for more details. | |
| You should have received a copy of the GNU Library General Public | |
| License along with libquadmath; see the file COPYING.LIB. If | |
| not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, | |
| Boston, MA 02110-1301, USA. */ | |
| #include <config.h> | |
| #include <stdarg.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include "quadmath-printf.h" | |
| /* Read a simple integer from a string and update the string pointer. | |
| It is assumed that the first character is a digit. */ | |
| static unsigned int | |
| read_int (const char **pstr) | |
| { | |
| unsigned int retval = (unsigned char) **pstr - '0'; | |
| while (isdigit ((unsigned char) *++(*pstr))) | |
| { | |
| retval *= 10; | |
| retval += (unsigned char) **pstr - '0'; | |
| } | |
| return retval; | |
| } | |
| #define PADSIZE 16 | |
| static char const blanks[PADSIZE] = | |
| {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; | |
| static char const zeroes[PADSIZE] = | |
| {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; | |
| static wchar_t const wblanks[PADSIZE] = | |
| { | |
| L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), | |
| L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' '), L_(' ') | |
| }; | |
| static wchar_t const wzeroes[PADSIZE] = | |
| { | |
| L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), | |
| L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0'), L_('0') | |
| }; | |
| attribute_hidden size_t | |
| __quadmath_do_pad (struct __quadmath_printf_file *fp, int wide, int c, | |
| size_t n) | |
| { | |
| ssize_t i; | |
| char padbuf[PADSIZE]; | |
| wchar_t wpadbuf[PADSIZE]; | |
| const char *padstr; | |
| size_t w, written = 0; | |
| if (wide) | |
| { | |
| if (c == ' ') | |
| padstr = (const char *) wblanks; | |
| else if (c == '0') | |
| padstr = (const char *) wzeroes; | |
| else | |
| { | |
| padstr = (const char *) wpadbuf; | |
| for (i = 0; i < PADSIZE; i++) | |
| wpadbuf[i] = c; | |
| } | |
| } | |
| else | |
| { | |
| if (c == ' ') | |
| padstr = blanks; | |
| else if (c == '0') | |
| padstr = zeroes; | |
| else | |
| { | |
| padstr = (const char *) padbuf; | |
| for (i = 0; i < PADSIZE; i++) | |
| padbuf[i] = c; | |
| } | |
| } | |
| for (i = n; i >= PADSIZE; i -= PADSIZE) | |
| { | |
| w = PUT (fp, (char *) padstr, PADSIZE); | |
| written += w; | |
| if (w != PADSIZE) | |
| return written; | |
| } | |
| if (i > 0) | |
| { | |
| w = PUT (fp, (char *) padstr, i); | |
| written += w; | |
| } | |
| return written; | |
| } | |
| /* This is a stripped down version of snprintf, which just handles | |
| a single %eEfFgGaA format entry with Q modifier. % has to be | |
| the first character of the format string, no $ can be used. */ | |
| int | |
| quadmath_snprintf (char *str, size_t size, const char *format, ...) | |
| { | |
| struct printf_info info; | |
| va_list ap; | |
| __float128 fpnum, *fpnum_addr = &fpnum, **fpnum_addr2 = &fpnum_addr; | |
| struct __quadmath_printf_file qfp; | |
| if (*format++ != '%') | |
| return -1; | |
| /* Clear information structure. */ | |
| memset (&info, '\0', sizeof info); | |
| /* info.alt = 0; | |
| info.space = 0; | |
| info.left = 0; | |
| info.showsign = 0; | |
| info.group = 0; | |
| info.i18n = 0; | |
| info.extra = 0; */ | |
| info.pad = ' '; | |
| /* info.wide = 0; */ | |
| /* Check for spec modifiers. */ | |
| do | |
| { | |
| switch (*format) | |
| { | |
| case ' ': | |
| /* Output a space in place of a sign, when there is no sign. */ | |
| info.space = 1; | |
| continue; | |
| case '+': | |
| /* Always output + or - for numbers. */ | |
| info.showsign = 1; | |
| continue; | |
| case '-': | |
| /* Left-justify things. */ | |
| info.left = 1; | |
| continue; | |
| case '#': | |
| /* Use the "alternate form": | |
| Hex has 0x or 0X, FP always has a decimal point. */ | |
| info.alt = 1; | |
| continue; | |
| case '0': | |
| /* Pad with 0s. */ | |
| info.pad = '0'; | |
| continue; | |
| case '\'': | |
| /* Show grouping in numbers if the locale information | |
| indicates any. */ | |
| info.group = 1; | |
| continue; | |
| case 'I': | |
| /* Use the internationalized form of the output. Currently | |
| means to use the `outdigits' of the current locale. */ | |
| info.i18n = 1; | |
| continue; | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| while (*++format); | |
| if (info.left) | |
| info.pad = ' '; | |
| va_start (ap, format); | |
| /* Get the field width. */ | |
| /* info.width = 0; */ | |
| if (*format == '*') | |
| { | |
| /* The field width is given in an argument. | |
| A negative field width indicates left justification. */ | |
| ++format; | |
| info.width = va_arg (ap, int); | |
| } | |
| else if (isdigit (*format)) | |
| /* Constant width specification. */ | |
| info.width = read_int (&format); | |
| /* Get the precision. */ | |
| /* -1 means none given; 0 means explicit 0. */ | |
| info.prec = -1; | |
| if (*format == '.') | |
| { | |
| ++format; | |
| if (*format == '*') | |
| { | |
| /* The precision is given in an argument. */ | |
| ++format; | |
| info.prec = va_arg (ap, int); | |
| } | |
| else if (isdigit (*format)) | |
| info.prec = read_int (&format); | |
| else | |
| /* "%.?" is treated like "%.0?". */ | |
| info.prec = 0; | |
| } | |
| /* Check for type modifiers. */ | |
| /* info.is_long_double = 0; | |
| info.is_short = 0; | |
| info.is_long = 0; | |
| info.is_char = 0; | |
| info.user = 0; */ | |
| /* We require Q modifier. */ | |
| if (*format++ != 'Q') | |
| { | |
| va_end (ap); | |
| return -1; | |
| } | |
| /* Get the format specification. */ | |
| info.spec = (wchar_t) *format++; | |
| if (info.spec == L_('\0') || *format != '\0') | |
| { | |
| va_end (ap); | |
| return -1; | |
| } | |
| switch (info.spec) | |
| { | |
| case L_('e'): | |
| case L_('E'): | |
| case L_('f'): | |
| case L_('F'): | |
| case L_('g'): | |
| case L_('G'): | |
| case L_('a'): | |
| case L_('A'): | |
| break; | |
| default: | |
| va_end (ap); | |
| return -1; | |
| } | |
| fpnum = va_arg (ap, __float128); | |
| va_end (ap); | |
| qfp.fp = NULL; | |
| qfp.str = str; | |
| qfp.size = size ? size - 1 : 0; | |
| qfp.len = 0; | |
| qfp.file_p = 0; | |
| if (info.spec == L_('a') || info.spec == L_('A')) | |
| __quadmath_printf_fphex (&qfp, &info, (const void *const *)&fpnum_addr2); | |
| else | |
| __quadmath_printf_fp (&qfp, &info, (const void *const *)&fpnum_addr2); | |
| if (size) | |
| *qfp.str = '\0'; | |
| return qfp.len; | |
| } | |
| #ifdef HAVE_PRINTF_HOOKS | |
| static int pa_flt128; | |
| int mod_Q attribute_hidden; | |
| static void | |
| flt128_va (void *mem, va_list *ap) | |
| { | |
| __float128 d = va_arg (*ap, __float128); | |
| memcpy (mem, &d, sizeof (d)); | |
| } | |
| static int | |
| flt128_ais (const struct printf_info *info, size_t n __attribute__ ((unused)), | |
| int *argtype, int *size) | |
| { | |
| if (info->user & mod_Q) | |
| { | |
| argtype[0] = pa_flt128; | |
| size[0] = sizeof (__float128); | |
| return 1; | |
| } | |
| #if __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 13) | |
| /* Workaround bug in glibc printf hook handling. */ | |
| size[0] = -1; | |
| switch (info->spec) | |
| { | |
| case L_('i'): | |
| case L_('d'): | |
| case L_('u'): | |
| case L_('o'): | |
| case L_('X'): | |
| case L_('x'): | |
| #if __LONG_MAX__ != __LONG_LONG_MAX__ | |
| if (info->is_long_double) | |
| argtype[0] = PA_INT|PA_FLAG_LONG_LONG; | |
| else | |
| #endif | |
| if (info->is_long) | |
| argtype[0] = PA_INT|PA_FLAG_LONG; | |
| else if (info->is_short) | |
| argtype[0] = PA_INT|PA_FLAG_SHORT; | |
| else if (info->is_char) | |
| argtype[0] = PA_CHAR; | |
| else | |
| argtype[0] = PA_INT; | |
| return 1; | |
| case L_('e'): | |
| case L_('E'): | |
| case L_('f'): | |
| case L_('F'): | |
| case L_('g'): | |
| case L_('G'): | |
| case L_('a'): | |
| case L_('A'): | |
| if (info->is_long_double) | |
| argtype[0] = PA_DOUBLE|PA_FLAG_LONG_DOUBLE; | |
| else | |
| argtype[0] = PA_DOUBLE; | |
| return 1; | |
| case L_('c'): | |
| argtype[0] = PA_CHAR; | |
| return 1; | |
| case L_('C'): | |
| argtype[0] = PA_WCHAR; | |
| return 1; | |
| case L_('s'): | |
| argtype[0] = PA_STRING; | |
| return 1; | |
| case L_('S'): | |
| argtype[0] = PA_WSTRING; | |
| return 1; | |
| case L_('p'): | |
| argtype[0] = PA_POINTER; | |
| return 1; | |
| case L_('n'): | |
| argtype[0] = PA_INT|PA_FLAG_PTR; | |
| return 1; | |
| case L_('m'): | |
| default: | |
| /* An unknown spec will consume no args. */ | |
| return 0; | |
| } | |
| #endif | |
| return -1; | |
| } | |
| static int | |
| flt128_printf_fp (FILE *fp, const struct printf_info *info, | |
| const void *const *args) | |
| { | |
| struct __quadmath_printf_file qpf | |
| = { .fp = fp, .str = NULL, .size = 0, .len = 0, .file_p = 1 }; | |
| if ((info->user & mod_Q) == 0) | |
| return -2; | |
| return __quadmath_printf_fp (&qpf, info, args); | |
| } | |
| static int | |
| flt128_printf_fphex (FILE *fp, const struct printf_info *info, | |
| const void *const *args) | |
| { | |
| struct __quadmath_printf_file qpf | |
| = { .fp = fp, .str = NULL, .size = 0, .len = 0, .file_p = 1 }; | |
| if ((info->user & mod_Q) == 0) | |
| return -2; | |
| return __quadmath_printf_fphex (&qpf, info, args); | |
| } | |
| __attribute__((constructor)) static void | |
| register_printf_flt128 (void) | |
| { | |
| pa_flt128 = register_printf_type (flt128_va); | |
| if (pa_flt128 == -1) | |
| return; | |
| mod_Q = register_printf_modifier (L_("Q")); | |
| if (mod_Q == -1) | |
| return; | |
| register_printf_specifier ('f', flt128_printf_fp, flt128_ais); | |
| register_printf_specifier ('F', flt128_printf_fp, flt128_ais); | |
| register_printf_specifier ('e', flt128_printf_fp, flt128_ais); | |
| register_printf_specifier ('E', flt128_printf_fp, flt128_ais); | |
| register_printf_specifier ('g', flt128_printf_fp, flt128_ais); | |
| register_printf_specifier ('G', flt128_printf_fp, flt128_ais); | |
| register_printf_specifier ('a', flt128_printf_fphex, flt128_ais); | |
| register_printf_specifier ('A', flt128_printf_fphex, flt128_ais); | |
| } | |
| __attribute__((destructor)) static void | |
| unregister_printf_flt128 (void) | |
| { | |
| /* No way to unregister printf type and modifier currently, | |
| and only one printf specifier can be registered right now. */ | |
| if (pa_flt128 == -1 || mod_Q == -1) | |
| return; | |
| register_printf_specifier ('f', NULL, NULL); | |
| register_printf_specifier ('F', NULL, NULL); | |
| register_printf_specifier ('e', NULL, NULL); | |
| register_printf_specifier ('E', NULL, NULL); | |
| register_printf_specifier ('g', NULL, NULL); | |
| register_printf_specifier ('G', NULL, NULL); | |
| register_printf_specifier ('a', NULL, NULL); | |
| register_printf_specifier ('A', NULL, NULL); | |
| } | |
| #endif |