1 /* Print size value using units for orders of magnitude.
2    Copyright (C) 1997-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <ctype.h>
20 #include <ieee754.h>
21 #include <math.h>
22 #include <printf.h>
23 #include <libioP.h>
24 
25 #define PUT(f, s, n) _IO_sputn (f, s, n)
26 #define PAD(f, c, n) (wide ? _IO_wpadn (f, c, n) : _IO_padn (f, c, n))
27 #undef putc
28 #define putc(c, f) (wide \
29 		    ? (int)_IO_putwc_unlocked (c, f) : _IO_putc_unlocked (c, f))
30 
31 
32 /* Macros for doing the actual output.  */
33 
34 #define outchar(ch)							      \
35   do									      \
36     {									      \
37       const int outc = (ch);						      \
38       if (putc (outc, fp) == EOF)					      \
39 	return -1;							      \
40       ++done;								      \
41     } while (0)
42 
43 #define PRINT(ptr, wptr, len)						      \
44   do									      \
45     {									      \
46       size_t outlen = (len);						      \
47       if (len > 20)							      \
48 	{								      \
49 	  if (PUT (fp, wide ? (const char *) wptr : ptr, outlen) != outlen)   \
50 	    return -1;							      \
51 	  ptr += outlen;						      \
52 	  done += outlen;						      \
53 	}								      \
54       else								      \
55 	{								      \
56 	  if (wide)							      \
57 	    while (outlen-- > 0)					      \
58 	      outchar (*wptr++);					      \
59 	  else								      \
60 	    while (outlen-- > 0)					      \
61 	      outchar (*ptr++);						      \
62 	}								      \
63     } while (0)
64 
65 #define PADN(ch, len)							      \
66   do									      \
67     {									      \
68       if (PAD (fp, ch, len) != len)					      \
69 	return -1;							      \
70       done += len;							      \
71     }									      \
72   while (0)
73 
74 /* Prototype for helper functions.  */
75 extern int __printf_fp (FILE *fp, const struct printf_info *info,
76 			const void *const *args);
77 
78 
79 int
__printf_size(FILE * fp,const struct printf_info * info,const void * const * args)80 __printf_size (FILE *fp, const struct printf_info *info,
81 	       const void *const *args)
82 {
83   /* Units for the both formats.  */
84 #define BINARY_UNITS	" kmgtpezy"
85 #define DECIMAL_UNITS	" KMGTPEZY"
86   static const char units[2][sizeof (BINARY_UNITS)] =
87   {
88     BINARY_UNITS,	/* For binary format.  */
89     DECIMAL_UNITS	/* For decimal format.  */
90   };
91   const char *tag = units[isupper (info->spec) != 0];
92   int divisor = isupper (info->spec) ? 1000 : 1024;
93 
94   /* The floating-point value to output.  */
95   union
96     {
97       union ieee754_double dbl;
98       long double ldbl;
99 #if __HAVE_DISTINCT_FLOAT128
100       _Float128 f128;
101 #endif
102     }
103   fpnum;
104   const void *ptr = &fpnum;
105 
106   int is_neg = 0;
107 
108   /* "NaN" or "Inf" for the special cases.  */
109   const char *special = NULL;
110   const wchar_t *wspecial = NULL;
111 
112   struct printf_info fp_info;
113   int done = 0;
114   int wide = info->wide;
115 
116 #define PRINTF_SIZE_FETCH(FLOAT, VAR)					\
117   {									\
118     (VAR) = *(const FLOAT *) args[0];					\
119 									\
120     /* Check for special values: not a number or infinity.  */		\
121     if (isnan (VAR))							\
122       {									\
123 	special = "nan";						\
124 	wspecial = L"nan";						\
125 	/* is_neg = 0; Already zero */					\
126       }									\
127     else if (isinf (VAR))						\
128       {									\
129 	is_neg = signbit (VAR);						\
130 	special = "inf";						\
131 	wspecial = L"inf";						\
132       }									\
133     else								\
134       while ((VAR) >= divisor && tag[1] != '\0')			\
135 	{								\
136 	  (VAR) /= divisor;						\
137 	  ++tag;							\
138 	}								\
139   }
140 
141   /* Fetch the argument value.	*/
142 #if __HAVE_DISTINCT_FLOAT128
143   if (info->is_binary128)
144     PRINTF_SIZE_FETCH (_Float128, fpnum.f128)
145   else
146 #endif
147 #ifndef __NO_LONG_DOUBLE_MATH
148   if (info->is_long_double && sizeof (long double) > sizeof (double))
149     PRINTF_SIZE_FETCH (long double, fpnum.ldbl)
150   else
151 #endif
152     PRINTF_SIZE_FETCH (double, fpnum.dbl.d)
153 
154 #undef PRINTF_SIZE_FETCH
155 
156   if (special)
157     {
158       int width = info->prec > info->width ? info->prec : info->width;
159 
160       if (is_neg || info->showsign || info->space)
161 	--width;
162       width -= 3;
163 
164       if (!info->left && width > 0)
165 	PADN (' ', width);
166 
167       if (is_neg)
168 	outchar ('-');
169       else if (info->showsign)
170 	outchar ('+');
171       else if (info->space)
172 	outchar (' ');
173 
174       PRINT (special, wspecial, 3);
175 
176       if (info->left && width > 0)
177 	PADN (' ', width);
178 
179       return done;
180     }
181 
182   /* Prepare to print the number.  We want to use `__printf_fp' so we
183      have to prepare a `printf_info' structure.  */
184   fp_info = *info;
185   fp_info.spec = 'f';
186   fp_info.prec = info->prec < 0 ? 3 : info->prec;
187   fp_info.wide = wide;
188 
189   if (fp_info.left && fp_info.pad == L' ')
190     {
191       /* We must do the padding ourself since the unit character must
192 	 be placed before the padding spaces.  */
193       fp_info.width = 0;
194 
195       done = __printf_fp (fp, &fp_info, &ptr);
196       if (done > 0)
197 	{
198 	  outchar (*tag);
199 	  if (info->width > done)
200 	    PADN (' ', info->width - done);
201 	}
202     }
203   else
204     {
205       /* We can let __printf_fp do all the printing and just add our
206 	 unit character afterwards.  */
207       fp_info.width = info->width - 1;
208 
209       done = __printf_fp (fp, &fp_info, &ptr);
210       if (done > 0)
211 	outchar (*tag);
212     }
213 
214   return done;
215 }
216 ldbl_strong_alias (__printf_size, printf_size);
217 
218 /* This is the function used by `vfprintf' to determine number and
219    type of the arguments.  */
220 int
printf_size_info(const struct printf_info * info,size_t n,int * argtypes)221 printf_size_info (const struct printf_info *info, size_t n, int *argtypes)
222 {
223   /* We need only one double or long double argument.  */
224   if (n >= 1)
225     argtypes[0] = PA_DOUBLE | (info->is_long_double ? PA_FLAG_LONG_DOUBLE : 0);
226 
227   return 1;
228 }
229