mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			423 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
| /* 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
 |