aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2015-09-03 20:36:44 -0500
committerRob Landley <rob@landley.net>2015-09-03 20:36:44 -0500
commitd06ea3708d12e3e87fa26441715ac47f6dc63032 (patch)
tree18a517dd614a27ec99b36f51822a90d3a1155736
parent5640847b0328c5319f353eed985781e166547ef7 (diff)
downloadtoybox-d06ea3708d12e3e87fa26441715ac47f6dc63032.tar.gz
Make human_readable() handle base 1024 units without floating point.
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.)
-rw-r--r--lib/lib.c42
-rw-r--r--lib/lib.h8
-rwxr-xr-xtests/test_human_readable.test10
3 files changed, 41 insertions, 19 deletions
diff --git a/lib/lib.c b/lib/lib.c
index c16cffe4..27305ec9 100644
--- 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
diff --git a/lib/lib.h b/lib/lib.h
index 17a4a974..41f38d1b 100644
--- 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
index 00000000..a66b4fda
--- /dev/null
+++ b/tests/test_human_readable.test
@@ -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" "" ""