From 8a6c36aed852aa43252c1348bb9668fd65f17497 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 21 Mar 2023 10:13:47 +0100 Subject: [PATCH 1/7] 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 | 40 ++++++++++++++++++++-------------------- rsync.1.md | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index 513d79b23..71738afe2 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'; + unsigned int mult = human_flag == 2 ? 1000 : 1024; + + if (abs_num >= mult) { + 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 fdf0b2e95..30bf59d51 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3352,9 +3352,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 92ed6415cdbf37a532bacdb16768a0ea8b2cc120 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 9 Jun 2026 10:05:44 +0200 Subject: [PATCH 2/7] human-readable: do not add trailing space in output --- lib/compat.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index 71738afe2..a6433afe2 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -185,22 +185,24 @@ char *do_big_num(int64 num, int human_flag, const char *fract) unsigned int mult = human_flag == 2 ? 1000 : 1024; if (abs_num >= mult) { - const char* units = " KMGTPE"; + char units[] = "\0KMGTPE"; + char* unit = units; uint64_t powi = 1; for (;;) { if (abs_num / mult < powi) break; - if (units[1] == '\0') + if (unit[1] == '\0') break; powi *= mult; - ++units; + ++unit; } + unit[1] = '\0'; - snprintf(bufs[n], sizeof bufs[0], "%.2f%c", - (double) num / powi, *units); + snprintf(bufs[n], sizeof bufs[0], "%.2f%s", + (double) num / powi, unit); return bufs[n]; } } From f9acfef1dbd1f02fad9dd6c9c0152cb4f1f820ac Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 21 Mar 2023 10:17:09 +0100 Subject: [PATCH 3/7] 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 +++++++++-- rsync.1.md | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index a6433afe2..aa982c22e 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -188,6 +188,7 @@ char *do_big_num(int64 num, int human_flag, const char *fract) char units[] = "\0KMGTPE"; char* unit = units; uint64_t powi = 1; + unsigned int powj = 1, precision = 2; for (;;) { if (abs_num / mult < powi) @@ -201,8 +202,14 @@ char *do_big_num(int64 num, int human_flag, const char *fract) } unit[1] = '\0'; - snprintf(bufs[n], sizeof bufs[0], "%.2f%s", - (double) num / powi, unit); + for (; precision > 0; precision--) { + powj *= 10; + if (abs_num / powi < powj) + break; + } + + snprintf(bufs[n], sizeof bufs[0], "%.*f%s", + precision, (double) num / powi, unit); return bufs[n]; } } diff --git a/rsync.1.md b/rsync.1.md index 30bf59d51..a3a2b28e8 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3354,7 +3354,9 @@ expand it. The unit letters that are appended in levels 2 and 3 are: `K` (kilo), `M` (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). + period is your local decimal point). Dynamic precision is applied, so the + three most-significant digits are shown (for example: 3.45M -> 46.7M -> + 523M -> 1.24G -> ...). 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 877148575d535f08bf29a9595dc6747859b911d0 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Wed, 22 Mar 2023 11:07:15 +0100 Subject: [PATCH 4/7] human-readable: also handle num < mult with the same code ... just make sure no precision is added. --- lib/compat.c | 55 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index aa982c22e..8dcaa57d9 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -184,34 +184,35 @@ char *do_big_num(int64 num, int human_flag, const char *fract) if (human_flag > 1) { unsigned int mult = human_flag == 2 ? 1000 : 1024; - if (abs_num >= mult) { - char units[] = "\0KMGTPE"; - char* unit = units; - uint64_t powi = 1; - unsigned int powj = 1, precision = 2; - - for (;;) { - if (abs_num / mult < powi) - break; - - if (unit[1] == '\0') - break; - - powi *= mult; - ++unit; - } - unit[1] = '\0'; - - for (; precision > 0; precision--) { - powj *= 10; - if (abs_num / powi < powj) - break; - } - - snprintf(bufs[n], sizeof bufs[0], "%.*f%s", - precision, (double) num / powi, unit); - return bufs[n]; + char units[] = "\0KMGTPE"; + char* unit = units; + uint64_t powi = 1; + unsigned int powj = 1, precision = 2; + + for (;;) { + if (abs_num / mult < powi) + break; + + if (unit[1] == '\0') + break; + + powi *= mult; + ++unit; } + unit[1] = '\0'; + + if (powi == 1) + precision = 0; + + for (; precision > 0; precision--) { + powj *= 10; + if (abs_num / powi < powj) + break; + } + + snprintf(bufs[n], sizeof bufs[0], "%.*f%s", precision, + (double) num / powi, unit); + return bufs[n]; } s = bufs[n] + sizeof bufs[0] - 1; From 8279aab629dcd2f2597a76b93ee25e63b1f1f1d3 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Thu, 23 Mar 2023 10:33:33 +0100 Subject: [PATCH 5/7] 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 8dcaa57d9..4cce66fb6 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -210,8 +210,8 @@ char *do_big_num(int64 num, int human_flag, const char *fract) break; } - snprintf(bufs[n], sizeof bufs[0], "%.*f%s", precision, - (double) num / powi, unit); + snprintf(bufs[n], sizeof bufs[0], "%.*f%s%s", precision, + (double) num / powi, unit, *unit && mult == 1024 ? "i" : ""); return bufs[n]; } diff --git a/rsync.1.md b/rsync.1.md index a3a2b28e8..83f3b6799 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3357,6 +3357,8 @@ expand it. period is your local decimal point). Dynamic precision is applied, so the three most-significant digits are shown (for example: 3.45M -> 46.7M -> 523M -> 1.24G -> ...). + 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 fdfd604ed50cf41ef87071a0dbe20dda9f494593 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 9 Apr 2024 09:56:39 +0200 Subject: [PATCH 6/7] 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..a2e4e0287 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 = (double) (ofs - ph_start.ofs) * 1000 / diff; /* Switch to total time taken for our last update. */ - remain = (double) diff / 1000.0; + remain = (double) diff / 1000; } 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 = (double) (ofs - ph_list[oldest_hpos].ofs) * 1000 / diff; + remain = rate ? (double) (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 > 9999 * 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 e329ff19c4473386e74c766c8643b991016fa3ac Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Tue, 9 Apr 2024 10:50:20 +0200 Subject: [PATCH 7/7] 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 a2e4e0287..8b3fcdd60 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;