From 6d8b301088461f86a62b00d8f3a88c0512bf533b Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 21 Mar 2023 10:13:47 +0100 Subject: [PATCH 1/6] human-readable: calculate numbers in a loop This drops a lot of `else if` blocks and extends units by "E" (Exa), which is 2^60 and thus the last to fit into int64. --- lib/compat.c | 36 ++++++++++++++++++------------------ rsync.1.md | 6 +++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index 513d79b23..9cc4be4fa 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -173,34 +173,34 @@ char *do_big_num(int64 num, int human_flag, const char *fract) static unsigned int n; char *s; int len, negated; + uint64_t abs_num; if (human_flag && !number_separator) (void)get_number_separator(); n = (n + 1) % (sizeof bufs / sizeof bufs[0]); + abs_num = num < 0 ? 0 - (uint64_t)num : (uint64_t)num; if (human_flag > 1) { int mult = human_flag == 2 ? 1000 : 1024; + if (num >= mult || num <= -mult) { - double dnum = (double)num / mult; - char units; - if (num < 0) - dnum = -dnum; - if (dnum < mult) - units = 'K'; - else if ((dnum /= mult) < mult) - units = 'M'; - else if ((dnum /= mult) < mult) - units = 'G'; - else if ((dnum /= mult) < mult) - units = 'T'; - else { - dnum /= mult; - units = 'P'; + const char* units = " KMGTPE"; + uint64_t powi = 1; + + for (;;) { + if (abs_num / mult < powi) + break; + + if (units[1] == '\0') + break; + + powi *= mult; + ++units; } - if (num < 0) - dnum = -dnum; - snprintf(bufs[n], sizeof bufs[0], "%.2f%c", dnum, units); + + snprintf(bufs[n], sizeof bufs[0], "%.2f%c", + (double) num / powi, *units); return bufs[n]; } } diff --git a/rsync.1.md b/rsync.1.md index 4aa7d6520..125bdb42f 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3342,9 +3342,9 @@ expand it. digits) by specifying the `--no-human-readable` (`--no-h`) option. The unit letters that are appended in levels 2 and 3 are: `K` (kilo), `M` - (mega), `G` (giga), `T` (tera), or `P` (peta). For example, a 1234567-byte - file would output as 1.23M in level-2 (assuming that a period is your local - decimal point). + (mega), `G` (giga), `T` (tera), `P` (peta) or `E` (exa). For example, a + 1234567-byte file would output as 1.23M in level-2 (assuming that a + period is your local decimal point). Backward compatibility note: versions of rsync prior to 3.1.0 do not support human-readable level 1, and they default to level 0. Thus, From f488dcf2aabce08a2ce0a30dd0cc750e4625c034 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Sat, 6 Jun 2026 15:42:38 +0200 Subject: [PATCH 2/6] human-readable: use dynamic precision length Let's lower precision for huge numbers. The output used to be: 3.45M -> 46.73M -> 523.11M -> 1.24G -> ... With this change the code always gives the three most significant digits: 3.45M -> 46.7M -> 523M -> 1.24G -> ... --- lib/compat.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index 9cc4be4fa..94eb4cd67 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -187,6 +187,7 @@ char *do_big_num(int64 num, int human_flag, const char *fract) if (num >= mult || num <= -mult) { const char* units = " KMGTPE"; uint64_t powi = 1; + unsigned int powj = 1, precision = 2; for (;;) { if (abs_num / mult < powi) @@ -199,8 +200,14 @@ char *do_big_num(int64 num, int human_flag, const char *fract) ++units; } - snprintf(bufs[n], sizeof bufs[0], "%.2f%c", - (double) num / powi, *units); + for (; precision > 0; precision--) { + powj *= 10; + if (abs_num / powi < powj) + break; + } + + snprintf(bufs[n], sizeof bufs[0], "%.*f%c", + precision, (double) num / powi, *units); return bufs[n]; } } From 5747ccf31ba902d3fbcaf33f4dda71209d2164e6 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Sat, 6 Jun 2026 18:18:14 +0200 Subject: [PATCH 3/6] human-readable: also handle num < mult with the same code ... just make sure no precision is added. --- lib/compat.c | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index 94eb4cd67..7ff3de53a 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -184,32 +184,33 @@ char *do_big_num(int64 num, int human_flag, const char *fract) if (human_flag > 1) { int mult = human_flag == 2 ? 1000 : 1024; - if (num >= mult || num <= -mult) { - const char* units = " KMGTPE"; - uint64_t powi = 1; - unsigned int powj = 1, precision = 2; - - for (;;) { - if (abs_num / mult < powi) - break; - - if (units[1] == '\0') - break; - - powi *= mult; - ++units; - } - - for (; precision > 0; precision--) { - powj *= 10; - if (abs_num / powi < powj) - break; - } - - snprintf(bufs[n], sizeof bufs[0], "%.*f%c", - precision, (double) num / powi, *units); - return bufs[n]; + const char* units = " KMGTPE"; + uint64_t powi = 1; + unsigned int powj = 1, precision = 2; + + for (;;) { + if (abs_num / mult < powi) + break; + + if (units[1] == '\0') + break; + + powi *= mult; + ++units; } + + if (powi == 1) + precision = 0; + + for (; precision > 0; precision--) { + powj *= 10; + if (abs_num / powi < powj) + break; + } + + snprintf(bufs[n], sizeof bufs[0], "%.*f%c", precision, + (double) num / powi, *units); + return bufs[n]; } s = bufs[n] + sizeof bufs[0] - 1; From 397f3ba4b8d8d1364eb5955de2c67ddbea00b092 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Thu, 23 Mar 2023 10:33:33 +0100 Subject: [PATCH 4/6] human-readable: add an "i" to unit to indicate binary (1024) base --- lib/compat.c | 4 ++-- rsync.1.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index 7ff3de53a..d80194a28 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -208,8 +208,8 @@ char *do_big_num(int64 num, int human_flag, const char *fract) break; } - snprintf(bufs[n], sizeof bufs[0], "%.*f%c", precision, - (double) num / powi, *units); + snprintf(bufs[n], sizeof bufs[0], "%.*f%c%s", precision, + (double) num / powi, *units, num > mult && mult == 1024 ? "i" : ""); return bufs[n]; } diff --git a/rsync.1.md b/rsync.1.md index 125bdb42f..72c20f799 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3345,6 +3345,8 @@ expand it. (mega), `G` (giga), `T` (tera), `P` (peta) or `E` (exa). For example, a 1234567-byte file would output as 1.23M in level-2 (assuming that a period is your local decimal point). + Additionally an `i` is appended in level-3 to indicate the binary base. + The same file would output as 1.17Mi in level-3. Backward compatibility note: versions of rsync prior to 3.1.0 do not support human-readable level 1, and they default to level 0. Thus, From 19b2184ca5261a0f3a49c51635449559d3119cf3 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 9 Apr 2024 09:56:39 +0200 Subject: [PATCH 5/6] human-readable: also use it to format rate Let's also simplify the code for rate in progress, and benefit from same functionality. --- progress.c | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/progress.c b/progress.c index 87207fbfa..98a01c84c 100644 --- a/progress.c +++ b/progress.c @@ -69,9 +69,8 @@ static unsigned long msdiff(struct timeval *t1, struct timeval *t2) static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_last) { char rembuf[64], eol[128]; - const char *units; unsigned long diff; - double rate, remain; + int64 rate, remain; int pct; if (is_last) { @@ -93,41 +92,31 @@ static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_l /* Compute stats based on the starting info. */ if (!ph_start.time.tv_sec || !(diff = msdiff(&ph_start.time, now))) diff = 1; - rate = (double) (ofs - ph_start.ofs) * 1000.0 / diff / 1024.0; + rate = (ofs - ph_start.ofs) * 1000 / diff; /* Switch to total time taken for our last update. */ - remain = (double) diff / 1000.0; + remain = diff; } else { strlcpy(eol, " ", sizeof eol); /* Compute stats based on recent progress. */ if (!(diff = msdiff(&ph_list[oldest_hpos].time, now))) diff = 1; - rate = (double) (ofs - ph_list[oldest_hpos].ofs) * 1000.0 / diff / 1024.0; - remain = rate ? (double) (size - ofs) / rate / 1000.0 : 0.0; + rate = (ofs - ph_list[oldest_hpos].ofs) * 1000 / diff; + remain = rate ? (size - ofs) / rate : 0; } - if (rate > 1024*1024) { - rate /= 1024.0 * 1024.0; - units = "GB/s"; - } else if (rate > 1024) { - rate /= 1024.0; - units = "MB/s"; - } else { - units = "kB/s"; - } - - if (remain < 0 || remain > 9999.0 * 3600.0) + if (remain < 0 || remain > (int64) 9999999 * 3600) strlcpy(rembuf, " ??:??:??", sizeof rembuf); else { snprintf(rembuf, sizeof rembuf, "%4u:%02u:%02u", - (unsigned int) (remain / 3600.0), - (unsigned int) (remain / 60.0) % 60, + (unsigned int) (remain / 3600), + (unsigned int) (remain / 60) % 60, (unsigned int) remain % 60); } output_needs_newline = 0; pct = ofs == size ? 100 : (int) (100.0 * ofs / size); - rprintf(FCLIENT, "\r%15s %3d%% %7.2f%s %s%s", - human_num(ofs), pct, rate, units, rembuf, eol); + rprintf(FCLIENT, "\r%15s %3d%% %7sB/s %s%s", + human_num(ofs), pct, human_num(rate), rembuf, eol); if (!is_last && !quiet) { output_needs_newline = 1; rflush(FCLIENT); From 4dfbafe0aedd28b3c4871a0f1a73838735e626e2 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 9 Apr 2024 10:50:20 +0200 Subject: [PATCH 6/6] human-readable: append suffix "B" for byte to ofs --- progress.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progress.c b/progress.c index 98a01c84c..5e20fb40d 100644 --- a/progress.c +++ b/progress.c @@ -115,7 +115,7 @@ static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_l output_needs_newline = 0; pct = ofs == size ? 100 : (int) (100.0 * ofs / size); - rprintf(FCLIENT, "\r%15s %3d%% %7sB/s %s%s", + rprintf(FCLIENT, "\r%15sB %3d%% %7sB/s %s%s", human_num(ofs), pct, human_num(rate), rembuf, eol); if (!is_last && !quiet) { output_needs_newline = 1;