(read_utmp): Use the new definitions.
[gnulib.git] / lib / human.c
1 /* human.c -- print human readable file size
2    Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Originally contributed by lm@sgi.com;
19    --si, output block size selection, and large file support
20    added by eggert@twinsun.com.  */
21
22 #if HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <sys/types.h>
27 #include <stdio.h>
28
29 #if HAVE_LIMITS_H
30 # include <limits.h>
31 #endif
32
33 #ifndef CHAR_BIT
34 # define CHAR_BIT 8
35 #endif
36 #if HAVE_STDLIB_H
37 # include <stdlib.h>
38 #endif
39
40 #ifndef HAVE_DECL_GETENV
41 char *getenv ();
42 #endif
43
44 #if ENABLE_NLS
45 # include <libintl.h>
46 # define _(Text) gettext (Text)
47 #else
48 # define _(Text) Text
49 #endif
50
51 #include <argmatch.h>
52 #include <error.h>
53 #include <xstrtol.h>
54
55 #include "human.h"
56
57 static const char suffixes[] =
58 {
59   0,    /* not used */
60   'k',  /* kilo */
61   'M',  /* Mega */
62   'G',  /* Giga */
63   'T',  /* Tera */
64   'P',  /* Peta */
65   'E',  /* Exa */
66   'Z',  /* Zetta */
67   'Y'   /* Yotta */
68 };
69
70 /* Convert N to a human readable format in BUF.
71
72    N is expressed in units of FROM_BLOCK_SIZE.  FROM_BLOCK_SIZE must
73    be positive.
74
75    If OUTPUT_BLOCK_SIZE is positive, use units of OUTPUT_BLOCK_SIZE in
76    the output number.  OUTPUT_BLOCK_SIZE must be a multiple of
77    FROM_BLOCK_SIZE or vice versa.
78
79    If OUTPUT_BLOCK_SIZE is negative, use a format like "127k" if
80    possible, using powers of -OUTPUT_BLOCK_SIZE; otherwise, use
81    ordinary decimal format.  Normally -OUTPUT_BLOCK_SIZE is either
82    1000 or 1024; it must be at least 2.  Most people visually process
83    strings of 3-4 digits effectively, but longer strings of digits are
84    more prone to misinterpretation.  Hence, converting to an
85    abbreviated form usually improves readability.  Use a suffix
86    indicating which power is being used.  For example, assuming
87    -OUTPUT_BLOCK_SIZE is 1024, 8500 would be converted to 8.3k,
88    133456345 to 127M, 56990456345 to 53G, and so on.  Numbers smaller
89    than -OUTPUT_BLOCK_SIZE aren't modified.  */
90
91 char *
92 human_readable (uintmax_t n, char *buf,
93                 int from_block_size, int output_block_size)
94 {
95   uintmax_t amt;
96   int base;
97   int to_block_size;
98   int tenths;
99   int power;
100   char *p;
101
102   /* 0 means adjusted N == AMT.TENTHS;
103      1 means AMT.TENTHS < adjusted N < AMT.TENTHS + 0.05;
104      2 means adjusted N == AMT.TENTHS + 0.05;
105      3 means AMT.TENTHS + 0.05 < adjusted N < AMT.TENTHS + 0.1.  */
106   int rounding;
107
108   if (output_block_size < 0)
109     {
110       base = -output_block_size;
111       to_block_size = 1;
112     }
113   else
114     {
115       base = 0;
116       to_block_size = output_block_size;
117     }
118
119   p = buf + LONGEST_HUMAN_READABLE;
120   *p = '\0';
121
122 #ifdef lint
123   /* Suppress `used before initialized' warning.  */
124   power = 0;
125 #endif
126
127   /* Adjust AMT out of FROM_BLOCK_SIZE units and into TO_BLOCK_SIZE units.  */
128
129   if (to_block_size <= from_block_size)
130     {
131       int multiplier = from_block_size / to_block_size;
132       amt = n * multiplier;
133       tenths = rounding = 0;
134
135       if (amt / multiplier != n)
136         {
137           /* Overflow occurred during multiplication.  We should use
138              multiple precision arithmetic here, but we'll be lazy and
139              resort to floating point.  This can yield answers that
140              are slightly off.  In practice it is quite rare to
141              overflow uintmax_t, so this is good enough for now.  */
142
143           double damt = n * (double) multiplier;
144
145           if (! base)
146             sprintf (buf, "%.0f", damt);
147           else
148             {
149               double e = 1;
150               power = 0;
151
152               do
153                 {
154                   e *= base;
155                   power++;
156                 }
157               while (e * base <= damt && power < sizeof suffixes - 1);
158
159               damt /= e;
160
161               sprintf (buf, "%.1f%c", damt, suffixes[power]);
162               if (4 < strlen (buf))
163                 sprintf (buf, "%.0f%c", damt, suffixes[power]);
164             }
165
166           return buf;
167         }
168     }
169   else
170     {
171       int divisor = to_block_size / from_block_size;
172       int r10 = (n % divisor) * 10;
173       int r2 = (r10 % divisor) * 2;
174       amt = n / divisor;
175       tenths = r10 / divisor;
176       rounding = r2 < divisor ? 0 < r2 : 2 + (divisor < r2);
177     }
178
179
180   /* Use power of BASE notation if adjusted AMT is large enough.  */
181
182   if (base && base <= amt)
183     {
184       power = 0;
185
186       do
187         {
188           int r10 = (amt % base) * 10 + tenths;
189           int r2 = (r10 % base) * 2 + (rounding >> 1);
190           amt /= base;
191           tenths = r10 / base;
192           rounding = (r2 < base
193                       ? 0 < r2 + rounding
194                       : 2 + (base < r2 + rounding));
195           power++;
196         }
197       while (base <= amt && power < sizeof suffixes - 1);
198
199       *--p = suffixes[power];
200
201       if (amt < 10)
202         {
203           tenths += 2 < rounding + (tenths & 1);
204
205           if (tenths == 10)
206             {
207               amt++;
208               tenths = 0;
209             }
210
211           if (amt < 10)
212             {
213               *--p = '0' + tenths;
214               *--p = '.';
215               tenths = 0;
216             }
217         }
218     }
219
220   if (5 < tenths + (2 < rounding + (amt & 1)))
221     {
222       amt++;
223
224       if (amt == base && power < sizeof suffixes - 1)
225         {
226           *p = suffixes[power + 1];
227           *--p = '0';
228           *--p = '.';
229           amt = 1;
230         }
231     }
232
233   do
234     *--p = '0' + (int) (amt % 10);
235   while ((amt /= 10) != 0);
236
237   return p;
238 }
239
240
241 /* The default block size used for output.  This number may change in
242    the future as disks get larger.  */
243 #ifndef DEFAULT_BLOCK_SIZE
244 # define DEFAULT_BLOCK_SIZE 1024
245 #endif
246
247 static char const *const block_size_args[] = { "human-readable", "si", 0 };
248 static int const block_size_types[] = { -1024, -1000 };
249
250 static strtol_error
251 humblock (char const *spec, int *block_size)
252 {
253   int i;
254
255   if (! spec && ! (spec = getenv ("BLOCK_SIZE")))
256     *block_size = getenv ("POSIXLY_CORRECT") ? 512 : DEFAULT_BLOCK_SIZE;
257   else if (0 <= (i = ARGMATCH (spec, block_size_args, block_size_types)))
258     *block_size = block_size_types[i];
259   else
260     {
261       char *ptr;
262       unsigned long val;
263       strtol_error e = xstrtoul (spec, &ptr, 0, &val, "eEgGkKmMpPtTyYzZ0");
264       if (e != LONGINT_OK)
265         return e;
266       if (*ptr)
267         return LONGINT_INVALID_SUFFIX_CHAR;
268       if ((int) val < 0 || val != (int) val)
269         return LONGINT_OVERFLOW;
270       *block_size = (int) val;
271     }
272
273   return LONGINT_OK;
274 }
275
276 void
277 human_block_size (char const *spec, int report_errors, int *block_size)
278 {
279   strtol_error e = humblock (spec, block_size);
280   if (e != LONGINT_OK && report_errors)
281     STRTOL_FATAL_ERROR (spec, _("block size"), e);
282 }