From d06ea3708d12e3e87fa26441715ac47f6dc63032 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Thu, 3 Sep 2015 20:36:44 -0500 Subject: 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.) --- lib/lib.c | 42 ++++++++++++++++++++++++++---------------- lib/lib.h | 8 +++++--- tests/test_human_readable.test | 10 ++++++++++ 3 files changed, 41 insertions(+), 19 deletions(-) create mode 100755 tests/test_human_readable.test 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" "" "" -- cgit v1.2.3