Human-readable file size

Hrvoje Niksic <[email protected]>
Newsgroups gmane.comp.web.wget.patches
Message-ID <[email protected]>
Along with the thousand-separated output, I find it useful to have
output in kilo-/mega-/giga-bytes, according to the file size.  If you
use `ls -lh', you surely know what I mean.

For example, wget
http://csociety-ftp.ecn.purdue.edu/pub/knoppix/KNOPPIX_V3.7-2004-12-08-EN.iso
prints:

    Length: 732,942,336 [text/plain]

It is not entirely obvious that fits on a CD.  To get megabytes, you
have to paste that amount into a calculator, remove the thousand
separators, and divide it with 1024^2.  What a hassle.  With this
patch, Wget prints the size like this:

    Length: 732,942,336 (699M) [text/plain]

Now the size in megabytes is much more apparent.  Of course, files
smaller than 1M are printed in kilobytes, and files smaller than 1K
are not specially marked at all.  In fact, the abbreviated size is
fully compatible with what we're accustomed to from `ls -lh'.  For
example:

Length: 936                 # small size, no change
Length: 2,500 (2.4K)        # small amount in K/M/..., include a decimal
Length: 25,000 (24K)        # larger amount, no decimals
Length: 25,000,000 (24M)    # larger yet uses megabytes
Length: 25,000,000,000 (23G)           # then gigabytes
Length: 25,000,000,000,000 (23T)       # then terrabytes
Length: 25,000,000,000,000,000 (23P)   # then petabytes

Read the source code to find out just how far this goes and be amused.


2005-03-21  Hrvoje Niksic  <[email protected]>

	* http.c (gethttp): Print the human-readable size.

	* ftp.c (getftp): Print the human-readable size of the file to be
	downloaded.

	* utils.c (human_readable): New function.

	* utils.c: Renamed "legible" to "with_thousand_seps",
	"legible_large_int" to "with_thousand_seps_large", and "legible_1"
	to "add_thousand_seps".

Index: src/ftp-ls.c
===================================================================
RCS file: /pack/anoncvs/wget/src/ftp-ls.c,v
retrieving revision 1.31
diff -u -r1.31 ftp-ls.c
--- src/ftp-ls.c	2005/03/19 17:23:31	1.31
+++ src/ftp-ls.c	2005/03/22 02:04:49
@@ -939,7 +939,7 @@
 	putc ('/', fp);
       fprintf (fp, "</a> ");
       if (f->type == FT_PLAINFILE)
-	fprintf (fp, _(" (%s bytes)"), legible (f->size));
+	fprintf (fp, _(" (%s bytes)"), with_thousand_seps (f->size));
       else if (f->type == FT_SYMLINK)
 	fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
       putc ('\n', fp);
Index: src/ftp.c
===================================================================
RCS file: /pack/anoncvs/wget/src/ftp.c,v
retrieving revision 1.95
diff -u -r1.95 ftp.c
--- src/ftp.c	2005/03/19 17:23:31	1.95
+++ src/ftp.c	2005/03/22 02:04:52
@@ -220,6 +220,26 @@
 }
 #endif
 
+static void
+print_length (wgint size, wgint start, int authoritative)
+{
+  logprintf (LOG_VERBOSE, _("Length: %s"), with_thousand_seps (size));
+  if (size >= 1024)
+    logprintf (LOG_VERBOSE, " (%s)", human_readable (size));
+  if (start > 0)
+    {
+      if (start >= 1024)
+	logprintf (LOG_VERBOSE, _(", %s (%s) remaining"),
+		   with_thousand_seps (size - start),
+		   human_readable (size - start));
+      else
+	logprintf (LOG_VERBOSE, _(", %s remaining"),
+		   with_thousand_seps (size - start));
+    }
+  if (!authoritative)
+    logputs (LOG_VERBOSE, _(" (unauthoritative)\n"));
+}
+
 /* Retrieves a file with denoted parameters through opening an FTP
    connection to the server.  It always closes the data connection,
    and closes the control connection in case of error.  */
@@ -993,20 +1013,11 @@
 
   if (*len)
     {
-      logprintf (LOG_VERBOSE, _("Length: %s"), legible (*len));
-      if (restval)
-	logprintf (LOG_VERBOSE, _(" [%s to go]"), legible (*len - restval));
-      logputs (LOG_VERBOSE, "\n");
+      print_length (*len, restval, 1);
       expected_bytes = *len;	/* for get_contents/show_progress */
     }
   else if (expected_bytes)
-    {
-      logprintf (LOG_VERBOSE, _("Length: %s"), legible (expected_bytes));
-      if (restval)
-	logprintf (LOG_VERBOSE, _(" [%s to go]"),
-		   legible (expected_bytes - restval));
-      logputs (LOG_VERBOSE, _(" (unauthoritative)\n"));
-    }
+    print_length (expected_bytes, restval, 0);
 
   /* Get the contents of the document.  */
   flags = 0;
Index: src/http.c
===================================================================
RCS file: /pack/anoncvs/wget/src/http.c,v
retrieving revision 1.152
diff -u -r1.152 http.c
--- src/http.c	2005/03/20 15:07:39	1.152
+++ src/http.c	2005/03/22 02:04:58
@@ -1733,9 +1740,20 @@
 	  logputs (LOG_VERBOSE, _("Length: "));
 	  if (contlen != -1)
 	    {
-	      logputs (LOG_VERBOSE, legible (contlen + contrange));
+	      logputs (LOG_VERBOSE, with_thousand_seps (contlen + contrange));
+	      if (contlen + contrange >= 1024)
+		logprintf (LOG_VERBOSE, " (%s)",
+			   human_readable (contlen + contrange));
 	      if (contrange)
-		logprintf (LOG_VERBOSE, _(" (%s to go)"), legible (contlen));
+		{
+		  if (contlen >= 1024)
+		    logprintf (LOG_VERBOSE, _(", %s (%s) remaining"),
+			       with_thousand_seps (contlen),
+			       human_readable (contlen));
+		  else
+		    logprintf (LOG_VERBOSE, _(", %s remaining"),
+			       with_thousand_seps (contlen));
+		}
 	    }
 	  else
 	    logputs (LOG_VERBOSE,
Index: src/main.c
===================================================================
RCS file: /pack/anoncvs/wget/src/main.c,v
retrieving revision 1.118
diff -u -r1.118 main.c
--- src/main.c	2005/03/20 15:07:39	1.118
+++ src/main.c	2005/03/22 02:05:00
@@ -943,13 +943,13 @@
     {
       logprintf (LOG_NOTQUIET,
 		 _("\nFINISHED --%s--\nDownloaded: %s bytes in %d files\n"),
-		 time_str (NULL), legible_large_int (total_downloaded_bytes),
+		 time_str (NULL), with_thousand_seps_large (total_downloaded_bytes),
 		 opt.numurls);
       /* Print quota warning, if exceeded.  */
       if (opt.quota && total_downloaded_bytes > opt.quota)
 	logprintf (LOG_NOTQUIET,
 		   _("Download quota (%s bytes) EXCEEDED!\n"),
-		   legible (opt.quota));
+		   with_thousand_seps_large (opt.quota));
     }
 
   if (opt.cookies_output)
Index: src/progress.c
===================================================================
RCS file: /pack/anoncvs/wget/src/progress.c,v
retrieving revision 1.45
diff -u -r1.45 progress.c
--- src/progress.c	2005/02/27 03:00:59	1.45
+++ src/progress.c	2005/03/22 02:05:03
@@ -721,7 +721,7 @@
   char *p = bp->buffer;
   wgint size = bp->initial_length + bp->count;
 
-  char *size_legible = legible (size);
+  char *size_legible = with_thousand_seps (size);
   int size_legible_len = strlen (size_legible);
 
   struct bar_progress_hist *hist = &bp->hist;
@@ -828,7 +828,7 @@
     }
 
   /* " 234,567,890" */
-  sprintf (p, " %-11s", legible (size));
+  sprintf (p, " %-11s", with_thousand_seps (size));
   p += strlen (p);
 
   /* " 1012.45K/s" */
Index: src/sysdep.h
===================================================================
RCS file: /pack/anoncvs/wget/src/sysdep.h,v
retrieving revision 1.35
diff -u -r1.35 sysdep.h
--- src/sysdep.h	2005/02/23 22:21:04	1.35
+++ src/sysdep.h	2005/03/22 02:05:05
@@ -111,7 +111,9 @@
 /* Define a large integral type useful for storing large sizes that
    exceed sizes of one download, such as when printing the sum of all
    downloads.  Note that this has nothing to do with large file
-   support, which determines the wgint type.
+   support, which determines the wgint type.  This should be as large
+   as possible even on systems where when wgint is 32-bit; also,
+   unlike wgint, this can be a floating point type.
 
    We use a 64-bit integral type where available, `double' otherwise.
    It's hard to print LARGE_INT's portably, but fortunately it's
Index: src/utils.c
===================================================================
RCS file: /pack/anoncvs/wget/src/utils.c,v
retrieving revision 1.85
diff -u -r1.85 utils.c
--- src/utils.c	2005/03/20 15:07:40	1.85
+++ src/utils.c	2005/03/22 02:05:09
@@ -1226,11 +1226,11 @@
 }
 
 
-/* Engine for legible and legible_large_int; add thousand separators
-   to numbers printed in strings.  */
+/* Add thousand separators to a number already in string form.  Used
+   by with_thousand_seps and with_thousand_seps_large.  */
 
 static char *
-legible_1 (const char *repr)
+add_thousand_seps (const char *repr)
 {
   static char outbuf[48];
   int i, i1, mod;
@@ -1266,41 +1266,106 @@
   return outbuf;
 }
 
-/* Legible -- return a static pointer to the legibly printed wgint.  */
+/* Return a static pointer to the number printed with thousand
+   separators inserted at the right places.  */
 
 char *
-legible (wgint l)
+with_thousand_seps (wgint l)
 {
   char inbuf[24];
   /* Print the number into the buffer.  */
   number_to_string (inbuf, l);
-  return legible_1 (inbuf);
+  return add_thousand_seps (inbuf);
 }
 
 /* Write a string representation of LARGE_INT NUMBER into the provided
-   buffer.  The buffer should be able to accept 24 characters,
-   including the terminating zero.
+   buffer.
 
    It would be dangerous to use sprintf, because the code wouldn't
    work on a machine with gcc-provided long long support, but without
-   libc support for "%lld".  However, such platforms will typically
-   not have snprintf and will use our version, which does support
-   "%lld" where long longs are available.  */
+   libc support for "%lld".  However, such old systems platforms
+   typically lack snprintf and will end up using our version, which
+   does support "%lld" whereever long longs are available.  */
 
 static void
-large_int_to_string (char *buffer, LARGE_INT number)
+large_int_to_string (char *buffer, int bufsize, LARGE_INT number)
 {
-  snprintf (buffer, 24, LARGE_INT_FMT, number);
+  snprintf (buffer, bufsize, LARGE_INT_FMT, number);
 }
 
-/* The same as legible(), but works on LARGE_INT.  */
+/* The same as with_thousand_seps, but works on LARGE_INT.  */
 
 char *
-legible_large_int (LARGE_INT l)
+with_thousand_seps_large (LARGE_INT l)
 {
   char inbuf[48];
-  large_int_to_string (inbuf, l);
-  return legible_1 (inbuf);
+  large_int_to_string (inbuf, sizeof (inbuf), l);
+  return add_thousand_seps (inbuf);
+}
+
+/* N, a byte quantity, is converted to a human-readable abberviated
+   form a la sizes printed by `ls -lh'.  The result is written to a
+   static buffer, a pointer to which is returned.
+
+   Unlike `with_thousand_seps', this approximates to the nearest unit.
+   Quoting GNU libit: "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."
+
+   This intentionally uses kilobyte (KB), megabyte (MB), etc. in their
+   original computer science meaning of "multiples of 1024".
+   Multiples of 1000 would be useless since Wget already adds thousand
+   separators for legibility.  We don't use the "*bibyte" names
+   invented in 1998, and seldom used in practice.  Wikipedia's entry
+   on kilobyte discusses this in some detail.  */
+
+char *
+human_readable (wgint n)
+{
+  /* These suffixes are compatible with those of GNU `ls -lh'. */
+  static char powers[] =
+    {
+      'K',			/* kilobyte, 2^10 bytes */
+      'M',			/* megabyte, 2^20 bytes */
+      'G',			/* gigabyte, 2^30 bytes */
+      'T',			/* terabyte, 2^40 bytes */
+      'P',			/* petabyte, 2^50 bytes */
+      'E',			/* exabyte,  2^60 bytes */
+    };
+  static char buf[8];
+  int i;
+
+  /* If the quantity is smaller than 1K, just print it. */
+  if (n < 1024)
+    {
+      snprintf (buf, sizeof (buf), "%d", (int) n);
+      return buf;
+    }
+
+  /* Loop over powers, dividing N with 1024 in each iteration.  This
+     works unchanged for all sizes of wgint, while still avoiding
+     non-portable `long double' arithmetic.  */
+  for (i = 0; i < countof (powers); i++)
+    {
+      /* At each iteration N is greater than the *subsequent* power.
+	 That way N/1024.0 produces a decimal number in the units of
+	 *this* power.  */
+      if ((n >> 10) < 1024 || i == countof (powers) - 1)
+	{
+	  /* Must cast to long first because MS VC can't directly cast
+	     __int64 to double.  (This is safe because N is known to
+	     be <2**20.)  */
+	  double val = (double) (long) n / 1024.0;
+	  /* Print values smaller than 10 with one decimal digits, and
+	     others without any decimals.  */
+	  snprintf (buf, sizeof (buf), "%.*f%c",
+		    val < 10 ? 1 : 0, val, powers[i]);
+	  return buf;
+	}
+      n >>= 10;
+    }
+  return NULL;			/* unreached */
 }
 
 /* Count the digits in an integer number.  */
Index: src/utils.h
===================================================================
RCS file: /pack/anoncvs/wget/src/utils.h,v
retrieving revision 1.37
diff -u -r1.37 utils.h
--- src/utils.h	2005/03/20 10:41:46	1.37
+++ src/utils.h	2005/03/22 02:05:10
@@ -112,8 +112,9 @@
 void string_set_free PARAMS ((struct hash_table *));
 void free_keys_and_values PARAMS ((struct hash_table *));
 
-char *legible PARAMS ((wgint));
-char *legible_large_int PARAMS ((LARGE_INT));
+char *with_thousand_seps PARAMS ((wgint));
+char *with_thousand_seps_large PARAMS ((LARGE_INT));
+char *human_readable PARAMS ((wgint));
 int numdigit PARAMS ((wgint));
 char *number_to_string PARAMS ((char *, wgint));
 char *number_to_static_string PARAMS ((wgint));
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.