OSDN Git Service

Make human_readable() handle base 1024 units without floating point.
authorRob Landley <rob@landley.net>
Fri, 4 Sep 2015 01:36:44 +0000 (20:36 -0500)
committerRob Landley <rob@landley.net>
Fri, 4 Sep 2015 01:36:44 +0000 (20:36 -0500)
Rounds correctly via brute force, displayed digits are decimal even when
working with powers of 2, shows at most 3 significant (decimal) digits.
(So no "1023M" nonsense, that's 1.0G.)

lib/lib.c
lib/lib.h
tests/test_human_readable.test [new file with mode: 0755]

index c16cffe..27305ec 100644 (file)
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -866,27 +866,37 @@ void names_to_pid(char **names, int (*callback)(pid_t pid, char *name))
   closedir(dp);
 }
 
-// display first few digits of number with power of two units, except we're
-// actually just counting decimal digits and showing mil/bil/trillions.
+// display first few digits of number with power of two units
 int human_readable(char *buf, unsigned long long num, int style)
 {
-  int end, len;
-
-  len = sprintf(buf, "%lld", num)-1;
-  end = (len%3)+1;
-  len /= 3;
+  unsigned long long snap = 0;
+  int len, unit, divisor = (style&HR_SI) ? 1024 : 1000;
+
+  // Divide rounding up until we have 3 or fewer digits. Since the part we
+  // print is decimal, the test is 999 even when we divide by 1024.
+  // We can't run out of units because 2<<64 is 18 exabytes.
+  // test 5675 is 5.5k not 5.6k.
+  for (unit = 0; num > 999; unit++) num = ((snap = num)+(divisor/2))/divisor;
+  len = sprintf(buf, "%llu", num);
+  if (unit && len == 1) {
+    // Redo rounding for 1.2M case, this works with HR_SI and not.
+    num = snap/divisor;
+    snap -= num*divisor;
+    snap = ((snap*100)+50)/divisor;
+    snap /= 10;
+    len = sprintf(buf, "%llu.%llu", num, snap);
+  }
+  if (style & HR_SPACE) buf[len++] = ' ';
+  if (unit) {
+    unit = " kMGTPE"[unit];
 
-  if (len && end == 1) {
-    buf[2] = buf[1];
-    buf[1] = '.';
-    end = 3;
+    if (!(style&HR_SI)) unit = toupper(unit);
+    buf[len++] = unit;
   }
-  if (style & HR_SPACE) buf[end++] = ' ';
-  if (len) buf[end++] = " KMGTPE"[len];
-  if (style & HR_B) buf[end++] = 'B';
-  buf[end++] = 0;
+  if (style & HR_B) buf[len++] = 'B';
+  buf[len] = 0;
 
-  return end;
+  return len;
 }
 
 // The qsort man page says you can use alphasort, the posix committee
index 17a4a97..41f38d1 100644 (file)
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -177,12 +177,14 @@ void replace_tempfile(int fdin, int fdout, char **tempname);
 void crc_init(unsigned int *crc_table, int little_endian);
 void base64_init(char *p);
 int yesno(char *prompt, int def);
-#define HR_SPACE 1
-#define HR_B 2
-int human_readable(char *buf, unsigned long long num, int style);
 int qstrcmp(const void *a, const void *b);
 int xpoll(struct pollfd *fds, int nfds, int timeout);
 
+#define HR_SPACE 1
+#define HR_B     2
+#define HR_SI    4
+int human_readable(char *buf, unsigned long long num, int style);
+
 // interestingtimes.c
 int xgettty(void);
 int terminal_size(unsigned *xx, unsigned *yy);
diff --git a/tests/test_human_readable.test b/tests/test_human_readable.test
new file mode 100755 (executable)
index 0000000..a66b4fd
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+testing "human_readable" "test_human_readable 123456789" "123M\n" "" ""
+testing "human_readable -b" "test_human_readable -b 123456789" "123MB\n" "" ""
+testing "human_readable -s" "test_human_readable -s 123456789" "123 M\n" "" ""
+testing "human_readable -i" "test_human_readable -i 5675" "5.5k\n" "" ""