Subject: Re: bug#47103: numfmt: invalid suffix 'k'
From: Pádraig Brady <[email-masked]>
To: Sven Köhler <[email-masked]>, [email-masked]
Date: Sun, 26 Nov 2023 16:59:36 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

On 26/11/2023 16:09, Sven Köhler wrote:
> So Pádraig's patch does allow for parsing lowercase k, but it does not
> change numfmt to use lowercase k in its output in si mode.
> 
> As Pádraig has shown, ls uses lowercase k in --si mode. So it uses
> lowercase k for 1000. I think that numfmt should behave the same for
> consistency reasons.

It does output lowercase 'k' in SI mode.

Attached is the full patch.

Marking this as done.

Will push this tomorrow.

thanks,
Pádraig

From 48b1b54d458897ebec98fe53139cb6b972ecc274 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Sun, 26 Nov 2023 16:41:56 +0000
Subject: [PATCH] numfmt: support lowercase 'k' for Kilo and Kibi

For consistency with the "SI" standard, and with other coreutils
which output a lowercase 'k' in "SI" mode.

* src/numfmt.c (suffix_power): Treat 'k' like 'K' on input.
(double_to_human): Output lowercase 'k' in SI mode.
(usage): Adjust accordingly.
* doc/coreutils.texi: Mention 'k' accepted, and printed in SI mode.
* tests/misc/numfmt.pl: Adjust accordingly.
* NEWS: Mention the change in behavior.
Fixes https://bugs.gnu.org/47103
---
 NEWS                 |   3 +
 doc/coreutils.texi   |  35 +++++----
 src/numfmt.c         |  11 ++-
 tests/misc/numfmt.pl | 170 +++++++++++++++++++++++--------------------
 4 files changed, 119 insertions(+), 100 deletions(-)

diff --git a/NEWS b/NEWS
index b1088f683..1a08076a4 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   ls --dired now implies long format output without hyperlinks enabled,
   and will take precedence over previously specified formats or hyperlink mode.
 
+  numfmt will accept lowercase 'k' to indicate Kilo or Kibi units on input,
+  and user lowercase 'k' when outputting such units in '--to=si' mode.
+
   wc no longer ignores encoding errors when counting words.
   Instead, it treats them as non white space.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 75a310fc8..7ad8c0db7 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -19109,7 +19109,7 @@ The default is no scaling, meaning all the digits of the number are printed.
 @opindex --to-unit
 Specify the output unit size (instead of the default 1).  Use this option when
 the output numbers represent other units (e.g. to represent @samp{4,000,000}
-bytes in blocks of 1KB, use @samp{--to=si --to-unit=1000}).
+bytes in blocks of 1kB, use @samp{--to=si --to-unit=1000}).
 Suffixes are handled as with @samp{--from=auto}.
 
 @optZeroTerminated
@@ -19137,7 +19137,8 @@ For output numbers, values larger than 1000 will be rounded, and printed with
 one of the following suffixes:
 
 @example
-@samp{K}  =>  @math{1000^1 = 10^3} (Kilo)
+@samp{K}  =>  @math{1000^1 = 10^3} (Kilo) (uppercase accepted on input)
+@samp{k}  =>  @math{1000^1 = 10^3} (Kilo) (lowercase used on output)
 @samp{M}  =>  @math{1000^2 = 10^6} (Mega)
 @samp{G}  =>  @math{1000^3 = 10^9} (Giga)
 @samp{T}  =>  @math{1000^4 = 10^{12}} (Tera)
@@ -19157,7 +19158,8 @@ For output numbers, values larger than 1024 will be rounded, and printed with
 one of the following suffixes:
 
 @example
-@samp{K}  =>  @math{1024^1 = 2^{10}} (Kibi)
+@samp{K}  =>  @math{1024^1 = 2^{10}} (Kibi) (uppercase used on output)
+@samp{k}  =>  @math{1024^1 = 2^{10}} (Kibi) (lowercase accepted on input)
 @samp{M}  =>  @math{1024^2 = 2^{20}} (Mebi)
 @samp{G}  =>  @math{1024^3 = 2^{30}} (Gibi)
 @samp{T}  =>  @math{1024^4 = 2^{40}} (Tebi)
@@ -19182,7 +19184,8 @@ For output numbers, values larger than 1024 will be rounded, and printed with
 one of the following suffixes:
 
 @example
-@samp{Ki}  =>  @math{1024^1 = 2^{10}} (Kibi)
+@samp{Ki}  =>  @math{1024^1 = 2^{10}} (Kibi) (uppercase used on output)
+@samp{ki}  =>  @math{1024^1 = 2^{10}} (Kibi) (lowercase accepted on input)
 @samp{Mi}  =>  @math{1024^2 = 2^{20}} (Mebi)
 @samp{Gi}  =>  @math{1024^3 = 2^{30}} (Gibi)
 @samp{Ti}  =>  @math{1024^4 = 2^{40}} (Tebi)
@@ -19212,7 +19215,7 @@ are interpreted as @emph{IEC} values.
 Converting a single number from/to @emph{human} representation:
 @example
 $ numfmt --to=si 500000
-500K
+500k
 
 $ numfmt --to=iec 500000
 489K
@@ -19258,9 +19261,9 @@ output sizes in human-readable format):
 @example
 # Third field (file size) will be shown in SI representation
 $ ls -log | numfmt --field 3 --header --to=si | head -n4
--rw-r--r--  1     94K Aug 23  2011 ABOUT-NLS
--rw-r--r--  1    3.7K Jan  7 16:15 AUTHORS
--rw-r--r--  1     36K Jun  1  2011 COPYING
+-rw-r--r--  1     94k Aug 23  2011 ABOUT-NLS
+-rw-r--r--  1    3.7k Jan  7 16:15 AUTHORS
+-rw-r--r--  1     36k Jun  1  2011 COPYING
 -rw-r--r--  1       0 Jan  7 15:15 ChangeLog
 
 # Second field (size) will be shown in IEC representation
@@ -19277,30 +19280,30 @@ Output can be tweaked using @option{--padding} or @option{--format}:
 @example
 # Pad to 10 characters, right-aligned
 $ du -s * | numfmt --to=si --padding=10
-      2.5K config.log
+      2.5k config.log
        108 config.status
-      1.7K configure
+      1.7k configure
         20 configure.ac
 
 # Pad to 10 characters, left-aligned
 $ du -s * | numfmt --to=si --padding=-10
-2.5K       config.log
+2.5k       config.log
 108        config.status
-1.7K       configure
+1.7k       configure
 20         configure.ac
 
 # Pad to 10 characters, left-aligned, using 'format'
 $ du -s * | numfmt --to=si --format="%10f"
-      2.5K config.log
+      2.5k config.log
        108 config.status
-      1.7K configure
+      1.7k configure
         20 configure.ac
 
 # Pad to 10 characters, left-aligned, using 'format'
 $ du -s * | numfmt --to=si --padding="%-10f"
-2.5K       config.log
+2.5k       config.log
 108        config.status
-1.7K       configure
+1.7k       configure
 20         configure.ac
 @end example
 
diff --git a/src/numfmt.c b/src/numfmt.c
index a5bdd2f4f..69ca8bd22 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -228,7 +228,7 @@ default_scale_base (enum scale_type scale)
     }
 }
 
-static char const zero_and_valid_suffixes[] = "0KMGTPEZYRQ";
+static char const zero_and_valid_suffixes[] = "0KkMGTPEZYRQ";
 static char const *valid_suffixes = 1 + zero_and_valid_suffixes;
 
 static inline bool
@@ -242,6 +242,7 @@ suffix_power (const char suf)
 {
   switch (suf)
     {
+    case 'k':                  /* kilo.  */
     case 'K':                  /* kilo or kibi.  */
       return 1;
 
@@ -811,7 +812,8 @@ double_to_human (long double val, int precision,
   int prec = user_precision == -1 ? show_decimal_point : user_precision;
 
   return snprintf (buf, buf_size, fmt, prec, val,
-                   suffix_power_char (power),
+                   power == 1 && scale == scale_SI
+                   ? "k" : suffix_power_char (power),
                    &"i"[! (scale == scale_IEC_I && 0 < power)],
                    suffix ? suffix : "");
 }
@@ -946,12 +948,13 @@ UNIT options:\n"), stdout);
       fputs (_("\
   auto       accept optional single/two letter suffix:\n\
                1K = 1000,\n\
+               1k = 1000,\n\
                1Ki = 1024,\n\
                1M = 1000000,\n\
                1Mi = 1048576,\n"), stdout);
       fputs (_("\
   si         accept optional single letter suffix:\n\
-               1K = 1000,\n\
+               1k = 1000,\n\
                1M = 1000000,\n\
                ...\n"), stdout);
       fputs (_("\
@@ -995,7 +998,7 @@ errors are not diagnosed and the exit status is 0.\n\
       printf (_("\n\
 Examples:\n\
   $ %s --to=si 1000\n\
-            -> \"1.0K\"\n\
+            -> \"1.0k\"\n\
   $ %s --to=iec 2048\n\
            -> \"2.0K\"\n\
   $ %s --to=iec-i 4096\n\
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index e111b3498..3047f806d 100755
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -48,16 +48,16 @@ my @Tests =
               {ERR => "$prog: invalid number: 'no_NL'\n"},
               {EXIT => '2'}],
 
-     ['8',  '--to=si 2000',                   {OUT => "2.0K"}],
-     ['9',  '--to=si 2001',                   {OUT => "2.1K"}],
-     ['10', '--to=si 1999',                   {OUT => "2.0K"}],
-     ['11', '--to=si --round=down   2001',   {OUT => "2.0K"}],
-     ['12', '--to=si --round=down   1999',   {OUT => "1.9K"}],
-     ['13', '--to=si --round=up 1901',   {OUT => "2.0K"}],
-     ['14', '--to=si --round=down   1901',   {OUT => "1.9K"}],
-     ['15', '--to=si --round=nearest 1901',   {OUT => "1.9K"}],
-     ['16', '--to=si --round=nearest 1945',   {OUT => "1.9K"}],
-     ['17', '--to=si --round=nearest 1955',   {OUT => "2.0K"}],
+     ['8',  '--to=si 2000',                   {OUT => "2.0k"}],
+     ['9',  '--to=si 2001',                   {OUT => "2.1k"}],
+     ['10', '--to=si 1999',                   {OUT => "2.0k"}],
+     ['11', '--to=si --round=down   2001',   {OUT => "2.0k"}],
+     ['12', '--to=si --round=down   1999',   {OUT => "1.9k"}],
+     ['13', '--to=si --round=up 1901',   {OUT => "2.0k"}],
+     ['14', '--to=si --round=down   1901',   {OUT => "1.9k"}],
+     ['15', '--to=si --round=nearest 1901',   {OUT => "1.9k"}],
+     ['16', '--to=si --round=nearest 1945',   {OUT => "1.9k"}],
+     ['17', '--to=si --round=nearest 1955',   {OUT => "2.0k"}],
 
      ['18',  '--to=iec 2048',                  {OUT => "2.0K"}],
      ['19',  '--to=iec 2049',                  {OUT => "2.1K"}],
@@ -149,7 +149,7 @@ my @Tests =
      ['suf-13', '--suffix=Foo 70',                  {OUT=>'70Foo'}],
      ['suf-14', '--suffix=Foo --from=si 70K',       {OUT=>'70000Foo'}],
      ['suf-15', '--suffix=Foo --from=si 70KFoo',    {OUT=>'70000Foo'}],
-     ['suf-16', '--suffix=Foo --to=si   7000Foo',    {OUT=>'7.0KFoo'}],
+     ['suf-16', '--suffix=Foo --to=si   7000Foo',    {OUT=>'7.0kFoo'}],
      ['suf-17', '--suffix=Foo --to=si   7000Bar',
               {ERR => "$prog: invalid suffix in input: '7000Bar'\n"},
               {EXIT => '2'}],
@@ -181,18 +181,18 @@ my @Tests =
      ['pad-3.1', '--padding=0 5',
              {ERR => "$prog: invalid padding value '0'\n"},
              {EXIT => '1'}],
-     ['pad-4', '--padding=10 --to=si 50000',             {OUT=>'       50K'}],
-     ['pad-5', '--padding=-10 --to=si 50000',            {OUT=>'50K       '}],
+     ['pad-4', '--padding=10 --to=si 50000',             {OUT=>'       50k'}],
+     ['pad-5', '--padding=-10 --to=si 50000',            {OUT=>'50k       '}],
 
      # padding too narrow
-     ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.0K'}],
+     ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.0k'}],

      # Padding + suffix
      ['pad-7', '--padding=10 --suffix=foo --to=si 50000',
-             {OUT=>'    50Kfoo'}],
+             {OUT=>'    50kfoo'}],
      ['pad-8', '--padding=-10 --suffix=foo --to=si 50000',
-             {OUT=>'50Kfoo    '}],
+             {OUT=>'50kfoo    '}],

      # Delimiters
@@ -231,63 +231,63 @@ my @Tests =
 
      # Multiple fields
      ['field-range-1', '--field 2,4 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1000 2.0K 3000 4.0K 5000"}],
+             {OUT=>"1000 2.0k 3000 4.0k 5000"}],
 
      ['field-range-2', '--field 2-4 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1000 2.0K 3.0K 4.0K 5000"}],
+             {OUT=>"1000 2.0k 3.0k 4.0k 5000"}],
 
      ['field-range-3', '--field 1,2,3-5 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5.0k"}],
 
      ['field-range-4', '--field 1-5 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5.0k"}],
 
      ['field-range-5', '--field 1-3,5 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4000 5.0K"}],
+             {OUT=>"1.0k 2.0k 3.0k 4000 5.0k"}],
 
      ['field-range-6', '--field 3- --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1000 2000 3.0K 4.0K 5.0K"}],
+             {OUT=>"1000 2000 3.0k 4.0k 5.0k"}],
 
      ['field-range-7', '--field -3 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4000 5000"}],
+             {OUT=>"1.0k 2.0k 3.0k 4000 5000"}],
 
      ['field-range-8', '--field 1-2,4-5 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3000 4.0k 5.0k"}],
      ['field-range-9', '--field 4-5,1-2 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3000 4.0k 5.0k"}],
 
      ['field-range-10','--field 1-3,2-4 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5000"}],
      ['field-range-11','--field 2-4,1-3 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5000"}],
 
      ['field-range-12','--field 1-1,3-3 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2000 3.0K 4000 5000"}],
+             {OUT=>"1.0k 2000 3.0k 4000 5000"}],
 
      ['field-range-13', '--field 1,-2 --to=si "1000 2000 3000"',
-             {OUT=>"1.0K 2.0K 3000"}],
+             {OUT=>"1.0k 2.0k 3000"}],
 
      ['field-range-14', '--field -2,4- --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3000 4.0k 5.0k"}],
      ['field-range-15', '--field -2,-4 --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5000"}],
      ['field-range-16', '--field 2-,4- --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1000 2.0K 3.0K 4.0K 5.0K"}],
+             {OUT=>"1000 2.0k 3.0k 4.0k 5.0k"}],
      ['field-range-17', '--field 4-,2- --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1000 2.0K 3.0K 4.0K 5.0K"}],
+             {OUT=>"1000 2.0k 3.0k 4.0k 5.0k"}],
 
      # white space are valid field separators
      # (undocumented? but works in cut as well).
      ['field-range-18', '--field "1,2 4" --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3000 4.0K 5000"}],
+             {OUT=>"1.0k 2.0k 3000 4.0k 5000"}],
 
      # Unlike 'cut', a lone '-' means 'all fields', even as part of a list
      # of fields.
      ['field-range-19','--field 3,- --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5.0k"}],
 
      ['all-fields-1', '--field=- --to=si "1000 2000 3000 4000 5000"',
-             {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+             {OUT=>"1.0k 2.0k 3.0k 4.0k 5.0k"}],
 
      ['field-range-err-1', '--field -foo --to=si 10',
              {EXIT=>1}, {ERR=>"$prog: invalid field value 'foo'\n$try"}],
@@ -317,9 +317,9 @@ my @Tests =
 
      # Auto-consume white-space, setup auto-padding
      ['whitespace-1', '--to=si --field 2 "A    500 B"', {OUT=>"A    500 B"}],
-     ['whitespace-2', '--to=si --field 2 "A   5000 B"', {OUT=>"A   5.0K B"}],
+     ['whitespace-2', '--to=si --field 2 "A   5000 B"', {OUT=>"A   5.0k B"}],
      ['whitespace-3', '--to=si "  500"', {OUT=>"  500"}],
-     ['whitespace-4', '--to=si " 6500"', {OUT=>" 6.5K"}],
+     ['whitespace-4', '--to=si " 6500"', {OUT=>" 6.5k"}],
      # NOTE: auto-padding is not enabled if the value is on the first
      #       field and there's no white-space before it.
      ['whitespace-5', '--to=si "6000000"', {OUT=>"6.0M"}],
@@ -331,14 +331,14 @@ my @Tests =
      ['whitespace-7', '--to=si --field 2',
              {IN_PIPE=>"rootfs    100000\n" .
                        "udevxx   2000000\n"},
-             {OUT    =>"rootfs      100K\n" .
+             {OUT    =>"rootfs      100k\n" .
                        "udevxx      2.0M"}],
      # auto-padding - second line requires a
      # larger padding (padding-buffer needs to be realloc'd)
      ['whitespace-8', '--to=si --field 2',
              {IN_PIPE=>"rootfs    100000\n" .
                        "udev         20000000\n"},
-             {OUT    =>"rootfs      100K\n" .
+             {OUT    =>"rootfs      100k\n" .
                        "udev              20M"}],

@@ -378,10 +378,10 @@ my @Tests =
 
      # The 'M' is treated as a delimiter, and so the input value is '4000'
      ['mix-13', '--delimiter=M --to=si --from=auto 4000M5000M9000',
-             {OUT=>"4.0KM5000M9000"}],
+             {OUT=>"4.0kM5000M9000"}],
      # 'M' is the delimiter, so the second input field is '5000'
      ['mix-14', '--delimiter=M --field 2 --from=auto --to=si 4000M5000M9000',
-             {OUT=>"4000M5.0KM9000"}],
+             {OUT=>"4000M5.0kM9000"}],

 
@@ -467,7 +467,7 @@ my @Tests =
      ['dbl-to-human-1','--to=si 800',  {OUT=>"800"}],
      ['dbl-to-human-2','--to=si 0',  {OUT=>"0"}],
      ['dbl-to-human-2.1','--to=si 999',  {OUT=>"999"}],
-     ['dbl-to-human-2.2','--to=si 1000',  {OUT=>"1.0K"}],
+     ['dbl-to-human-2.2','--to=si 1000',  {OUT=>"1.0k"}],
      #NOTE: the following are consistent with "ls -lh" output
      ['dbl-to-human-2.3','--to=iec 999',  {OUT=>"999"}],
      ['dbl-to-human-2.4','--to=iec 1023',  {OUT=>"1023"}],
@@ -478,28 +478,28 @@ my @Tests =
      ['dbl-to-human-2.8','--to=iec-i 0',  {OUT=>"0"}],
 
      # values resulting in "N.Nx" output
-     ['dbl-to-human-3','--to=si 8000', {OUT=>"8.0K"}],
-     ['dbl-to-human-3.1','--to=si 8001', {OUT=>"8.1K"}],
-     ['dbl-to-human-4','--to=si --round=down 8001', {OUT=>"8.0K"}],
+     ['dbl-to-human-3','--to=si 8000', {OUT=>"8.0k"}],
+     ['dbl-to-human-3.1','--to=si 8001', {OUT=>"8.1k"}],
+     ['dbl-to-human-4','--to=si --round=down 8001', {OUT=>"8.0k"}],
 
-     ['dbl-to-human-5','--to=si --round=down 3500', {OUT=>"3.5K"}],
-     ['dbl-to-human-6','--to=si --round=nearest 3500', {OUT=>"3.5K"}],
-     ['dbl-to-human-7','--to=si --round=up 3500', {OUT=>"3.5K"}],
+     ['dbl-to-human-5','--to=si --round=down 3500', {OUT=>"3.5k"}],
+     ['dbl-to-human-6','--to=si --round=nearest 3500', {OUT=>"3.5k"}],
+     ['dbl-to-human-7','--to=si --round=up 3500', {OUT=>"3.5k"}],
 
-     ['dbl-to-human-8','--to=si --round=down    3501', {OUT=>"3.5K"}],
-     ['dbl-to-human-9','--to=si --round=nearest  3501', {OUT=>"3.5K"}],
-     ['dbl-to-human-10','--to=si --round=up 3501', {OUT=>"3.6K"}],
+     ['dbl-to-human-8','--to=si --round=down    3501', {OUT=>"3.5k"}],
+     ['dbl-to-human-9','--to=si --round=nearest  3501', {OUT=>"3.5k"}],
+     ['dbl-to-human-10','--to=si --round=up 3501', {OUT=>"3.6k"}],
 
-     ['dbl-to-human-11','--to=si --round=nearest  3550', {OUT=>"3.6K"}],
+     ['dbl-to-human-11','--to=si --round=nearest  3550', {OUT=>"3.6k"}],
      ['dbl-to-human-12','--to=si --from=si 999.89K', {OUT=>"1.0M"}],
-     ['dbl-to-human-13','--to=si --from=si 9.9K', {OUT=>"9.9K"}],
-     ['dbl-to-human-14','--to=si 9900', {OUT=>"9.9K"}],
+     ['dbl-to-human-13','--to=si --from=si 9.9K', {OUT=>"9.9k"}],
+     ['dbl-to-human-14','--to=si 9900', {OUT=>"9.9k"}],
      ['dbl-to-human-15','--to=iec --from=si 3.3K', {OUT=>"3.3K"}],
      ['dbl-to-human-16','--to=iec --round=down --from=si 3.3K', {OUT=>"3.2K"}],
 
      # values resulting in 'NNx' output
-     ['dbl-to-human-17','--to=si 9999', {OUT=>"10K"}],
-     ['dbl-to-human-18','--to=si --round=down 35000', {OUT=>"35K"}],
+     ['dbl-to-human-17','--to=si 9999', {OUT=>"10k"}],
+     ['dbl-to-human-18','--to=si --round=down 35000', {OUT=>"35k"}],
      ['dbl-to-human-19','--to=iec 35000', {OUT=>"35K"}],
      ['dbl-to-human-20','--to=iec --round=down 35000', {OUT=>"34K"}],
      ['dbl-to-human-21','--to=iec 35000000', {OUT=>"34M"}],
@@ -513,9 +513,9 @@ my @Tests =
      ['dbl-to-human-26','--to=si 999000000000', {OUT=>"999G"}],
      ['dbl-to-human-27','--to=iec 999000000000', {OUT=>"931G"}],
      ['dbl-to-human-28','--to=si 123600000000000', {OUT=>"124T"}],
-     ['dbl-to-human-29','--to=si 998123', {OUT=>"999K"}],
-     ['dbl-to-human-30','--to=si --round=nearest 998123', {OUT=>"998K"}],
-     ['dbl-to-human-31','--to=si 99999', {OUT=>"100K"}],
+     ['dbl-to-human-29','--to=si 998123', {OUT=>"999k"}],
+     ['dbl-to-human-30','--to=si --round=nearest 998123', {OUT=>"998k"}],
+     ['dbl-to-human-31','--to=si 99999', {OUT=>"100k"}],
      ['dbl-to-human-32','--to=iec 102399', {OUT=>"100K"}],
      ['dbl-to-human-33','--to=iec-i 102399', {OUT=>"100Ki"}],
 
@@ -710,7 +710,7 @@ my @Tests =
 
      # Very large format strings
      ['fmt-15', '--format "--%100000f--" --to=si 4200',
-                  {OUT=>"--" . " " x 99996 . "4.2K--" }],
+                  {OUT=>"--" . " " x 99996 . "4.2k--" }],
 
      # --format padding overrides --padding
      ['fmt-16', '--format="%6f" --padding=66 1234',{OUT=>"  1234"}],
@@ -765,28 +765,28 @@ my @Tests =
 
      ## Ignore Errors with multiple conversions
      ['ign-err-m1', '--invalid=ignore --to=si 1000 2000 bad 3000',
-             {OUT => "1.0K\n2.0K\nbad\n3.0K"},
+             {OUT => "1.0k\n2.0k\nbad\n3.0k"},
              {EXIT => 0}],
      ['ign-err-m1.1', '--invalid=ignore --to=si',
              {IN_PIPE => "1000\n2000\nbad\n3000\n"},
-             {OUT => "1.0K\n2.0K\nbad\n3.0K"},
+             {OUT => "1.0k\n2.0k\nbad\n3.0k"},
              {EXIT => 0}],
      ['ign-err-m1.3', '--invalid=fail --debug --to=si 1000 2000 3000',
-             {OUT => "1.0K\n2.0K\n3.0K"},
+             {OUT => "1.0k\n2.0k\n3.0k"},
              {EXIT => 0}],
      ['ign-err-m2', '--invalid=fail --to=si 1000 Foo 3000',
-             {OUT => "1.0K\nFoo\n3.0K\n"},
+             {OUT => "1.0k\nFoo\n3.0k\n"},
              {ERR => "$prog: invalid number: 'Foo'\n"},
              {EXIT => 2}],
      ['ign-err-m2.1', '--invalid=warn --to=si',
              {IN_PIPE => "1000\nFoo\n3000\n"},
-             {OUT => "1.0K\nFoo\n3.0K"},
+             {OUT => "1.0k\nFoo\n3.0k"},
              {ERR => "$prog: invalid number: 'Foo'\n"},
              {EXIT => 0}],
 
      # --debug will trigger a final warning at EOF
      ['ign-err-m2.2', '--invalid=fail --debug --to=si 1000 Foo 3000',
-             {OUT => "1.0K\nFoo\n3.0K\n"},
+             {OUT => "1.0k\nFoo\n3.0k\n"},
              {ERR => "$prog: invalid number: 'Foo'\n" .
                      "$prog: failed to convert some of the input numbers\n"},
              {EXIT => 2}],
@@ -818,15 +818,15 @@ my @NullDelim_Tests =
      ['z4', '-z --field=3 --to=si',
              {IN_PIPE => "A B 1001 C\x00" .
                          "D E 2002 F\x00"},
-             {OUT => "A B 1.1K C\x00" .
-                     "D E 2.1K F\x00"}],
+             {OUT => "A B 1.1k C\x00" .
+                     "D E 2.1k F\x00"}],
 
      # Input from STDIN, with fields and embedded NL
      ['z5', '-z --field=3 --to=si',
              {IN_PIPE => "A\nB 1001 C\x00" .
                          "D E\n2002 F\x00"},
-             {OUT => "A B 1.1K C\x00" .
-                     "D E 2.1K F\x00"}],
+             {OUT => "A B 1.1k C\x00" .
+                     "D E 2.1k F\x00"}],
   );
 
 my @Limit_Tests =
@@ -851,9 +851,9 @@ my @Limit_Tests =
      ['large-3.1', '--to=si                           1', {OUT=>   "1"}],
      ['large-3.2', '--to=si                          10', {OUT=>  "10"}],
      ['large-3.3', '--to=si                         100', {OUT=> "100"}],
-     ['large-3.4', '--to=si                        1000', {OUT=>"1.0K"}],
-     ['large-3.5', '--to=si                       10000', {OUT=> "10K"}],
-     ['large-3.6', '--to=si                      100000', {OUT=>"100K"}],
+     ['large-3.4', '--to=si                        1000', {OUT=>"1.0k"}],
+     ['large-3.5', '--to=si                       10000', {OUT=> "10k"}],
+     ['large-3.6', '--to=si                      100000', {OUT=>"100k"}],
      ['large-3.7', '--to=si                     1000000', {OUT=>"1.0M"}],
      ['large-3.8', '--to=si                    10000000', {OUT=> "10M"}],
      ['large-3.9', '--to=si                   100000000', {OUT=>"100M"}],
@@ -1069,18 +1069,28 @@ push @Tests, @Locale_Tests if $locale ne 'C';
 
 ## Check all valid/invalid suffixes
 foreach my $suf ( 'A' .. 'Z', 'a' .. 'z' ) {
-  if ( $suf =~ /^[KMGTPEZYRQ]$/ )
+  if ( $suf =~ /^[KkMGTPEZYRQ]$/ )
     {
+      my $si_suf = $suf;
+      my $iec_suf = $suf;
+      if ( $suf eq "k" )
+        {
+          $iec_suf = "K";
+        }
+      if ( $suf eq "K" )
+        {
+          $si_suf = "k";
+        }
       push @Tests, ["auto-suf-si-$suf","--from=si --to=si 1$suf",
-              {OUT=>"1.0$suf"}];
+              {OUT=>"1.0$si_suf"}];
       push @Tests, ["auto-suf-iec-$suf","--from=iec --to=iec 1$suf",
-              {OUT=>"1.0$suf"}];
+              {OUT=>"1.0$iec_suf"}];
       push @Tests, ["auto-suf-auto-$suf","--from=auto --to=iec 1${suf}i",
-              {OUT=>"1.0$suf"}];
+              {OUT=>"1.0$iec_suf"}];
       push @Tests, ["auto-suf-iec-to-ieci-$suf","--from=iec --to=iec-i 1${suf}",
-              {OUT=>"1.0${suf}i"}];
+              {OUT=>"1.0${iec_suf}i"}];
       push @Tests, ["auto-suf-ieci-to-iec-$suf",
-              "--from=iec-i --to=iec 1${suf}i",{OUT=>"1.0${suf}"}];
+              "--from=iec-i --to=iec 1${suf}i",{OUT=>"1.0${iec_suf}"}];
     }
   else
     {
-- 
2.41.0