X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fhuman.c;h=a9ccf38c48312e5270889101283f65c35672afb5;hb=4c692979e6e60bc1fdb6a6ef5ae3f8b2e9600fc3;hp=ddd4b4aef6920855a7881bd70b818f1899f98788;hpb=42313b425853288d438927be086f393e2d41acdc;p=gnulib.git diff --git a/lib/human.c b/lib/human.c index ddd4b4aef..a9ccf38c4 100644 --- a/lib/human.c +++ b/lib/human.c @@ -1,5 +1,7 @@ /* human.c -- print human readable file size - Copyright (C) 1996, 1997 Free Software Foundation, Inc. + + Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001 Free Software + Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,12 +18,11 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Originally contributed by lm@sgi.com; - --si and large file support added by eggert@twinsun.com. */ - -#include + --si, output block size selection, and large file support + added by eggert@twinsun.com. */ -#if HAVE_INTTYPES_H -# include +#if HAVE_CONFIG_H +# include #endif #include @@ -31,47 +32,129 @@ # include #endif +#if HAVE_STRING_H +# include +#else +# include +#endif + #ifndef CHAR_BIT # define CHAR_BIT 8 #endif +#if HAVE_STDLIB_H +# include +#endif + +#ifndef HAVE_DECL_GETENV +"this configure-time declaration test was not run" +#endif +#if !HAVE_DECL_GETENV +char *getenv (); +#endif + +#if ENABLE_NLS +# include +# define _(Text) gettext (Text) +#else +# define _(Text) Text +#endif + +#include +#include +#include #include "human.h" static const char suffixes[] = { 0, /* not used */ - 'k', /* kilo */ - 'M', /* Mega */ - 'G', /* Giga */ - 'T', /* Tera */ - 'P', /* Peta */ - 'E', /* Exa */ - 'Z', /* Zetta */ - 'Y' /* Yotta */ + 'K', /* kibi ('k' for kilo is a special case) */ + 'M', /* mega or mebi */ + 'G', /* giga or gibi */ + 'T', /* tera or tebi */ + 'P', /* peta or pebi */ + 'E', /* exa or exbi */ + 'Z', /* zetta or 2**70 */ + 'Y' /* yotta or 2**80 */ }; +/* Generate into P[-1] (and possibly P[-2]) the proper suffix for + POWER and BASE. Return the address of the generated suffix. */ +static char * +generate_suffix_backwards (char *p, int power, int base) +{ + char letter = suffixes[power]; + + if (base == 1000) + { + *--p = 'B'; + if (power == 1) + letter = 'k'; + } + + *--p = letter; + return p; +} + +/* If INEXACT_STYLE is not human_round_to_even, and if easily + possible, adjust VALUE according to the style. */ +static double +adjust_value (enum human_inexact_style inexact_style, double value) +{ + /* Do not use the floor or ceil functions, as that would mean + linking with the standard math library, which is a porting pain. + So leave the value alone if it is too large to easily round. */ + if (inexact_style != human_round_to_even && value < (uintmax_t) -1) + { + uintmax_t u = value; + value = u + (inexact_style == human_ceiling && u != value); + } + + return value; +} + +/* Like human_readable_inexact, except always round to even. */ +char * +human_readable (uintmax_t n, char *buf, + int from_block_size, int output_block_size) +{ + return human_readable_inexact (n, buf, from_block_size, output_block_size, + human_round_to_even); +} + /* Convert N to a human readable format in BUF. - N is expressed in units of FROM_UNITS; use units of TO_UNITS in the - output number. FROM_UNITS and TO_UNITS must be positive, and one must - be a multiple of the other. - - If BASE is nonzero, use a format like "127k" if possible, - using powers of BASE; otherwise, use ordinary decimal format. - Normally BASE is either 1000 or 1024; it must be at least 2. - Most people visually process strings of 3-4 digits effectively, - but longer strings of digits are more prone to misinterpretation. - Hence, converting to an abbreviated form usually improves readability. - Use a suffix indicating which power is being used. - For example, assuming BASE is 1024, 8500 would be converted to 8.3k, + N is expressed in units of FROM_BLOCK_SIZE. FROM_BLOCK_SIZE must + be nonnegative. + + OUTPUT_BLOCK_SIZE must be nonzero. If it is positive, use units of + OUTPUT_BLOCK_SIZE in the output number. + + Use INEXACT_STYLE to determine whether to take the ceiling or floor + of any result that cannot be expressed exactly. + + If OUTPUT_BLOCK_SIZE is negative, use a format like "127K" if + possible, using powers of -OUTPUT_BLOCK_SIZE; otherwise, use + ordinary decimal format. Normally -OUTPUT_BLOCK_SIZE is either + 1000 or 1024; it must be at least 2. Most people visually process + strings of 3-4 digits effectively, but longer strings of digits are + more prone to misinterpretation. Hence, converting to an + abbreviated form usually improves readability. Use a suffix + indicating which power is being used. For example, assuming + -OUTPUT_BLOCK_SIZE is 1024, 8500 would be converted to 8.3K, 133456345 to 127M, 56990456345 to 53G, and so on. Numbers smaller - than BASE aren't modified. */ + than -OUTPUT_BLOCK_SIZE aren't modified. If -OUTPUT_BLOCK_SIZE is + 1024, append a "B" after any size letter. */ char * -human_readable (uintmax_t n, char *buf, int from_units, int to_units, int base) +human_readable_inexact (uintmax_t n, char *buf, + int from_block_size, int output_block_size, + enum human_inexact_style inexact_style) { uintmax_t amt; - int tenths; + int base; + int to_block_size; + int tenths = 0; int power; char *p; @@ -79,7 +162,18 @@ human_readable (uintmax_t n, char *buf, int from_units, int to_units, int base) 1 means AMT.TENTHS < adjusted N < AMT.TENTHS + 0.05; 2 means adjusted N == AMT.TENTHS + 0.05; 3 means AMT.TENTHS + 0.05 < adjusted N < AMT.TENTHS + 0.1. */ - int rounding; + int rounding = 0; + + if (output_block_size < 0) + { + base = -output_block_size; + to_block_size = 1; + } + else + { + base = 0; + to_block_size = output_block_size; + } p = buf + LONGEST_HUMAN_READABLE; *p = '\0'; @@ -89,58 +183,63 @@ human_readable (uintmax_t n, char *buf, int from_units, int to_units, int base) power = 0; #endif - /* Adjust AMT out of FROM_UNITS units and into TO_UNITS units. */ - - if (to_units <= from_units) - { - int multiplier = from_units / to_units; - amt = n * multiplier; - tenths = rounding = 0; - - if (amt / multiplier != n) - { - /* Overflow occurred during multiplication. We should use - multiple precision arithmetic here, but we'll be lazy and - resort to floating point. This can yield answers that - are slightly off. In practice it is quite rare to - overflow uintmax_t, so this is good enough for now. */ - - double damt = n * (double) multiplier; - - if (! base) - sprintf (buf, "%.0f", damt); - else - { - double e = 1; - power = 0; - - do - { - e *= base; - power++; - } - while (e * base <= amt && power < sizeof suffixes - 1); - - damt /= e; - - sprintf (buf, "%.1f%c", damt, suffixes[power]); - if (4 < strlen (buf)) - sprintf (buf, "%.0f%c", damt, suffixes[power]); - } - - return buf; - } - } - else - { - int divisor = to_units / from_units; - int r10 = (n % divisor) * 10; - int r2 = (r10 % divisor) * 2; - amt = n / divisor; - tenths = r10 / divisor; - rounding = r2 < divisor ? 0 < r2 : 2 + (divisor < r2); - } - + /* Adjust AMT out of FROM_BLOCK_SIZE units and into TO_BLOCK_SIZE units. */ + + { + int multiplier; + int divisor; + int r2; + int r10; + if (to_block_size <= from_block_size + ? (from_block_size % to_block_size != 0 + || (multiplier = from_block_size / to_block_size, + (amt = n * multiplier) / multiplier != n)) + : (from_block_size == 0 + || to_block_size % from_block_size != 0 + || (divisor = to_block_size / from_block_size, + r10 = (n % divisor) * 10, + r2 = (r10 % divisor) * 2, + amt = n / divisor, + tenths = r10 / divisor, + rounding = r2 < divisor ? 0 < r2 : 2 + (divisor < r2), + 0))) + { + /* Either the result cannot be computed easily using uintmax_t, + or from_block_size is zero. Fall back on floating point. + FIXME: This can yield answers that are slightly off. */ + + double damt = n * (from_block_size / (double) to_block_size); + + if (! base) + sprintf (buf, "%.0f", adjust_value (inexact_style, damt)); + else + { + char suffix[3]; + char const *psuffix; + double e = 1; + power = 0; + + do + { + e *= base; + power++; + } + while (e * base <= damt && power < sizeof suffixes - 1); + + damt /= e; + + suffix[2] = '\0'; + psuffix = generate_suffix_backwards (suffix + 2, power, base); + sprintf (buf, "%.1f%s", + adjust_value (inexact_style, damt), psuffix); + if (4 + (base == 1000) < strlen (buf)) + sprintf (buf, "%.0f%s", + adjust_value (inexact_style, damt * 10) / 10, psuffix); + } + + return buf; + } + } /* Use power of BASE notation if adjusted AMT is large enough. */ @@ -161,28 +260,37 @@ human_readable (uintmax_t n, char *buf, int from_units, int to_units, int base) } while (base <= amt && power < sizeof suffixes - 1); - *--p = suffixes[power]; + p = generate_suffix_backwards (p, power, base); if (amt < 10) { - tenths += 2 < rounding + (tenths & 1); - - if (tenths == 10) + if (2 * (1 - (int) inexact_style) + < rounding + (tenths & (inexact_style == human_round_to_even))) { - amt++; - tenths = 0; + tenths++; + rounding = 0; + + if (tenths == 10) + { + amt++; + tenths = 0; + } } if (amt < 10) { *--p = '0' + tenths; *--p = '.'; - tenths = 0; + tenths = rounding = 0; } } } - if (5 < tenths + (2 < rounding + (amt & 1))) + if (inexact_style == human_ceiling + ? 0 < tenths + rounding + : inexact_style == human_round_to_even + ? 5 < tenths + (2 < rounding + (amt & 1)) + : /* inexact_style == human_floor */ 0) { amt++; @@ -201,3 +309,58 @@ human_readable (uintmax_t n, char *buf, int from_units, int to_units, int base) return p; } + + +/* The default block size used for output. This number may change in + the future as disks get larger. */ +#ifndef DEFAULT_BLOCK_SIZE +# define DEFAULT_BLOCK_SIZE 1024 +#endif + +static char const *const block_size_args[] = { "human-readable", "si", 0 }; +static int const block_size_types[] = { -1024, -1000 }; + +static int +default_block_size (void) +{ + return getenv ("POSIXLY_CORRECT") ? 512 : DEFAULT_BLOCK_SIZE; +} + +static strtol_error +humblock (char const *spec, int *block_size) +{ + int i; + + if (! spec && ! (spec = getenv ("BLOCK_SIZE"))) + *block_size = default_block_size (); + else if (0 <= (i = ARGMATCH (spec, block_size_args, block_size_types))) + *block_size = block_size_types[i]; + else + { + char *ptr; + unsigned long val; + strtol_error e = xstrtoul (spec, &ptr, 0, &val, "eEgGkKmMpPtTyYzZ0"); + if (e != LONGINT_OK) + return e; + if (*ptr) + return LONGINT_INVALID_SUFFIX_CHAR; + if ((int) val < 0 || val != (int) val) + return LONGINT_OVERFLOW; + *block_size = (int) val; + } + + return LONGINT_OK; +} + +void +human_block_size (char const *spec, int report_errors, int *block_size) +{ + strtol_error e = humblock (spec, block_size); + if (*block_size == 0) + { + *block_size = default_block_size (); + e = LONGINT_INVALID; + } + if (e != LONGINT_OK && report_errors) + STRTOL_FATAL_ERROR (spec, _("block size"), e); +}