fix(ui): a size one byte under a unit no longer reads 1024.0K

1 MiB - 1 byte  ->  "1024.0K"   in the SFTP listing
                    ->  "1024.0 KiB" while installing a remote server

Both loops asked whether the value had reached the next unit, then printed
it rounded to one decimal. 1023.999 has not reached 1024, and prints as
`1024.0` -- a number that disagrees with the unit beside it, next to a
`1.0M` one byte along.

The comparison is now against what will be shown rather than what is held,
at the same one decimal the format uses. The exact boundaries are
unchanged, and `1023.9K` still prints as itself: the carry only moves a
value that would have displayed as `1024.0`.

The two existing tests grew the boundary case rather than gaining
siblings, since each already walks its own formatter through the units.

`update.rs`'s `human_bytes` is left alone -- it prints megabytes and never
advances a unit, so it has no boundary to get wrong. That the three of
them format differently at all is deliberate: a size column is tight, a
progress line is not.
This commit is contained in:
l0ng-ai
2026-08-16 16:37:22 +08:00
parent 7b3968714b
commit 9723ff3dd8
2 changed files with 19 additions and 2 deletions
+7 -1
View File
@@ -540,7 +540,9 @@ pub fn human_bytes(n: u64) -> String {
let units = ["KiB", "MiB", "GiB"];
let mut value = n / KIB;
for (i, unit) in units.iter().enumerate() {
if value < KIB || i == units.len() - 1 {
// Against the shown value, not the held one: 1023.999 KiB prints as
// `1024.0 KiB`, a number that disagrees with its unit.
if (value * 10.0).round() < 10_240.0 || i == units.len() - 1 {
return format!("{value:.1} {unit}");
}
value /= KIB;
@@ -905,6 +907,10 @@ mod tests {
assert_eq!(human_bytes(1024), "1.0 KiB");
assert_eq!(human_bytes(1_572_864), "1.5 MiB");
assert_eq!(human_bytes(3 * 1024 * 1024 * 1024), "3.0 GiB");
// Rounding must not leave the number and the unit disagreeing.
assert_eq!(human_bytes(1_048_575), "1.0 MiB", "1 MiB - 1");
assert_eq!(human_bytes(1_073_741_823), "1.0 GiB", "1 GiB - 1");
}
#[test]
+12 -1
View File
@@ -283,7 +283,11 @@ fn human_size(bytes: u64) -> String {
const UNITS: [&str; 5] = ["B", "K", "M", "G", "T"];
let mut value = bytes as f64;
let mut unit = 0;
while value >= 1024.0 && unit < UNITS.len() - 1 {
// The comparison is against what will be *shown*, not what is held.
// One byte under a mebibyte is 1023.999 KiB, which prints as `1024.0K` —
// a number and a unit that disagree. Rounding to the same one decimal the
// format uses moves it up with the value.
while unit < UNITS.len() - 1 && (value * 10.0).round() >= 10_240.0 {
value /= 1024.0;
unit += 1;
}
@@ -1895,6 +1899,7 @@ impl Tty7App {
#[cfg(test)]
mod tests {
use super::*;
fn upload(job_id: u64, state: SftpJobState) -> SftpJobProgress {
@@ -2139,6 +2144,12 @@ mod tests {
assert_eq!(human_size(1024), "1.0K");
assert_eq!(human_size(1536), "1.5K");
assert_eq!(human_size(1024 * 1024), "1.0M");
// A byte under the next unit rounds to `1024.0` at one decimal, and a
// size column showing `1024.0K` beside `1.0M` reads as a mistake.
assert_eq!(human_size(1_048_575), "1.0M", "1 MiB - 1");
assert_eq!(human_size(1_073_741_823), "1.0G", "1 GiB - 1");
assert_eq!(human_size(1_048_524), "1023.9K", "just below the carry");
}
#[test]