/* human.c -- print human readable file size Copyright (C) 1996, 1997, 1998, 1999, 2000 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 the Free Software Foundation; either version 2, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Originally contributed by lm@sgi.com; --si, output block size selection, and large file support added by eggert@twinsun.com. */ #if HAVE_CONFIG_H # include #endif #include #include #if HAVE_LIMITS_H # 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 */ }; /* 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_BLOCK_SIZE. FROM_BLOCK_SIZE must be nonnegative. If OUTPUT_BLOCK_SIZE is positive, use units of OUTPUT_BLOCK_SIZE in the output number. OUTPUT_BLOCK_SIZE must be a multiple of FROM_BLOCK_SIZE or vice versa. 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 -OUTPUT_BLOCK_SIZE aren't modified. */ char * 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 base; int to_block_size; int tenths = 0; int power; char *p; /* 0 means adjusted N == AMT.TENTHS; 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 = 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'; #ifdef lint /* Suppress `used before initialized' warning. */ power = 0; #endif /* Adjust AMT out of FROM_BLOCK_SIZE units and into TO_BLOCK_SIZE units. */ if (to_block_size <= from_block_size) { int multiplier = from_block_size / to_block_size; amt = n * multiplier; 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 <= damt && 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 if (from_block_size == 0) amt = 0; else { int divisor = to_block_size / from_block_size; int r10 = (n % divisor) * 10; int r2 = (r10 % divisor) * 2; amt = n / divisor; tenths = r10 / divisor; rounding = r2 < divisor ? 0 < r2 : 2 + (divisor < r2); } /* Use power of BASE notation if adjusted AMT is large enough. */ if (base && base <= amt) { power = 0; do { int r10 = (amt % base) * 10 + tenths; int r2 = (r10 % base) * 2 + (rounding >> 1); amt /= base; tenths = r10 / base; rounding = (r2 < base ? 0 < r2 + rounding : 2 + (base < r2 + rounding)); power++; } while (base <= amt && power < sizeof suffixes - 1); *--p = suffixes[power]; if (amt < 10) { if (2 * (1 - (int) inexact_style) < rounding + (tenths & (inexact_style == human_round_to_even))) { tenths++; rounding = 0; if (tenths == 10) { amt++; tenths = 0; } } if (amt < 10) { *--p = '0' + tenths; *--p = '.'; tenths = rounding = 0; } } } 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++; if (amt == base && power < sizeof suffixes - 1) { *p = suffixes[power + 1]; *--p = '0'; *--p = '.'; amt = 1; } } do *--p = '0' + (int) (amt % 10); while ((amt /= 10) != 0); 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); }