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