浏览代码

Initial revision

Paul Eggert 25 年之前
父节点
当前提交
a9d7ae22a8
共有 1 个文件被更改,包括 325 次插入0 次删除
  1. 325 0
      lib/human.c

+ 325 - 0
lib/human.c

@@ -0,0 +1,325 @@
+/* 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 [email protected];
+   --si, output block size selection, and large file support
+   added by [email protected].  */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#if HAVE_STRING_H
+# include <string.h>
+#else
+# include <strings.h>
+#endif
+
+#ifndef CHAR_BIT
+# define CHAR_BIT 8
+#endif
+#if HAVE_STDLIB_H
+# include <stdlib.h>
+#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 <libintl.h>
+# define _(Text) gettext (Text)
+#else
+# define _(Text) Text
+#endif
+
+#include <argmatch.h>
+#include <error.h>
+#include <xstrtol.h>
+
+#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);
+}