Repository: uutils/coreutils (language: rust)

Issue:
numfmt: should use lowercase k with --to=si option
Environment: Ubuntu 24.04, uutils main branch (git commit 2430e2a396d5f5ae29413081b065a4503245839b), GNU coreutils v9.6.8-fbfd88-dirty

Steps to reproduce:
```
numfmt --to=si 2000
```

What happens now: uutils `numfmt` uses uppercase K:
```
2.0K
```

What I expected to happen: GNU `numfmt` uses lowercase k:
```
2.0k
```

Notes: this is causing a test failure in GNU test file `tests/misc/numfmt.pl`.


Discussion / hints:
Just to make sure: Is it completely decorrelatted from any LC_* and location stuff ?
> Just to make sure: Is it completely decorrelatted from any LC_* and location stuff ?

Yes, the relevant tests in [tests/misc/numfmt.pl](https://github.com/coreutils/coreutils/blob/master/tests/misc/numfmt.pl#L1089) don't use localization (they turn it off at the top of the file).
Hey i am interested in fixing this. can this be asssigned to me?
@melonSorbet sure, go ahead :)

Fix patch:
diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs
index 5933092f62f..b6250825935 100644
--- a/src/uu/numfmt/src/format.rs
+++ b/src/uu/numfmt/src/format.rs
@@ -281,12 +281,12 @@ fn transform_to(
             format!(
                 "{:.precision$}{}",
                 i2,
-                DisplayableSuffix(s),
+                DisplayableSuffix(s, opts.to),
                 precision = precision
             )
         }
-        Some(s) if i2.abs() < 10.0 => format!("{:.1}{}", i2, DisplayableSuffix(s)),
-        Some(s) => format!("{:.0}{}", i2, DisplayableSuffix(s)),
+        Some(s) if i2.abs() < 10.0 => format!("{:.1}{}", i2, DisplayableSuffix(s, opts.to)),
+        Some(s) => format!("{:.0}{}", i2, DisplayableSuffix(s, opts.to)),
     })
 }
 
diff --git a/src/uu/numfmt/src/units.rs b/src/uu/numfmt/src/units.rs
index 585bae46141..c52dee20c02 100644
--- a/src/uu/numfmt/src/units.rs
+++ b/src/uu/numfmt/src/units.rs
@@ -45,20 +45,21 @@ pub enum RawSuffix {
 
 pub type Suffix = (RawSuffix, WithI);
 
-pub struct DisplayableSuffix(pub Suffix);
+pub struct DisplayableSuffix(pub Suffix, pub Unit);
 
 impl fmt::Display for DisplayableSuffix {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let Self((ref raw_suffix, ref with_i)) = *self;
-        match raw_suffix {
-            RawSuffix::K => write!(f, "K"),
-            RawSuffix::M => write!(f, "M"),
-            RawSuffix::G => write!(f, "G"),
-            RawSuffix::T => write!(f, "T"),
-            RawSuffix::P => write!(f, "P"),
-            RawSuffix::E => write!(f, "E"),
-            RawSuffix::Z => write!(f, "Z"),
-            RawSuffix::Y => write!(f, "Y"),
+        let Self((ref raw_suffix, ref with_i), unit) = *self;
+        match (raw_suffix, unit) {
+            (RawSuffix::K, Unit::Si) => write!(f, "k"),
+            (RawSuffix::K, _) => write!(f, "K"),
+            (RawSuffix::M, _) => write!(f, "M"),
+            (RawSuffix::G, _) => write!(f, "G"),
+            (RawSuffix::T, _) => write!(f, "T"),
+            (RawSuffix::P, _) => write!(f, "P"),
+            (RawSuffix::E, _) => write!(f, "E"),
+            (RawSuffix::Z, _) => write!(f, "Z"),
+            (RawSuffix::Y, _) => write!(f, "Y"),
         }
         .and_then(|()| match with_i {
             true => write!(f, "i"),


Test patch:
diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs
index 7569465ea18..64ce538c760 100644
--- a/tests/by-util/test_numfmt.rs
+++ b/tests/by-util/test_numfmt.rs
@@ -84,7 +84,7 @@ fn test_to_si() {
         .args(&["--to=si"])
         .pipe_in("1000\n1100000\n100000000")
         .succeeds()
-        .stdout_is("1.0K\n1.1M\n100M\n");
+        .stdout_is("1.0k\n1.1M\n100M\n");
 }
 
 #[test]
@@ -243,15 +243,15 @@ fn test_suffixes() {
     // TODO add support for ronna (R) and quetta (Q)
     let valid_suffixes = ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' /*'R' , 'Q'*/];
 
-    // TODO implement special handling of 'K'
     for c in ('A'..='Z').chain('a'..='z') {
         let args = ["--from=si", "--to=si", &format!("1{c}")];
 
         if valid_suffixes.contains(&c) {
+            let s = if c == 'K' { 'k' } else { c };
             new_ucmd!()
                 .args(&args)
                 .succeeds()
-                .stdout_only(format!("1.0{c}\n"));
+                .stdout_only(format!("1.0{s}\n"));
         } else {
             new_ucmd!()
                 .args(&args)
@@ -504,7 +504,7 @@ fn test_delimiter_to_si() {
         .args(&["-d=,", "--to=si"])
         .pipe_in("1234,56")
         .succeeds()
-        .stdout_only("1.3K,56\n");
+        .stdout_only("1.3k,56\n");
 }
 
 #[test]
@@ -513,7 +513,7 @@ fn test_delimiter_skips_leading_whitespace() {
         .args(&["-d=,", "--to=si"])
         .pipe_in("     \t               1234,56")
         .succeeds()
-        .stdout_only("1.3K,56\n");
+        .stdout_only("1.3k,56\n");
 }
 
 #[test]
@@ -522,7 +522,7 @@ fn test_delimiter_preserves_leading_whitespace_in_unselected_fields() {
         .args(&["-d=|", "--to=si"])
         .pipe_in("             1000|   2000")
         .succeeds()
-        .stdout_only("1.0K|   2000\n");
+        .stdout_only("1.0k|   2000\n");
 }
 
 #[test]
@@ -550,7 +550,7 @@ fn test_delimiter_with_padding() {
         .args(&["-d=|", "--to=si", "--padding=5"])
         .pipe_in("1000|2000")
         .succeeds()
-        .stdout_only(" 1.0K|2000\n");
+        .stdout_only(" 1.0k|2000\n");
 }
 
 #[test]
@@ -559,21 +559,21 @@ fn test_delimiter_with_padding_and_fields() {
         .args(&["-d=|", "--to=si", "--padding=5", "--field=-"])
         .pipe_in("1000|2000")
         .succeeds()
-        .stdout_only(" 1.0K| 2.0K\n");
+        .stdout_only(" 1.0k| 2.0k\n");
 }
 
 #[test]
 fn test_round() {
     for (method, exp) in [
-        ("from-zero", ["9.1K", "-9.1K", "9.1K", "-9.1K"]),
-        ("from-zer", ["9.1K", "-9.1K", "9.1K", "-9.1K"]),
-        ("f", ["9.1K", "-9.1K", "9.1K", "-9.1K"]),
-        ("towards-zero", ["9.0K", "-9.0K", "9.0K", "-9.0K"]),
-        ("up", ["9.1K", "-9.0K", "9.1K", "-9.0K"]),
-        ("down", ["9.0K", "-9.1K", "9.0K", "-9.1K"]),
-        ("nearest", ["9.0K", "-9.0K", "9.1K", "-9.1K"]),
-        ("near", ["9.0K", "-9.0K", "9.1K", "-9.1K"]),
-        ("n", ["9.0K", "-9.0K", "9.1K", "-9.1K"]),
+        ("from-zero", ["9.1k", "-9.1k", "9.1k", "-9.1k"]),
+        ("from-zer", ["9.1k", "-9.1k", "9.1k", "-9.1k"]),
+        ("f", ["9.1k", "-9.1k", "9.1k", "-9.1k"]),
+        ("towards-zero", ["9.0k", "-9.0k", "9.0k", "-9.0k"]),
+        ("up", ["9.1k", "-9.0k", "9.1k", "-9.0k"]),
+        ("down", ["9.0k", "-9.1k", "9.0k", "-9.1k"]),
+        ("nearest", ["9.0k", "-9.0k", "9.1k", "-9.1k"]),
+        ("near", ["9.0k", "-9.0k", "9.1k", "-9.1k"]),
+        ("n", ["9.0k", "-9.0k", "9.1k", "-9.1k"]),
     ] {
         new_ucmd!()
             .args(&[
@@ -649,7 +649,7 @@ fn test_transform_with_suffix_on_input() {
         .args(&["--suffix=b", "--to=si"])
         .pipe_in("2000b")
         .succeeds()
-        .stdout_only("2.0Kb\n");
+        .stdout_only("2.0kb\n");
 }
 
 #[test]
@@ -658,7 +658,7 @@ fn test_transform_without_suffix_on_input() {
         .args(&["--suffix=b", "--to=si"])
         .pipe_in("2000")
         .succeeds()
-        .stdout_only("2.0Kb\n");
+        .stdout_only("2.0kb\n");
 }
 
 #[test]
@@ -667,7 +667,7 @@ fn test_transform_with_suffix_and_delimiter() {
         .args(&["--suffix=b", "--to=si", "-d=|"])
         .pipe_in("1000b|2000|3000")
         .succeeds()
-        .stdout_only("1.0Kb|2000|3000\n");
+        .stdout_only("1.0kb|2000|3000\n");
 }
 
 #[test]
diff --git a/tests/fixtures/numfmt/df_expected.txt b/tests/fixtures/numfmt/df_expected.txt
index ea8c3d79f4e..a3b5977616f 100644
--- a/tests/fixtures/numfmt/df_expected.txt
+++ b/tests/fixtures/numfmt/df_expected.txt
@@ -3,6 +3,6 @@ udev                           8.2G            0         8.2G   0% /dev
 tmpfs                          1.7G         2.1M         1.7G   1% /run
 /dev/nvme0n1p2                 1.1T         433G         523G  46% /
 tmpfs                          8.3G         145M         8.1G   2% /dev/shm
-tmpfs                          5.3M         4.1K         5.3M   1% /run/lock
+tmpfs                          5.3M         4.1k         5.3M   1% /run/lock
 tmpfs                          8.3G            0         8.3G   0% /sys/fs/cgroup
 /dev/nvme0n1p1                 536M         8.2M         528M   2% /boot/efi
diff --git a/tests/fixtures/numfmt/gnutest_si_result.txt b/tests/fixtures/numfmt/gnutest_si_result.txt
index 7238ba40c78..e62393d35b7 100644
--- a/tests/fixtures/numfmt/gnutest_si_result.txt
+++ b/tests/fixtures/numfmt/gnutest_si_result.txt
@@ -1,34 +1,34 @@
--1.1K
--1.0K
+-1.1k
+-1.0k
 -999
 1
 500
 999
-1.0K
-1.0K
-1.1K
-1.1K
-9.9K
-10K
-10K
-10K
-10K
-10K
-11K
-11K
-11K
-50K
-99K
-100K
-100K
-100K
-100K
-100K
-101K
-101K
-101K
-102K
-999K
+1.0k
+1.0k
+1.1k
+1.1k
+9.9k
+10k
+10k
+10k
+10k
+10k
+11k
+11k
+11k
+50k
+99k
+100k
+100k
+100k
+100k
+100k
+101k
+101k
+101k
+102k
+999k
 1.0M
 1.0M
 1.0M