From 0a74f3be8e385b055664577bc0341cf017fd36d0 Mon Sep 17 00:00:00 2001 From: chiheb ben cheikh Date: Fri, 17 Apr 2026 14:14:31 +0100 Subject: [PATCH] fix: use IEC prefixes (Ki, Mi, Gi) for base-1024 size display (#570) dust was calculating sizes using base-1024 (IEC) but displaying them with SI prefixes (K, M, G). This is misleading since SI prefixes imply powers of 1000. Now base-1024 values use proper IEC prefixes (Ki, Mi, Gi, Ti, Pi) and base-1000 values (via -o si) continue using SI prefixes (K, M, G, T, P). --- src/display.rs | 78 ++++++++++++++++++++++--------------- tests/test_exact_output.rs | 80 +++++++++++++++++++------------------- 2 files changed, 87 insertions(+), 71 deletions(-) diff --git a/src/display.rs b/src/display.rs index 39d6a943..8464c3d1 100644 --- a/src/display.rs +++ b/src/display.rs @@ -16,7 +16,8 @@ use std::iter::repeat_n; use std::path::Path; use thousands::Separable; -pub static UNITS: [char; 5] = ['P', 'T', 'G', 'M', 'K']; +pub static SI_UNITS: [&str; 5] = ["P", "T", "G", "M", "K"]; +pub static IEC_UNITS: [&str; 5] = ["Pi", "Ti", "Gi", "Mi", "Ki"]; static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' ']; const FILETIME_SHOW_LENGTH: usize = 19; @@ -412,6 +413,14 @@ fn get_pretty_name( } } +pub fn get_units(output_str: &str) -> &'static [&'static str; 5] { + if get_type_of_thousand(output_str) == 1024 { + &IEC_UNITS + } else { + &SI_UNITS + } +} + // If we are working with SI units or not pub fn get_type_of_thousand(output_str: &str) -> u64 { if output_str.is_empty() { @@ -425,14 +434,16 @@ pub fn get_type_of_thousand(output_str: &str) -> u64 { } } -pub fn get_number_format(output_str: &str) -> Option<(u64, char)> { +pub fn get_number_format(output_str: &str) -> Option<(u64, &'static str)> { if output_str.starts_with('b') { - return Some((1, 'B')); - } - for (i, u) in UNITS.iter().enumerate() { - if output_str.starts_with((*u).to_ascii_lowercase()) { - let marker = get_type_of_thousand(output_str).pow((UNITS.len() - i) as u32); - return Some((marker, *u)); + return Some((1, "B")); + } + let units = get_units(output_str); + for (i, u) in units.iter().enumerate() { + if output_str.starts_with(u.chars().next().unwrap().to_ascii_lowercase()) { + let thousand = get_type_of_thousand(output_str); + let marker = thousand.pow((units.len() - i) as u32); + return Some((marker, u)); } } None @@ -447,8 +458,10 @@ pub fn human_readable_number(size: u64, output_str: &str) -> String { format!("{}{}", (size / x), u) } None => { - for (i, u) in UNITS.iter().enumerate() { - let marker = get_type_of_thousand(output_str).pow((UNITS.len() - i) as u32); + let units = get_units(output_str); + let thousand = get_type_of_thousand(output_str); + for (i, u) in units.iter().enumerate() { + let marker = thousand.pow((units.len() - i) as u32); if size >= marker { if size / marker < 10 { return format!("{:.1}{}", (size as f32 / marker as f32), u); @@ -502,7 +515,7 @@ mod tests { let data = get_fake_display_data(20); let s = format_string(&n, indent, percent_bar, is_biggest, &data); - assert_eq!(s, " 4.0K ┌─┴ short"); + assert_eq!(s, "4.0Ki ┌─┴ short"); } #[test] @@ -521,7 +534,7 @@ mod tests { let s = format_string(&n, indent, percent_bar, is_biggest, &data); assert_eq!( s, - " 4.0K ┌─┴ very_long_name_longer_than_the_eighty_character_limit_very_.." + "4.0Ki ┌─┴ very_long_name_longer_than_the_eighty_character_limit_very_.." ); } @@ -539,7 +552,7 @@ mod tests { data.initial.is_screen_reader = true; let s = format_string(&n, indent, percent_bar, is_biggest, &data); - assert_eq!(s, "short 3 4.0K 100%"); + assert_eq!(s, "short 3 4.0Ki 100%"); } #[test] @@ -554,26 +567,29 @@ mod tests { assert_eq!(human_readable_number(1, ""), "1B"); assert_eq!(human_readable_number(956, ""), "956B"); assert_eq!(human_readable_number(1004, ""), "1004B"); - assert_eq!(human_readable_number(1024, ""), "1.0K"); - assert_eq!(human_readable_number(1536, ""), "1.5K"); - assert_eq!(human_readable_number(1024 * 512, ""), "512K"); - assert_eq!(human_readable_number(1024 * 1024, ""), "1.0M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1, ""), "1023M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, ""), "20G"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024, ""), "1.0T"); + assert_eq!(human_readable_number(1024, ""), "1.0Ki"); + assert_eq!(human_readable_number(1536, ""), "1.5Ki"); + assert_eq!(human_readable_number(1024 * 512, ""), "512Ki"); + assert_eq!(human_readable_number(1024 * 1024, ""), "1.0Mi"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1, ""), "1023Mi"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, ""), "20Gi"); + assert_eq!( + human_readable_number(1024 * 1024 * 1024 * 1024, ""), + "1.0Ti" + ); assert_eq!( human_readable_number(1024 * 1024 * 1024 * 1024 * 234, ""), - "234T" + "234Ti" ); assert_eq!( human_readable_number(1024 * 1024 * 1024 * 1024 * 1024, ""), - "1.0P" + "1.0Pi" ); } #[test] fn test_human_readable_number_si() { - assert_eq!(human_readable_number(1024 * 100, ""), "100K"); + assert_eq!(human_readable_number(1024 * 100, ""), "100Ki"); assert_eq!(human_readable_number(1024 * 100, "si"), "102K"); } @@ -584,14 +600,14 @@ mod tests { assert_eq!(hrn(1023, "b"), "1023B"); assert_eq!(hrn(1000 * 1000, "bytes"), "1000000B"); assert_eq!(hrn(1023, "kb"), "1K"); - assert_eq!(hrn(1023, "k"), "0K"); - assert_eq!(hrn(1023, "kib"), "0K"); - assert_eq!(hrn(1024, "kib"), "1K"); - assert_eq!(hrn(1024 * 512, "kib"), "512K"); - assert_eq!(hrn(1024 * 1024, "kib"), "1024K"); - assert_eq!(hrn(1024 * 1000 * 1000 * 20, "kib"), "20000000K"); - assert_eq!(hrn(1024 * 1024 * 1000 * 20, "mib"), "20000M"); - assert_eq!(hrn(1024 * 1024 * 1024 * 20, "gib"), "20G"); + assert_eq!(hrn(1023, "k"), "0Ki"); + assert_eq!(hrn(1023, "kib"), "0Ki"); + assert_eq!(hrn(1024, "kib"), "1Ki"); + assert_eq!(hrn(1024 * 512, "kib"), "512Ki"); + assert_eq!(hrn(1024 * 1024, "kib"), "1024Ki"); + assert_eq!(hrn(1024 * 1000 * 1000 * 20, "kib"), "20000000Ki"); + assert_eq!(hrn(1024 * 1024 * 1000 * 20, "mib"), "20000Mi"); + assert_eq!(hrn(1024 * 1024 * 1024 * 20, "gib"), "20Gi"); } #[cfg(test)] diff --git a/tests/test_exact_output.rs b/tests/test_exact_output.rs index 2d4a4bad..8c952da7 100644 --- a/tests/test_exact_output.rs +++ b/tests/test_exact_output.rs @@ -117,19 +117,19 @@ fn main_output() -> Vec { // Some linux currently thought to be Manjaro, Arch // Although probably depends on how drive is formatted let mac_and_some_linux = r#" - 0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0% -4.0K ├── hello_file│█████████████████████████████████████████████████ │ 100% -4.0K ┌─┴ many │█████████████████████████████████████████████████ │ 100% -4.0K ┌─┴ test_dir │█████████████████████████████████████████████████ │ 100% + 0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0% +4.0Ki ├── hello_file│████████████████████████████████████████████████ │ 100% +4.0Ki ┌─┴ many │████████████████████████████████████████████████ │ 100% +4.0Ki ┌─┴ test_dir │████████████████████████████████████████████████ │ 100% "# .trim() .to_string(); let ubuntu = r#" - 0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0% -4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33% -8.0K ┌─┴ many │ █████████████████████████████████ │ 67% - 12K ┌─┴ test_dir │█████████████████████████████████████████████████ │ 100% + 0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0% +4.0Ki ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33% +8.0Ki ┌─┴ many │ █████████████████████████████████ │ 67% + 12Ki ┌─┴ test_dir │████████████████████████████████████████████████ │ 100% "# .trim() .to_string(); @@ -146,18 +146,18 @@ pub fn test_main_long_paths() { fn main_output_long_paths() -> Vec { let mac_and_some_linux = r#" - 0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0% -4.0K ├── /tmp/test_dir/many/hello_file│██████████████████████████████ │ 100% -4.0K ┌─┴ /tmp/test_dir/many │██████████████████████████████ │ 100% -4.0K ┌─┴ /tmp/test_dir │██████████████████████████████ │ 100% + 0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0% +4.0Ki ├── /tmp/test_dir/many/hello_file│████████████████████████████ │ 100% +4.0Ki ┌─┴ /tmp/test_dir/many │████████████████████████████ │ 100% +4.0Ki ┌─┴ /tmp/test_dir │████████████████████████████ │ 100% "# .trim() .to_string(); let ubuntu = r#" - 0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░░█ │ 0% -4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░███████████ │ 33% -8.0K ┌─┴ /tmp/test_dir/many │ █████████████████████ │ 67% - 12K ┌─┴ /tmp/test_dir │██████████████████████████████ │ 100% + 0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░█ │ 0% +4.0Ki ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░██████████ │ 33% +8.0Ki ┌─┴ /tmp/test_dir/many │ ████████████████████ │ 67% + 12Ki ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100% "# .trim() .to_string(); @@ -174,25 +174,25 @@ pub fn test_substring_of_names_and_long_names() { fn no_substring_of_names_output() -> Vec { let ubuntu = " - 0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes.. -4.0K ├── dir_name_clash -4.0K │ ┌── hello -8.0K ├─┴ dir -4.0K │ ┌── hello -8.0K ├─┴ dir_substring - 24K ┌─┴ test_dir2 + 0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goe.. +4.0Ki ├── dir_name_clash +4.0Ki │ ┌── hello +8.0Ki ├─┴ dir +4.0Ki │ ┌── hello +8.0Ki ├─┴ dir_substring + 24Ki ┌─┴ test_dir2 " .trim() .into(); let mac_and_some_linux = " - 0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes.. -4.0K │ ┌── hello -4.0K ├─┴ dir -4.0K ├── dir_name_clash -4.0K │ ┌── hello -4.0K ├─┴ dir_substring - 12K ┌─┴ test_dir2 + 0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goe.. +4.0Ki │ ┌── hello +4.0Ki ├─┴ dir +4.0Ki ├── dir_name_clash +4.0Ki │ ┌── hello +4.0Ki ├─┴ dir_substring + 12Ki ┌─┴ test_dir2 " .trim() .into(); @@ -209,9 +209,9 @@ pub fn test_unicode_directories() { fn unicode_dir() -> Vec { // The way unicode & asian characters are rendered on the terminal should make this line up let ubuntu = " - 0B ┌── ラウトは難しいです!.japan│ █ │ 0% - 0B ├── 👩.unicode │ █ │ 0% -4.0K ┌─┴ test_dir_unicode │███████████████████████████████████ │ 100% + 0B ┌── ラウトは難しいです!.japan│ █ │ 0% + 0B ├── 👩.unicode │ █ │ 0% +4.0Ki ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100% " .trim() .into(); @@ -235,21 +235,21 @@ pub fn test_apparent_size() { fn apparent_size_output() -> Vec { // The apparent directory sizes are too unpredictable and system dependent to try and match - let one_space_before = r#" - 0B ┌── a_file - 6B ├── hello_file + let two_space_before = r#" + 0B ┌── a_file + 6B ├── hello_file "# .trim() .to_string(); - let two_space_before = r#" - 0B ┌── a_file - 6B ├── hello_file + let three_space_before = r#" + 0B ┌── a_file + 6B ├── hello_file "# .trim() .to_string(); - vec![one_space_before, two_space_before] + vec![two_space_before, three_space_before] } #[cfg_attr(target_os = "windows", ignore)]