From ea9b95231bdd933e44a1d0681d31c9523ef763cb Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 6 Dec 2012 22:30:23 +0000
Subject: [PATCH 01/16] numfmt: a new command to format numbers

* AUTHORS: Add my name.
* NEWS: Mention the new program.
* README: Reference the new program.
* build-aux/gen-lists-of-programs.sh
* scripts/git-hooks/commit-msg: Allow numfmt: commit prefix.
* man/local.mk
* man/numfmt.x
* po/POTFILES.in
* src/.gitignore
* man/.gitignore
* src/numfmt.c
* tests/local.mk
* tests/misc/numfmt.pl: A new test.
---
 AUTHORS                            |    1 +
 NEWS                               |    4 +
 README                             |    6 +-
 build-aux/gen-lists-of-programs.sh |    1 +
 man/.gitignore                     |    1 +
 man/local.mk                       |    1 +
 man/numfmt.x                       |    4 +
 po/POTFILES.in                     |    1 +
 scripts/git-hooks/commit-msg       |    2 +-
 src/.gitignore                     |    1 +
 src/numfmt.c                       |  779 ++++++++++++++++++++++++++++++++++++
 tests/local.mk                     |    1 +
 tests/misc/numfmt.pl               |  242 +++++++++++
 13 files changed, 1040 insertions(+), 4 deletions(-)
 create mode 100644 man/numfmt.x
 create mode 100644 src/numfmt.c
 create mode 100644 tests/misc/numfmt.pl

diff --git a/AUTHORS b/AUTHORS
index 552e9d4..df21e90 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -52,6 +52,7 @@ nice: David MacKenzie
 nl: Scott Bartram, David MacKenzie
 nohup: Jim Meyering
 nproc: Giuseppe Scrivano
+numfmt: Assaf Gordon
 od: Jim Meyering
 paste: David M. Ihnat, David MacKenzie
 pathchk: Paul Eggert, David MacKenzie, Jim Meyering
diff --git a/NEWS b/NEWS
index e22d3a9..46448fb 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release ?.? (????-??-??) [?]
 
+** New programs
+
+  numfmt: reformat numbers
+
 ** New features
 
   df now accepts the --output[=FIELD_LIST] option to define the list of columns
diff --git a/README b/README
index 6d8aa14..f093a9f 100644
--- a/README
+++ b/README
@@ -11,9 +11,9 @@ The programs that can be built with this package are:
   csplit cut date dd df dir dircolors dirname du echo env expand expr
   factor false fmt fold groups head hostid hostname id install join kill
   link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
-  nproc od paste pathchk pinky pr printenv printf ptx pwd readlink realpath
-  rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred
-  shuf sleep sort split stat stdbuf stty sum sync tac tail tee test
+  nproc numfmt od paste pathchk pinky pr printenv printf ptx pwd readlink
+  realpath rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum
+  shred shuf sleep sort split stat stdbuf stty sum sync tac tail tee test
   timeout touch tr true truncate tsort tty uname unexpand uniq unlink
   uptime users vdir wc who whoami yes
 
diff --git a/build-aux/gen-lists-of-programs.sh b/build-aux/gen-lists-of-programs.sh
index 212ce02..bf63ee3 100755
--- a/build-aux/gen-lists-of-programs.sh
+++ b/build-aux/gen-lists-of-programs.sh
@@ -85,6 +85,7 @@ normal_progs='
     nl
     nproc
     nohup
+    numfmt
     od
     paste
     pathchk
diff --git a/man/.gitignore b/man/.gitignore
index aa6fa51..aef4002 100644
--- a/man/.gitignore
+++ b/man/.gitignore
@@ -49,6 +49,7 @@ nice.1
 nl.1
 nohup.1
 nproc.1
+numfmt.1
 od.1
 paste.1
 pathchk.1
diff --git a/man/local.mk b/man/local.mk
index 2c05bb6..7613bcc 100644
--- a/man/local.mk
+++ b/man/local.mk
@@ -115,6 +115,7 @@ man/nice.1:      src/nice
 man/nl.1:        src/nl
 man/nohup.1:     src/nohup
 man/nproc.1:     src/nproc
+man/numfmt.1:    src/numfmt
 man/od.1:        src/od
 man/paste.1:     src/paste
 man/pathchk.1:   src/pathchk
diff --git a/man/numfmt.x b/man/numfmt.x
new file mode 100644
index 0000000..6deaff1
--- /dev/null
+++ b/man/numfmt.x
@@ -0,0 +1,4 @@
+[NAME]
+numfmt \- Convert numbers from/to human-readable strings
+[DESCRIPTION]
+.\" Add any additional description here
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b938dfc..d134899 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -91,6 +91,7 @@ src/nice.c
 src/nl.c
 src/nohup.c
 src/nproc.c
+src/numfmt.c
 src/od.c
 src/operand2sig.c
 src/paste.c
diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg
index 56286d4..1732103 100755
--- a/scripts/git-hooks/commit-msg
+++ b/scripts/git-hooks/commit-msg
@@ -16,7 +16,7 @@ my @valid = qw(
     cp csplit cut date dd df dir dircolors dirname du echo env expand
     expr factor false fmt fold groups head hostid hostname id install
     join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp
-    mv nice nl nohup nproc od paste pathchk pinky pr printenv printf
+    mv nice nl nohup nproc numfmt od paste pathchk pinky pr printenv printf
     ptx pwd readlink realpath rm rmdir runcon seq sha1sum sha224sum sha256sum
     sha384sum sha512sum shred shuf sleep sort split stat stdbuf stty
     sum sync tac tail tee test timeout touch tr true truncate tsort
diff --git a/src/.gitignore b/src/.gitignore
index 18cccc1..25573df 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -59,6 +59,7 @@ nice
 nl
 nohup
 nproc
+numfmt
 od
 paste
 pathchk
diff --git a/src/numfmt.c b/src/numfmt.c
new file mode 100644
index 0000000..44675f9
--- /dev/null
+++ b/src/numfmt.c
@@ -0,0 +1,779 @@
+/* Reformat numbers like 11505426432 to the more human-readable 11G
+   Copyright (C) 2012 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "mbsalign.h"
+#include "argmatch.h"
+#include "error.h"
+#include "system.h"
+#include "xstrtol.h"
+#include "human.h"
+
+/* The official name of this program (e.g., no 'g' prefix).  */
+#define PROGRAM_NAME "numfmt"
+
+#define AUTHORS proper_name ("Assaf Gordon")
+
+#define BUFFER_SIZE (16 * 1024)
+
+enum
+{
+  FROM_OPTION = CHAR_MAX + 1,
+  FROM_UNIT_OPTION,
+  TO_OPTION,
+  TO_UNIT_OPTION,
+  ROUND_OPTION,
+  SUFFIX_OPTION,
+  GROUPING_OPTION,
+  PADDING_OPTION,
+  FIELD_OPTION,
+  DEBUG_OPTION
+};
+
+enum scale_type
+{
+scale_none, /* the default: no scaling */
+scale_auto, /* --from only */
+scale_SI,
+scale_IEC
+};
+
+static char const *const scale_from_args[] =
+{
+"none", "auto", "SI", "IEC", NULL
+};
+static enum scale_type const scale_from_types[] =
+{
+scale_none, scale_auto, scale_SI, scale_IEC
+};
+
+static char const *const scale_to_args[] =
+{
+"none", "SI", "IEC", NULL
+};
+static enum scale_type const scale_to_types[] =
+{
+scale_none, scale_SI, scale_IEC
+};
+
+
+enum round_type
+{
+round_ceiling,
+round_floor,
+round_nearest
+};
+
+static char const *const round_args[] =
+{
+"ceiling","floor","nearest", NULL
+};
+
+static enum round_type const round_types[] =
+{
+round_ceiling,round_floor,round_nearest
+};
+
+static struct option const longopts[] =
+{
+  {"from", required_argument, NULL, FROM_OPTION},
+  {"from-unit", required_argument, NULL, FROM_UNIT_OPTION},
+  {"to", required_argument, NULL, TO_OPTION},
+  {"to-unit", required_argument, NULL, TO_UNIT_OPTION},
+  {"round", required_argument, NULL, ROUND_OPTION},
+  {"padding", required_argument, NULL, PADDING_OPTION},
+  {"suffix", required_argument, NULL, SUFFIX_OPTION},
+  {"grouping", no_argument, NULL, GROUPING_OPTION},
+  {"delimiter", required_argument, NULL, 'd'},
+  {"field", required_argument, NULL, FIELD_OPTION},
+  {"debug", no_argument,NULL,DEBUG_OPTION},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
+/* If delimtier has this value, blanks separate fields.  */
+enum { DELIMITER_DEFAULT = CHAR_MAX + 1 };
+
+static enum scale_type scale_from=scale_none;
+static enum scale_type scale_to=scale_none;
+static enum round_type _round=round_ceiling;
+static const char *suffix = NULL;
+static uintmax_t from_unit_size=1;
+static uintmax_t to_unit_size=1;
+static int human_print_options=0; /* see enum in 'human.c' */
+static int grouping=0;
+static char *padding_buffer=NULL;
+static long int padding_width=0;
+static mbs_align_t padding_alignment=MBS_ALIGN_RIGHT;
+static long int field=1;
+static int debug=0;
+static int delimiter = DELIMITER_DEFAULT;
+
+
+/*
+   Converts a string to a long-long-int, optionally handling suffixes.
+   Mostly copied from <gnulib>/lib/xstdtol.c, with modified functionality.
+ */
+
+//The following #defines are copied from "xstrtoumax.c"
+#define __strtol strtoumax
+#define __strtol_t uintmax_t
+#define STRTOL_T_MINIMUM 0
+#define STRTOL_T_MAXIMUM UINTMAX_MAX
+
+static strtol_error
+bkm_scale (__strtol_t *x, int scale_factor)
+{
+  if (TYPE_SIGNED (__strtol_t) && *x < STRTOL_T_MINIMUM / scale_factor)
+    {
+      *x = STRTOL_T_MINIMUM;
+      return LONGINT_OVERFLOW;
+    }
+  if (STRTOL_T_MAXIMUM / scale_factor < *x)
+    {
+      *x = STRTOL_T_MAXIMUM;
+      return LONGINT_OVERFLOW;
+    }
+  *x *= scale_factor;
+  return LONGINT_OK;
+}
+
+static strtol_error
+bkm_scale_by_power (__strtol_t *x, int base, int power)
+{
+  strtol_error err = LONGINT_OK;
+  while (power--)
+    err |= bkm_scale (x, base);
+  return err;
+}
+
+static inline int
+valid_suffix ( const char suf )
+{
+  static const char *valid_suffixes="kKMGTPEZY";
+  return (strchr (valid_suffixes,suf)!=NULL);
+}
+
+static inline int
+suffix_power ( const char suf )
+{
+  switch (suf)
+    {
+    case 'k': /* special case: lower case 'k' is acceptable */
+    case 'K': /* kilo/kibi */
+      return 1;
+
+    case 'M': /* mega or mebi */
+      return 2;
+
+    case 'G': /* giga or gibi */
+      return 3;
+
+    case 'T': /* tera or tebi */
+      return 4;
+
+    case 'P': /* peta or pebi */
+      return 5;
+
+    case 'E': /* exa or exbi */
+      return 6;
+
+    case 'Z': /* zetta or 2**70 */
+      return 7;
+
+    case 'Y': /* yotta or 2**80 */
+      return 8;
+
+    default: /* should never happen. assert? */
+      return 0;
+    }
+}
+
+static strtol_error
+human_xstrtol (const char *s, char **ptr,
+           __strtol_t *val, enum scale_type scaling)
+{
+  char *t_ptr;
+  char **p;
+  __strtol_t tmp;
+  strtol_error err = LONGINT_OK;
+  int base=(scaling==scale_IEC)?1024:1000; /* 'auto' is checked below */
+  int overflow=0;
+  int skip=1;
+  const int strtol_base = 10 ; //TODO: allow user-changable base?
+
+
+  p = (ptr ? ptr : &t_ptr);
+
+  errno = 0;
+  tmp = __strtol (s, p, strtol_base);
+
+  if (*p == s)
+    {
+      /* No digits found */
+        return LONGINT_INVALID;
+    }
+  else if (errno != 0)
+    {
+      if (errno != ERANGE)
+        return LONGINT_INVALID;
+      err = LONGINT_OVERFLOW;
+    }
+
+  if (**p == '\0' || scaling==scale_none || !valid_suffix (**p))
+    {
+      /* no suffixes after the number, or no scaling requested,
+         or no valid suffixes found */
+      *val = tmp;
+      return err;
+    }
+
+  if (scaling==scale_auto && (p[0][1]=='i'))
+    {
+      /* auto-scaling enabled, and the first suffix character
+         is followed by an 'i' (e.g. Ki, Mi, Gi) */
+      base = 1024 ;
+      ++skip;
+    }
+
+  overflow = bkm_scale_by_power (&tmp, base, suffix_power (**p));
+
+  err |= overflow;
+
+  *p += skip; /* skip the suffix characters */
+
+  *val = tmp;
+  return err;
+}
+
+static void
+human_xstrtol_fatal (enum strtol_error err,
+                     char const *input_str)
+{
+  char const *msgid;
+
+  switch (err)
+    {
+    default:
+      abort ();
+
+    case LONGINT_INVALID:
+      msgid = N_("invalid input number '%s'");
+      break;
+
+    case LONGINT_OVERFLOW:
+      msgid = N_("input value too large '%s'");
+      break;
+    }
+  error (EXIT_FAILURE,0,gettext (msgid), input_str);
+}
+
+/* Convert a string of decimal digits, N_STRING, with an optional suffinx
+   to an integral value.  Upon successful conversion,
+   return that value.  If it cannot be converted, give a diagnostic and exit.
+*/
+static uintmax_t
+string_to_integer (const char *n_string)
+{
+  strtol_error s_err;
+  uintmax_t n;
+
+  s_err = xstrtoumax (n_string, NULL, 10, &n, "bkKmMGTPEZY0");
+
+  if (s_err == LONGINT_OVERFLOW)
+    {
+      error (EXIT_FAILURE, 0,
+             _("%s: unit size is so large that it is not representable"),
+                n_string);
+    }
+
+  if (s_err != LONGINT_OK)
+    {
+      error (EXIT_FAILURE, 0, _("%s: invalid unit size"), n_string);
+    }
+  return n;
+}
+
+
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    emit_try_help ();
+  else
+    {
+      printf (_("\
+Usage: %s [OPTIONS] [NUMBER]\n\
+"),
+          program_name);
+      fputs (_("\
+Reformats NUMBER(s) to/from human-readable values.\n\
+Numbers can be processed either from stdin or command arguments.\n\
+\n\
+"), stdout);
+      fputs (_("\
+  --from=UNIT     auto-scale input numbers to UNITs. Default is 'none'.\n\
+                  See UNIT below.\n\
+  --from-unit=N   specify the input unit size (instead of the default 1).\n\
+  --to=UNIT       auto-scale output numbers to UNITs.\n\
+                  See UNIT below.\n\
+  --to-unit=N     specify the output unit size (instead of the default 1).\n\
+  --round=METHOD  round input numbers. METHOD can be:\n\
+                  ceiling (the default), floor, nearest\n\
+  --suffix=SUFFIX add SUFFIX to output numbers, and accept optional SUFFIX\n\
+                  in input numbers.\n\
+  --padding=N     pad the output to N characters.\n\
+                  Default is right-aligned. Negative N will left-align.\n\
+                  Note: if N is too small, the output will be truncated,\n\
+                  and a warning will be printed to stderr.\n\
+  --grouping      group digits together (e.g. 1,000,000).\n\
+                  Uses the locale-defined grouping (i.e. have no effect\n\
+                  in C/POSIX locales).\n\
+  --field N       replace the number in input field N (default is 1)\n\
+  -d, --delimiter=X  use X instead of whitespace for field delimiter\n\
+  \n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+
+      fputs (_("\
+\n\
+UNIT options:\n\
+ none:\n\
+      No auto-scaling is done. Suffixes will trigger an error.\n\
+ auto ('--from' only):\n\
+      1K  = 1000\n\
+      1Ki = 1024\n\
+      1G  = 1000000\n\
+      1Gi = 1048576\n\
+ SI:\n\
+      1K = 1000\n\
+      1G  = 1000000\n\
+      ...\n\
+ IEC:\n\
+      1K = 1024\n\
+      1G = 1048576\n\
+      ...\n\
+\n\
+"), stdout);
+
+      printf (_("\
+\n\
+Examples:\n\
+  %s --to=SI 1000           -> \"1K\"\n\
+  echo 1K | %s --from=SI    -> \"1000\"\n\
+  echo 1K | %s --from=IEC   -> \"1024\"\n\
+"),
+              program_name, program_name, program_name);
+      emit_ancillary_info ();
+    }
+  exit (status);
+}
+
+/* Finds newline chacaters in a string, and replaces them with NULL */
+static inline void
+chomp (char *s)
+{
+  while (*s != 0) {
+      if (*s=='\n' || *s=='\r') {
+          *s = 0 ;
+          return;
+      }
+      ++s;
+  }
+  return ;
+}
+
+
+/*
+   Parsesa numeric value from a string.
+   Returns the integer value.
+
+   On error, exits with a proper error message.
+
+   The input string must not contain any extra characters
+   (except valid suffixes if 'from' scaling is enabled).
+*/
+static __strtol_t
+parse_number (const char* str)
+{
+  char *ptr;
+  __strtol_t val=0;
+
+  strtol_error err = human_xstrtol (str,&ptr,&val,scale_from);
+  if (err != LONGINT_OK)
+    human_xstrtol_fatal (err, str);
+
+  if (debug)
+    fprintf (stderr,"Parsing number:\n  input string: '%s'\n  " \
+            "remaining characters: '%s'\n  numeric value: %zu\n",
+            str,ptr,val);
+
+  if ( *ptr!='\0' )
+    {
+      if ((strlen (ptr)==1) && valid_suffix (*ptr))
+        {
+          /* This is a potentially valid suffix, so try to be helpful */
+         error (EXIT_FAILURE,0,_("not accepting suffix '%s' in input '%s'" \
+                               " (consider using --from)"), ptr, str);
+        }
+      else
+        {
+         /* Invalid trailing characters */
+         error (EXIT_FAILURE,0,_("invalid suffix in input '%s': '%s'"),
+                str, ptr);
+        }
+    }
+  return val;
+}
+
+
+/*
+   Prints the given number, using the requested representation.
+   The number is printed to STDOUT,
+   with padding and alignment.
+*/
+static void
+print_number (const __strtol_t val)
+{
+  /* Generate Output */
+  char buf[LONGEST_HUMAN_READABLE + 1];
+  char *s = human_readable (val, buf, human_print_options,
+                           from_unit_size,to_unit_size);
+
+  if (debug)
+    fprintf (stderr,"Formatting output:\n  value: %zu\n  humanized: '%s'\n",
+            val,s);
+
+  if (padding_width)
+    {
+      size_t w = padding_width;
+      mbsalign (s,padding_buffer,padding_width+1,&w,
+               padding_alignment,  MBA_UNIBYTE_ONLY );
+      if (strlen (padding_buffer)<strlen (s))
+        error (0,0,_("value '%s' truncated due to padding, possible data loss"),
+              s);
+
+      if (debug)
+        fprintf (stderr,"  After padding: '%s'\n", padding_buffer);
+
+      fputs (padding_buffer,stdout);
+    }
+  else
+    {
+      fputs (s,stdout);
+    }
+}
+
+/*
+   Converts the given number to the requested representation,
+   and handles automatic suffix addition.
+ */
+static void
+process_suffixed_number (char* text)
+{
+  if (suffix && strlen (text)>strlen (suffix))
+    {
+      char *possible_suffix = text+strlen (text)-strlen (suffix);
+
+      if (STREQ (suffix,possible_suffix))
+        {
+          /* trim suffix, ONLY if it's at the end of the text */
+          *possible_suffix='\0';
+          if (debug)
+            fprintf (stderr,"Trimming suffix '%s'\n", suffix);
+        }
+      else
+        {
+          if (debug)
+            fprintf (stderr,"No valid suffix found\n");
+        }
+    }
+
+  const __strtol_t val = parse_number (text);
+  print_number (val);
+
+  if (suffix)
+    fputs (suffix,stdout);
+}
+
+/*
+   Skips the requested number of fields in the input string.
+
+   Returns a pointer to the *delimiter* of the requested field,
+   or a pointer to NULL (if reached the end of the string)
+ */
+static inline char*
+__attribute ((pure))
+skip_fields (char* buf, int fields)
+{
+  char* ptr = buf;
+  if (delimiter != DELIMITER_DEFAULT)
+    while (*ptr && fields--)
+      {
+        while (*ptr && *ptr == delimiter)
+          ++ptr;
+        while (*ptr && *ptr != delimiter)
+          ++ptr;
+      }
+  else
+    while (*ptr && fields--)
+      {
+        while (*ptr && isblank (*ptr))
+          ++ptr;
+        while (*ptr && !isblank (*ptr))
+          ++ptr;
+      }
+  return ptr;
+}
+
+/*
+   Parses a delimited string, and extracts the requested field.
+   NOTE: the input buffer is modified.
+
+   Output:
+     _prefix, _data, _suffix will point to the relevant positions
+     in the input string, or be NULL if not such part exist.
+ */
+static void
+extract_fields (char* line, int _field,
+                char** /*out*/ _prefix,
+                char** /*out*/ _data,
+                char** /*out*/ _suffix)
+{
+  char* ptr = line ;
+  *_prefix = NULL;
+  *_data = NULL ;
+  *_suffix = NULL ;
+
+  if (debug)
+    fprintf (stderr,"Extracting Fields:\n  input: '%s'\n  field: %d\n",
+        line, _field);
+
+  if (field>1)
+    {
+      /* skip the requested number of fields */
+      *_prefix = line;
+      ptr = skip_fields (line, field-1);
+      if (*ptr=='\0')
+        {
+          /* not enough fields in the input - print warning? */
+          if (debug)
+            fprintf (stderr,"  TOO FEW FIELDS!\n  prefix: '%s'\n",
+                *_prefix);
+          return ;
+        }
+
+      *ptr='\0';
+      ++ptr;
+    }
+
+  *_data = ptr;
+  *_suffix = skip_fields (*_data,1);
+  if (**_suffix)
+    {
+      /* there is a suffix (i.e. the field is not the last on the line),
+         so null-terminate the _data before it */
+      **_suffix = '\0';
+      ++(*_suffix);
+    }
+  else
+    *_suffix=NULL;
+
+  if (debug)
+    fprintf (stderr,"  prefix: '%s'\n  number: '%s'\n  suffix: '%s'\n",
+        *_prefix,*_data,*_suffix);
+}
+
+
+/*
+   Converts a number in a given line of text.
+ */
+static void
+process_line (char* line)
+{
+  char *pre,*num,*suf;
+  extract_fields (line,field,&pre,&num,&suf);
+  if (pre)
+    {
+      fputs (pre,stdout);
+      fputc ((delimiter==DELIMITER_DEFAULT)?' ':delimiter, stdout);
+    }
+
+  process_suffixed_number (num);
+
+  if (suf)
+    {
+      fputc ((delimiter==DELIMITER_DEFAULT)?' ':delimiter, stdout);
+      fputs (suf,stdout);
+    }
+
+  fputs ("\n",stdout);
+}
+
+int
+main (int argc, char **argv)
+{
+  initialize_main (&argc, &argv);
+  set_program_name (argv[0]);
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  atexit (close_stdout);
+
+  while (true)
+    {
+      int c = getopt_long (argc, argv, "d:", longopts, NULL);
+
+      if (c == -1)
+        break;
+
+      switch (c)
+        {
+        case FROM_OPTION:
+          scale_from = XARGMATCH ("--from", optarg,
+                                  scale_from_args, scale_from_types);
+          break;
+
+        case FROM_UNIT_OPTION:
+          from_unit_size = string_to_integer (optarg);
+          break;
+
+        case TO_OPTION:
+          scale_to = XARGMATCH ("--to", optarg, scale_to_args, scale_to_types);
+          break;
+
+        case TO_UNIT_OPTION:
+          to_unit_size = string_to_integer (optarg);
+          break;
+
+        case ROUND_OPTION:
+          _round = XARGMATCH ("--round", optarg, round_args, round_types);
+          break;
+
+        case GROUPING_OPTION:
+          grouping = 1;
+          break;
+
+        case PADDING_OPTION:
+          if (xstrtol (optarg,NULL,10, &padding_width,"")!=LONGINT_OK
+              || padding_width==0)
+            error (EXIT_FAILURE,0,_("invalid padding value '%s'"),optarg);
+          if (padding_width<0)
+            {
+              padding_alignment = MBS_ALIGN_LEFT;
+              padding_width = -padding_width;
+            }
+          padding_buffer = malloc (padding_width+1);
+          if (!padding_buffer)
+            error (EXIT_FAILURE,0, _("out of memory (requested %ld bytes)"),
+                    padding_width+1);
+          break;
+
+        case FIELD_OPTION:
+          if (xstrtol (optarg,NULL,10, &field,"")!=LONGINT_OK
+                || field<=0)
+              error (EXIT_FAILURE,0,_("invalid field value '%s'"),optarg);
+          break;
+
+        case 'd':
+          if (strlen (optarg)!=1)
+            error (EXIT_FAILURE,0,_("delimiter must be exactly one character"));
+          delimiter=optarg[0];
+          break;
+
+        case SUFFIX_OPTION:
+          suffix = optarg;
+          break;
+
+        case DEBUG_OPTION:
+          debug=1;
+          break;
+
+        case_GETOPT_HELP_CHAR;
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+        default:
+          usage (EXIT_FAILURE);
+        }
+    }
+
+  switch (_round)
+    {
+    case round_ceiling:
+      human_print_options |= human_ceiling;
+      break;
+    case round_floor:
+      human_print_options |= human_floor;
+      break;
+    case round_nearest:
+      human_print_options |= human_round_to_nearest;
+      break;
+    }
+
+  switch (scale_to)
+    {
+    case scale_SI:
+      human_print_options |= human_autoscale | human_SI;
+      break;
+    case scale_IEC:
+      human_print_options |= human_autoscale | human_base_1024 | human_SI ;
+      break;
+
+    case scale_auto:
+      /* should never happen. assert? */
+
+    case scale_none:
+      break;
+    }
+
+  if (grouping)
+    {
+      if (scale_to!=scale_none)
+        error (EXIT_FAILURE,0,_("--grouping cannot be combined with --to"));
+      human_print_options |= human_group_digits;
+    }
+
+  if (argc > optind)
+    {
+    for (; optind < argc; optind++)
+      process_line (argv[optind]);
+    }
+  else
+    {
+      char buf[BUFFER_SIZE + 1];
+
+      while ( fgets (buf,BUFFER_SIZE,stdin) != NULL )
+        {
+          chomp (buf);
+          process_line (buf);
+        }
+
+      if (ferror (stdin))
+          error (0,errno,_("error reading input"));
+    }
+
+  free (padding_buffer);
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/tests/local.mk b/tests/local.mk
index 5eeddd5..8f56e43 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -288,6 +288,7 @@ all_tests =					\
   tests/misc/nohup.sh				\
   tests/misc/nproc-avail.sh			\
   tests/misc/nproc-positive.sh			\
+  tests/misc/numfmt.pl				\
   tests/misc/od-N.sh				\
   tests/misc/od-multiple-t.sh			\
   tests/misc/od-x8.sh				\
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
new file mode 100644
index 0000000..a9bd32d
--- /dev/null
+++ b/tests/misc/numfmt.pl
@@ -0,0 +1,242 @@
+#!/usr/bin/perl
+# Basic tests for "numfmt".
+
+# Copyright (C) 2012 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+my $prog = 'numfmt';
+
+# TODO: add localization tests with "grouping"
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @Tests =
+    (
+     ['1', '1234',             {OUT => "1234"}],
+     ['2', '--from=SI 1K',     {OUT => "1000"}],
+     ['3', '--from=IEC 1K',    {OUT => "1024"}],
+     ['4', '--from=auto 1K',   {OUT => "1000"}],
+     ['5', '--from=auto 1Ki',  {OUT => "1024"}],
+
+     ['6', {IN_PIPE => "1234"},            {OUT => "1234"}],
+     ['7', '--from=SI', {IN_PIPE => "2K"}, {OUT => "2000"}],
+
+     #NOTE: "SI" outputs lower "k"?
+     ['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=floor   2001',   {OUT => "2.0k"}],
+     ['12', '--to=SI --round=floor   1999',   {OUT => "1.9k"}],
+     ['13', '--to=SI --round=ceiling 1901',   {OUT => "2.0k"}],
+     ['14', '--to=SI --round=floor   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"}],
+     ['20', '--to=IEC 2047',                   {OUT => "2.0K"}],
+     ['21', '--to=IEC --round=floor   2049',   {OUT => "2.0K"}],
+     ['22', '--to=IEC --round=floor   2047',   {OUT => "1.9K"}],
+     ['23', '--to=IEC --round=ceiling 2040',   {OUT => "2.0K"}],
+     ['24', '--to=IEC --round=floor   2040',   {OUT => "1.9K"}],
+     ['25', '--to=IEC --round=nearest 1996',   {OUT => "1.9K"}],
+     ['26', '--to=IEC --round=nearest 1997',   {OUT => "2.0K"}],
+
+     ['27', '--from-unit=512 4',   {OUT => "2048"}],
+     ['28', '--to-unit=512 2048',   {OUT => "4"}],
+
+     ['29', '--from-unit=512 --from=SI 4M',   {OUT => "2048000000"}],
+     ['30', '--from-unit=512 --from=IEC --to=IEC 4M',   {OUT => "2.0G"}],
+
+     # Test Suffix logic
+     ['suf-1', '4000',    {OUT=>'4000'}],
+     ['suf-2', '4Q',
+             {ERR => "$prog: invalid suffix in input '4Q': 'Q'\n"},
+             {EXIT => '1'}],
+     ['suf-2.1', '4M',
+             {ERR => "$prog: not accepting suffix 'M' " .
+             "in input '4M' (consider using --from)\n"},
+             {EXIT => '1'}],
+     ['suf-3', '--from=SI 4M',  {OUT=>'4000000'}],
+     ['suf-4', '--from=SI 4Q',
+             {ERR => "$prog: invalid suffix in input '4Q': 'Q'\n"},
+             {EXIT => '1'}],
+     ['suf-5', '--from=SI 4MQ',
+             {ERR => "$prog: invalid suffix in input '4MQ': 'Q'\n"},
+             {EXIT => '1'}],
+
+     ['suf-6', '--from=IEC 4M',  {OUT=>'4194304'}],
+     ['suf-7', '--from=auto 4M',  {OUT=>'4000000'}],
+     ['suf-8', '--from=auto 4Mi',  {OUT=>'4194304'}],
+     ['suf-9', '--from=auto 4MiQ',
+             {ERR => "$prog: invalid suffix in input '4MiQ': 'Q'\n"},
+             {EXIT => '1'}],
+     ['suf-10', '--from=auto 4QiQ',
+             {ERR => "$prog: invalid suffix in input '4QiQ': 'QiQ'\n"},
+             {EXIT => '1'}],
+
+     # characters after a white space are OK - printed as-is
+     ['suf-11', '"4 M"',     {OUT=>'4 M'}],
+
+     # Custom suffix
+     ['suf-12', '--suffix=Foo 70Foo',               {OUT=>'70Foo'}],
+     ['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-17', '--suffix=Foo --to=SI   7000Bar',
+              {ERR => "$prog: invalid suffix in input '7000Bar': 'Bar'\n"},
+              {EXIT => '1'}],
+     ['suf-18', '--suffix=Foo --to=SI   7000FooF',
+              {ERR => "$prog: invalid suffix in input '7000FooF': 'FooF'\n"},
+              {EXIT => '1'}],
+
+     ## GROUPING
+
+     # "C" locale - no grouping.
+     ['grp-1', '--from=SI --grouping 7M',   {OUT=>'7000000'}],
+     ['grp-2', '--from=SI --to=SI --grouping 7M',
+              {ERR => "$prog: --grouping cannot be combined with --to\n"},
+              {EXIT => '1'}],
+
+     ## TODO: check under a different locale (one that supports grouping)?
+     #['grp-3', '--from=SI --grouping 7M',   {OUT=>'7,000,000'}],
+
+
+     ## Padding
+     ['pad-1', '--padding=10 5',             {OUT=>'         5'}],
+     ['pad-2', '--padding=-10 5',            {OUT=>'5         '}],
+     ['pad-3', '--padding=A 5',
+             {ERR => "$prog: invalid padding value 'A'\n"},
+             {EXIT => '1'}],
+     ['pad-4', '--padding=10 --to=SI 50000',             {OUT=>'       50k'}],
+     ['pad-5', '--padding=-10 --to=SI 50000',            {OUT=>'50k       '}],
+
+     # padding causing truncation, data loss
+     ['pad-6', '--padding=2 --to=SI 1000', {OUT=>'1.'},
+             {ERR => "$prog: value '1.0k' truncated due to padding, " .
+                "possible data loss\n"}],
+
+
+     # Padding + suffix
+     ['pad-7', '--padding=10 --suffix=foo --to=SI 50000',
+             {OUT=>'       50kfoo'}],
+     ['pad-8', '--padding=-10 --suffix=foo --to=SI 50000',
+             {OUT=>'50k       foo'}],
+
+
+     # Delimiters
+     ['delim-1', '--delimiter=: --from=auto 40M:',   {OUT=>'40000000:'}],
+     ['delim-2', '--delimiter="" --from=auto 40M:',
+             {ERR => "$prog: delimiter must be exactly one character\n"},
+             {EXIT => '1'}],
+     ['delim-3', '--delimiter=" " --from=auto "40M Foo"',{OUT=>'40000000 Foo'}],
+     ['delim-4', '--delimiter=: --from=auto 40M:60M',  {OUT=>'40000000:60M'}],
+
+     #Fields
+     ['field-1', '--field A',
+             {ERR => "$prog: invalid field value 'A'\n"},
+             {EXIT => '1'}],
+     ['field-2', '--field 2 --from=auto "Hello 40M World 90G"',
+             {OUT=>'Hello 40000000 World 90G'}],
+     ['field-3', '--field 3 --from=auto "Hello 40M World 90G"',
+             {OUT=>"Hello 40M "},
+             {ERR=>"$prog: invalid input number 'World'\n"},
+             {EXIT => 1},],
+     # Last field - no text after number
+     ['field-4', '--field 4 --from=auto "Hello 40M World 90G"',
+             {OUT=>"Hello 40M World 90000000000"}],
+     # Last field - a delimiter after the number
+     ['field-5', '--field 4 --from=auto "Hello 40M World 90G "',
+             {OUT=>"Hello 40M World 90000000000 "}],
+
+     # Mix Fields + Delimiters
+     ['field-6', '--delimiter=: --field 2 --from=auto "Hello:40M:World:90G"',
+             {OUT=>"Hello:40000000:World:90G"}],
+
+
+     # Corner-cases:
+     # weird mix of identical suffix,delimiters
+     # The priority is:
+     #   1. delimiters (and fields) are parsed (in process_line()
+     #   2. optional custom suffix is removed (in process_suffixed_number())
+     #   3. Remaining suffixes must be valid SI/IEC (in human_xstrtol())
+
+     # custom suffix comes BEFORE SI/IEC suffix,
+     #   so these are 40 of "M", not 40,000,000.
+     ['mix-1', '--suffix=M --from=SI 40M',     {OUT=>"40M"}],
+
+     # These are fourty-million Ms .
+     ['mix-2', '--suffix=M --from=SI 40MM',     {OUT=>"40000000M"}],
+
+     ['mix-3', '--suffix=M --from=auto 40MM',     {OUT=>"40000000M"}],
+     ['mix-4', '--suffix=M --from=auto 40MiM',     {OUT=>"41943040M"}],
+     ['mix-5', '--suffix=M --to=SI --from=SI 4MM',     {OUT=>"4.0MM"}],
+
+     # This might be confusing to the user, but it's legit:
+     # The M in the output is the custom suffix, not Mega.
+     ['mix-6', '--suffix=M 40',     {OUT=>"40M"}],
+     ['mix-7', '--suffix=M 4000000',     {OUT=>"4000000M"}],
+     ['mix-8', '--suffix=M --to=SI 4000000',     {OUT=>"4.0MM"}],
+
+     # The output 'M' is the custom suffix.
+     ['mix-10', '--delimiter=M --suffix=M 40',     {OUT=>"40M"}],
+
+     # The INPUT 'M' is a delimiter (delimiters are top priority)
+     # The output contains one M for custom suffix, and one 'M' delimiter.
+     ['mix-11', '--delimiter=M --suffix=M 40M',     {OUT=>"40MM"}],
+
+     # Same as above, the "M" is NOT treated as a mega SI prefix,
+     ['mix-12', '--delimiter=M --from=SI --suffix=M 40M',     {OUT=>"40MM"}],
+
+     # 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"}],
+     # '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"}],
+
+    );
+
+# Prepend the command line argument and append a newline to end
+# of each expected 'OUT' string.
+my $t;
+
+Test:
+foreach $t (@Tests)
+  {
+    # Don't fiddle with expected OUT string if there's a nonzero exit status.
+    foreach my $e (@$t)
+      {
+        ref $e eq 'HASH' && exists $e->{EXIT} && $e->{EXIT}
+          and next Test;
+      }
+
+    foreach my $e (@$t)
+      {
+        ref $e eq 'HASH' && exists $e->{OUT}
+          and $e->{OUT} .= "\n"
+      }
+  }
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
-- 
1.7.7.4


From ac83dceddaeaf5568a7fc4cba3c7b580ce9326b3 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Fri, 7 Dec 2012 11:47:36 -0500
Subject: [PATCH 02/16] numfmt: change SI/IEC parameters to lowercase.

---
 src/numfmt.c         |    4 +-
 tests/misc/numfmt.pl |   98 +++++++++++++++++++++++++-------------------------
 2 files changed, 51 insertions(+), 51 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 44675f9..0cdcbe0 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -57,7 +57,7 @@ scale_IEC
 
 static char const *const scale_from_args[] =
 {
-"none", "auto", "SI", "IEC", NULL
+"none", "auto", "si", "iec", NULL
 };
 static enum scale_type const scale_from_types[] =
 {
@@ -66,7 +66,7 @@ scale_none, scale_auto, scale_SI, scale_IEC
 
 static char const *const scale_to_args[] =
 {
-"none", "SI", "IEC", NULL
+"none", "si", "iec", NULL
 };
 static enum scale_type const scale_to_types[] =
 {
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index a9bd32d..fb451d6 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -28,41 +28,41 @@ my $prog = 'numfmt';
 my @Tests =
     (
      ['1', '1234',             {OUT => "1234"}],
-     ['2', '--from=SI 1K',     {OUT => "1000"}],
-     ['3', '--from=IEC 1K',    {OUT => "1024"}],
+     ['2', '--from=si 1K',     {OUT => "1000"}],
+     ['3', '--from=iec 1K',    {OUT => "1024"}],
      ['4', '--from=auto 1K',   {OUT => "1000"}],
      ['5', '--from=auto 1Ki',  {OUT => "1024"}],
 
      ['6', {IN_PIPE => "1234"},            {OUT => "1234"}],
-     ['7', '--from=SI', {IN_PIPE => "2K"}, {OUT => "2000"}],
+     ['7', '--from=si', {IN_PIPE => "2K"}, {OUT => "2000"}],
 
      #NOTE: "SI" outputs lower "k"?
-     ['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=floor   2001',   {OUT => "2.0k"}],
-     ['12', '--to=SI --round=floor   1999',   {OUT => "1.9k"}],
-     ['13', '--to=SI --round=ceiling 1901',   {OUT => "2.0k"}],
-     ['14', '--to=SI --round=floor   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"}],
-     ['20', '--to=IEC 2047',                   {OUT => "2.0K"}],
-     ['21', '--to=IEC --round=floor   2049',   {OUT => "2.0K"}],
-     ['22', '--to=IEC --round=floor   2047',   {OUT => "1.9K"}],
-     ['23', '--to=IEC --round=ceiling 2040',   {OUT => "2.0K"}],
-     ['24', '--to=IEC --round=floor   2040',   {OUT => "1.9K"}],
-     ['25', '--to=IEC --round=nearest 1996',   {OUT => "1.9K"}],
-     ['26', '--to=IEC --round=nearest 1997',   {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=floor   2001',   {OUT => "2.0k"}],
+     ['12', '--to=si --round=floor   1999',   {OUT => "1.9k"}],
+     ['13', '--to=si --round=ceiling 1901',   {OUT => "2.0k"}],
+     ['14', '--to=si --round=floor   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"}],
+     ['20', '--to=iec 2047',                   {OUT => "2.0K"}],
+     ['21', '--to=iec --round=floor   2049',   {OUT => "2.0K"}],
+     ['22', '--to=iec --round=floor   2047',   {OUT => "1.9K"}],
+     ['23', '--to=iec --round=ceiling 2040',   {OUT => "2.0K"}],
+     ['24', '--to=iec --round=floor   2040',   {OUT => "1.9K"}],
+     ['25', '--to=iec --round=nearest 1996',   {OUT => "1.9K"}],
+     ['26', '--to=iec --round=nearest 1997',   {OUT => "2.0K"}],
 
      ['27', '--from-unit=512 4',   {OUT => "2048"}],
      ['28', '--to-unit=512 2048',   {OUT => "4"}],
 
-     ['29', '--from-unit=512 --from=SI 4M',   {OUT => "2048000000"}],
-     ['30', '--from-unit=512 --from=IEC --to=IEC 4M',   {OUT => "2.0G"}],
+     ['29', '--from-unit=512 --from=si 4M',   {OUT => "2048000000"}],
+     ['30', '--from-unit=512 --from=iec --to=iec 4M',   {OUT => "2.0G"}],
 
      # Test Suffix logic
      ['suf-1', '4000',    {OUT=>'4000'}],
@@ -73,15 +73,15 @@ my @Tests =
              {ERR => "$prog: not accepting suffix 'M' " .
              "in input '4M' (consider using --from)\n"},
              {EXIT => '1'}],
-     ['suf-3', '--from=SI 4M',  {OUT=>'4000000'}],
-     ['suf-4', '--from=SI 4Q',
+     ['suf-3', '--from=si 4M',  {OUT=>'4000000'}],
+     ['suf-4', '--from=si 4Q',
              {ERR => "$prog: invalid suffix in input '4Q': 'Q'\n"},
              {EXIT => '1'}],
-     ['suf-5', '--from=SI 4MQ',
+     ['suf-5', '--from=si 4MQ',
              {ERR => "$prog: invalid suffix in input '4MQ': 'Q'\n"},
              {EXIT => '1'}],
 
-     ['suf-6', '--from=IEC 4M',  {OUT=>'4194304'}],
+     ['suf-6', '--from=iec 4M',  {OUT=>'4194304'}],
      ['suf-7', '--from=auto 4M',  {OUT=>'4000000'}],
      ['suf-8', '--from=auto 4Mi',  {OUT=>'4194304'}],
      ['suf-9', '--from=auto 4MiQ',
@@ -97,26 +97,26 @@ my @Tests =
      # Custom suffix
      ['suf-12', '--suffix=Foo 70Foo',               {OUT=>'70Foo'}],
      ['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-17', '--suffix=Foo --to=SI   7000Bar',
+     ['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-17', '--suffix=Foo --to=si   7000Bar',
               {ERR => "$prog: invalid suffix in input '7000Bar': 'Bar'\n"},
               {EXIT => '1'}],
-     ['suf-18', '--suffix=Foo --to=SI   7000FooF',
+     ['suf-18', '--suffix=Foo --to=si   7000FooF',
               {ERR => "$prog: invalid suffix in input '7000FooF': 'FooF'\n"},
               {EXIT => '1'}],
 
      ## GROUPING
 
      # "C" locale - no grouping.
-     ['grp-1', '--from=SI --grouping 7M',   {OUT=>'7000000'}],
-     ['grp-2', '--from=SI --to=SI --grouping 7M',
+     ['grp-1', '--from=si --grouping 7M',   {OUT=>'7000000'}],
+     ['grp-2', '--from=si --to=si --grouping 7M',
               {ERR => "$prog: --grouping cannot be combined with --to\n"},
               {EXIT => '1'}],
 
      ## TODO: check under a different locale (one that supports grouping)?
-     #['grp-3', '--from=SI --grouping 7M',   {OUT=>'7,000,000'}],
+     #['grp-3', '--from=si --grouping 7M',   {OUT=>'7,000,000'}],
 
 
      ## Padding
@@ -125,19 +125,19 @@ my @Tests =
      ['pad-3', '--padding=A 5',
              {ERR => "$prog: invalid padding value 'A'\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 causing truncation, data loss
-     ['pad-6', '--padding=2 --to=SI 1000', {OUT=>'1.'},
+     ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.'},
              {ERR => "$prog: value '1.0k' truncated due to padding, " .
                 "possible data loss\n"}],
 
 
      # Padding + suffix
-     ['pad-7', '--padding=10 --suffix=foo --to=SI 50000',
+     ['pad-7', '--padding=10 --suffix=foo --to=si 50000',
              {OUT=>'       50kfoo'}],
-     ['pad-8', '--padding=-10 --suffix=foo --to=SI 50000',
+     ['pad-8', '--padding=-10 --suffix=foo --to=si 50000',
              {OUT=>'50k       foo'}],
 
 
@@ -180,20 +180,20 @@ my @Tests =
 
      # custom suffix comes BEFORE SI/IEC suffix,
      #   so these are 40 of "M", not 40,000,000.
-     ['mix-1', '--suffix=M --from=SI 40M',     {OUT=>"40M"}],
+     ['mix-1', '--suffix=M --from=si 40M',     {OUT=>"40M"}],
 
      # These are fourty-million Ms .
-     ['mix-2', '--suffix=M --from=SI 40MM',     {OUT=>"40000000M"}],
+     ['mix-2', '--suffix=M --from=si 40MM',     {OUT=>"40000000M"}],
 
      ['mix-3', '--suffix=M --from=auto 40MM',     {OUT=>"40000000M"}],
      ['mix-4', '--suffix=M --from=auto 40MiM',     {OUT=>"41943040M"}],
-     ['mix-5', '--suffix=M --to=SI --from=SI 4MM',     {OUT=>"4.0MM"}],
+     ['mix-5', '--suffix=M --to=si --from=si 4MM',     {OUT=>"4.0MM"}],
 
      # This might be confusing to the user, but it's legit:
      # The M in the output is the custom suffix, not Mega.
      ['mix-6', '--suffix=M 40',     {OUT=>"40M"}],
      ['mix-7', '--suffix=M 4000000',     {OUT=>"4000000M"}],
-     ['mix-8', '--suffix=M --to=SI 4000000',     {OUT=>"4.0MM"}],
+     ['mix-8', '--suffix=M --to=si 4000000',     {OUT=>"4.0MM"}],
 
      # The output 'M' is the custom suffix.
      ['mix-10', '--delimiter=M --suffix=M 40',     {OUT=>"40M"}],
@@ -203,13 +203,13 @@ my @Tests =
      ['mix-11', '--delimiter=M --suffix=M 40M',     {OUT=>"40MM"}],
 
      # Same as above, the "M" is NOT treated as a mega SI prefix,
-     ['mix-12', '--delimiter=M --from=SI --suffix=M 40M',     {OUT=>"40MM"}],
+     ['mix-12', '--delimiter=M --from=si --suffix=M 40M',     {OUT=>"40MM"}],
 
      # The 'M' is treated as a delimiter, and so the input value is '4000'
-     ['mix-13', '--delimiter=M --to=SI --from=auto 4000M5000M9000',
+     ['mix-13', '--delimiter=M --to=si --from=auto 4000M5000M9000',
              {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',
+     ['mix-14', '--delimiter=M --field 2 --from=auto --to=si 4000M5000M9000',
              {OUT=>"4000M5.0kM9000"}],
 
     );
-- 
1.7.7.4


From 94869bbb00b9c7ee226a558182e317844a452398 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Fri, 7 Dec 2012 12:05:16 -0500
Subject: [PATCH 03/16] numfmt: separate debug/devdebug options.

---
 src/numfmt.c |   41 +++++++++++++++++++++++++++++++----------
 1 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 0cdcbe0..918a267 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -18,6 +18,8 @@
 #include <getopt.h>
 #include <stdio.h>
 #include <sys/types.h>
+#include <langinfo.h>
+#include <locale.h>
 
 #include "mbsalign.h"
 #include "argmatch.h"
@@ -44,7 +46,8 @@ enum
   GROUPING_OPTION,
   PADDING_OPTION,
   FIELD_OPTION,
-  DEBUG_OPTION
+  DEBUG_OPTION,
+  DEV_DEBUG_OPTION
 };
 
 enum scale_type
@@ -104,6 +107,7 @@ static struct option const longopts[] =
   {"delimiter", required_argument, NULL, 'd'},
   {"field", required_argument, NULL, FIELD_OPTION},
   {"debug", no_argument,NULL,DEBUG_OPTION},
+  {"devdebug", no_argument,NULL,DEV_DEBUG_OPTION},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -124,9 +128,14 @@ static char *padding_buffer=NULL;
 static long int padding_width=0;
 static mbs_align_t padding_alignment=MBS_ALIGN_RIGHT;
 static long int field=1;
-static int debug=0;
 static int delimiter = DELIMITER_DEFAULT;
 
+/* Debug for users: print warnings to STDERR about possible
+   error (similar to sort's debug) */
+static int debug=0;
+
+/* debugging for developers - to be removed in final version? */
+static int dev_debug=0;
 
 /*
    Converts a string to a long-long-int, optionally handling suffixes.
@@ -350,6 +359,7 @@ Numbers can be processed either from stdin or command arguments.\n\
                   in C/POSIX locales).\n\
   --field N       replace the number in input field N (default is 1)\n\
   -d, --delimiter=X  use X instead of whitespace for field delimiter\n\
+  --debug         Print warning about possible errors.\n\
   \n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -423,7 +433,7 @@ parse_number (const char* str)
   if (err != LONGINT_OK)
     human_xstrtol_fatal (err, str);
 
-  if (debug)
+  if (dev_debug)
     fprintf (stderr,"Parsing number:\n  input string: '%s'\n  " \
             "remaining characters: '%s'\n  numeric value: %zu\n",
             str,ptr,val);
@@ -460,7 +470,7 @@ print_number (const __strtol_t val)
   char *s = human_readable (val, buf, human_print_options,
                            from_unit_size,to_unit_size);
 
-  if (debug)
+  if (dev_debug)
     fprintf (stderr,"Formatting output:\n  value: %zu\n  humanized: '%s'\n",
             val,s);
 
@@ -473,7 +483,7 @@ print_number (const __strtol_t val)
         error (0,0,_("value '%s' truncated due to padding, possible data loss"),
               s);
 
-      if (debug)
+      if (dev_debug)
         fprintf (stderr,"  After padding: '%s'\n", padding_buffer);
 
       fputs (padding_buffer,stdout);
@@ -499,12 +509,12 @@ process_suffixed_number (char* text)
         {
           /* trim suffix, ONLY if it's at the end of the text */
           *possible_suffix='\0';
-          if (debug)
+          if (dev_debug)
             fprintf (stderr,"Trimming suffix '%s'\n", suffix);
         }
       else
         {
-          if (debug)
+          if (dev_debug)
             fprintf (stderr,"No valid suffix found\n");
         }
     }
@@ -565,7 +575,7 @@ extract_fields (char* line, int _field,
   *_data = NULL ;
   *_suffix = NULL ;
 
-  if (debug)
+  if (dev_debug)
     fprintf (stderr,"Extracting Fields:\n  input: '%s'\n  field: %d\n",
         line, _field);
 
@@ -577,7 +587,7 @@ extract_fields (char* line, int _field,
       if (*ptr=='\0')
         {
           /* not enough fields in the input - print warning? */
-          if (debug)
+          if (dev_debug)
             fprintf (stderr,"  TOO FEW FIELDS!\n  prefix: '%s'\n",
                 *_prefix);
           return ;
@@ -599,7 +609,7 @@ extract_fields (char* line, int _field,
   else
     *_suffix=NULL;
 
-  if (debug)
+  if (dev_debug)
     fprintf (stderr,"  prefix: '%s'\n  number: '%s'\n  suffix: '%s'\n",
         *_prefix,*_data,*_suffix);
 }
@@ -710,6 +720,10 @@ main (int argc, char **argv)
           debug=1;
           break;
 
+        case DEV_DEBUG_OPTION:
+          dev_debug=1;
+          break;
+
         case_GETOPT_HELP_CHAR;
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
 
@@ -752,8 +766,15 @@ main (int argc, char **argv)
       if (scale_to!=scale_none)
         error (EXIT_FAILURE,0,_("--grouping cannot be combined with --to"));
       human_print_options |= human_group_digits;
+      if (debug && (strlen(nl_langinfo(THOUSEP))==0))
+            error(0,0,_("--grouping has not effect in this locale"));
     }
 
+  /* Warn about no-op */
+  if (debug && scale_from==scale_none && scale_to==scale_none &&
+      !grouping && (padding_width==0))
+      error(0,0,_("No conversion option specified"));
+
   if (argc > optind)
     {
     for (; optind < argc; optind++)
-- 
1.7.7.4


From 92f425695a2b8acef9e708d3035814e21e801583 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Fri, 7 Dec 2012 12:08:26 -0500
Subject: [PATCH 04/16] numfmt: fix segfault when no numbers are found.

---
 src/numfmt.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 918a267..d042bde 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -629,7 +629,11 @@ process_line (char* line)
       fputc ((delimiter==DELIMITER_DEFAULT)?' ':delimiter, stdout);
     }
 
-  process_suffixed_number (num);
+  if (num)
+    process_suffixed_number (num);
+  if (!num && debug)
+    error(0,0,_("Input line is too short, " \
+               "no numbers found to convert in field %ld"), field);
 
   if (suf)
     {
-- 
1.7.7.4


From bbea54cd56bcb2abea14a2e2c3384f1e6d6864ba Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Fri, 7 Dec 2012 13:25:35 -0500
Subject: [PATCH 05/16] numfmt: improve --field, add more tests.

---
 src/numfmt.c         |    4 ++--
 tests/misc/numfmt.pl |    7 +++++++
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index d042bde..5c4d426 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -624,10 +624,10 @@ process_line (char* line)
   char *pre,*num,*suf;
   extract_fields (line,field,&pre,&num,&suf);
   if (pre)
-    {
       fputs (pre,stdout);
+
+  if (pre && num)
       fputc ((delimiter==DELIMITER_DEFAULT)?' ':delimiter, stdout);
-    }
 
   if (num)
     process_suffixed_number (num);
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index fb451d6..5b86e21 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -170,6 +170,13 @@ my @Tests =
      ['field-6', '--delimiter=: --field 2 --from=auto "Hello:40M:World:90G"',
              {OUT=>"Hello:40000000:World:90G"}],
 
+     # not enough fields - silently ignored
+     ['field-7', '--field 3 --to=si "Hello World"', {OUT=>"Hello World"}],
+     ['field-8', '--field 3 --debug --to=si "Hello World"',
+	     {OUT=>"Hello World"},
+             {ERR=>"$prog: Input line is too short, no numbers found " .
+	           "to convert in field 3\n"}],
+
 
      # Corner-cases:
      # weird mix of identical suffix,delimiters
-- 
1.7.7.4


From bcce3d34296479aa729383f9187e91634139d5ec Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Fri, 7 Dec 2012 13:51:47 -0500
Subject: [PATCH 06/16] numfmt: add --header option.

---
 src/numfmt.c         |   33 ++++++++++++++++++++++++++++++---
 tests/misc/numfmt.pl |   27 +++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 5c4d426..58c72b7 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -47,7 +47,8 @@ enum
   PADDING_OPTION,
   FIELD_OPTION,
   DEBUG_OPTION,
-  DEV_DEBUG_OPTION
+  DEV_DEBUG_OPTION,
+  HEADER_OPTION
 };
 
 enum scale_type
@@ -108,6 +109,7 @@ static struct option const longopts[] =
   {"field", required_argument, NULL, FIELD_OPTION},
   {"debug", no_argument,NULL,DEBUG_OPTION},
   {"devdebug", no_argument,NULL,DEV_DEBUG_OPTION},
+  {"header", optional_argument,NULL,HEADER_OPTION},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -130,6 +132,9 @@ static mbs_align_t padding_alignment=MBS_ALIGN_RIGHT;
 static long int field=1;
 static int delimiter = DELIMITER_DEFAULT;
 
+/* if non-zero, the first 'header' lines from STDIN are skipped */
+static uintmax_t header=0;
+
 /* Debug for users: print warnings to STDERR about possible
    error (similar to sort's debug) */
 static int debug=0;
@@ -728,6 +733,19 @@ main (int argc, char **argv)
           dev_debug=1;
           break;
 
+        case HEADER_OPTION:
+          if (optarg)
+            {
+              if (xstrtoumax (optarg,NULL,10,&header,"")!=LONGINT_OK
+                  || header==0)
+                error (EXIT_FAILURE,0,_("invalid header value '%s'"), optarg);
+            }
+          else
+            {
+              header=1;
+            }
+          break;
+
         case_GETOPT_HELP_CHAR;
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
 
@@ -781,13 +799,22 @@ main (int argc, char **argv)
 
   if (argc > optind)
     {
-    for (; optind < argc; optind++)
-      process_line (argv[optind]);
+      if (debug && header)
+        error(0,0,_("--header ignored with command-line input"));
+
+      for (; optind < argc; optind++)
+        process_line (argv[optind]);
     }
   else
     {
       char buf[BUFFER_SIZE + 1];
 
+      /* FIXME: check if the line is too long,
+                'buf' doesn't contain a CRLF, and so this isn't
+                the entire line */
+      while ( header-- && fgets (buf,BUFFER_SIZE,stdin) != NULL )
+          fputs(buf,stdout);
+
       while ( fgets (buf,BUFFER_SIZE,stdin) != NULL )
         {
           chomp (buf);
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index 5b86e21..ed360c0 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -219,6 +219,33 @@ my @Tests =
      ['mix-14', '--delimiter=M --field 2 --from=auto --to=si 4000M5000M9000',
              {OUT=>"4000M5.0kM9000"}],
 
+
+
+     ## Header testing
+
+     # header - silently ignored with command line parameters
+     ['header-1', '--header --to=iec 4000', {OUT=>"4.0K"}],
+
+     # header warning with --debug
+     ['header-2', '--debug --header --to=iec 4000', {OUT=>"4.0K"},
+	     {ERR=>"$prog: --header ignored with command-line input\n"}],
+
+     ['header-3', '--header=A',
+             {ERR=>"$prog: invalid header value 'A'\n"},
+             {EXIT => 1},],
+     ['header-4', '--header=0',
+             {ERR=>"$prog: invalid header value '0'\n"},
+             {EXIT => 1},],
+     ['header-5', '--header=-6',
+             {ERR=>"$prog: invalid header value '-6'\n"},
+             {EXIT => 1},],
+     ['header-6', '--debug --header --to=iec',
+	     {IN_PIPE=>"size\n5000\n90000\n"},
+	     {OUT=>"size\n4.9K\n88K"}],
+     ['header-7', '--debug --header=3 --to=iec',
+	     {IN_PIPE=>"hello\nworld\nsize\n5000\n90000\n"},
+	     {OUT=>"hello\n\world\nsize\n4.9K\n88K"}],
+
     );
 
 # Prepend the command line argument and append a newline to end
-- 
1.7.7.4


From 1c2ac2d7f1446b9ed2eecc82fef8492dbbe537d7 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Tue, 11 Dec 2012 13:48:37 -0500
Subject: [PATCH 07/16] numfmt: 'syntax-check' corrections.

---
 src/numfmt.c         |   21 ++++++++++-----------
 tests/misc/numfmt.pl |   36 +++++++++++++++++++++++++-----------
 2 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 58c72b7..927f633 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -19,7 +19,6 @@
 #include <stdio.h>
 #include <sys/types.h>
 #include <langinfo.h>
-#include <locale.h>
 
 #include "mbsalign.h"
 #include "argmatch.h"
@@ -364,7 +363,7 @@ Numbers can be processed either from stdin or command arguments.\n\
                   in C/POSIX locales).\n\
   --field N       replace the number in input field N (default is 1)\n\
   -d, --delimiter=X  use X instead of whitespace for field delimiter\n\
-  --debug         Print warning about possible errors.\n\
+  --debug         print warning about possible errors.\n\
   \n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -637,8 +636,8 @@ process_line (char* line)
   if (num)
     process_suffixed_number (num);
   if (!num && debug)
-    error(0,0,_("Input line is too short, " \
-               "no numbers found to convert in field %ld"), field);
+    error (0,0,_("input line is too short, " \
+                 "no numbers found to convert in field %ld"), field);
 
   if (suf)
     {
@@ -788,19 +787,19 @@ main (int argc, char **argv)
       if (scale_to!=scale_none)
         error (EXIT_FAILURE,0,_("--grouping cannot be combined with --to"));
       human_print_options |= human_group_digits;
-      if (debug && (strlen(nl_langinfo(THOUSEP))==0))
-            error(0,0,_("--grouping has not effect in this locale"));
+      if (debug && (strlen (nl_langinfo (THOUSEP))==0))
+            error (0,0,_("--grouping has not effect in this locale"));
     }
 
   /* Warn about no-op */
-  if (debug && scale_from==scale_none && scale_to==scale_none &&
-      !grouping && (padding_width==0))
-      error(0,0,_("No conversion option specified"));
+  if (debug && scale_from==scale_none && scale_to==scale_none
+      && !grouping && (padding_width==0))
+      error (0,0,_("no conversion option specified"));
 
   if (argc > optind)
     {
       if (debug && header)
-        error(0,0,_("--header ignored with command-line input"));
+        error (0,0,_("--header ignored with command-line input"));
 
       for (; optind < argc; optind++)
         process_line (argv[optind]);
@@ -813,7 +812,7 @@ main (int argc, char **argv)
                 'buf' doesn't contain a CRLF, and so this isn't
                 the entire line */
       while ( header-- && fgets (buf,BUFFER_SIZE,stdin) != NULL )
-          fputs(buf,stdout);
+          fputs (buf,stdout);
 
       while ( fgets (buf,BUFFER_SIZE,stdin) != NULL )
         {
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index ed360c0..b7058b0 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -25,6 +25,10 @@ my $prog = 'numfmt';
 # Turn off localization of executable's output.
 @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
 
+my $locale = $ENV{LOCALE_FR_UTF8};
+! defined $locale || $locale eq 'none'
+  and $locale = 'C';
+
 my @Tests =
     (
      ['1', '1234',             {OUT => "1234"}],
@@ -109,15 +113,12 @@ my @Tests =
 
      ## GROUPING
 
-     # "C" locale - no grouping.
+     # "C" locale - no grouping (locale-specific tests, below)
      ['grp-1', '--from=si --grouping 7M',   {OUT=>'7000000'}],
      ['grp-2', '--from=si --to=si --grouping 7M',
               {ERR => "$prog: --grouping cannot be combined with --to\n"},
               {EXIT => '1'}],
 
-     ## TODO: check under a different locale (one that supports grouping)?
-     #['grp-3', '--from=si --grouping 7M',   {OUT=>'7,000,000'}],
-
 
      ## Padding
      ['pad-1', '--padding=10 5',             {OUT=>'         5'}],
@@ -173,9 +174,9 @@ my @Tests =
      # not enough fields - silently ignored
      ['field-7', '--field 3 --to=si "Hello World"', {OUT=>"Hello World"}],
      ['field-8', '--field 3 --debug --to=si "Hello World"',
-	     {OUT=>"Hello World"},
+             {OUT=>"Hello World"},
              {ERR=>"$prog: Input line is too short, no numbers found " .
-	           "to convert in field 3\n"}],
+                   "to convert in field 3\n"}],
 
 
      # Corner-cases:
@@ -228,7 +229,7 @@ my @Tests =
 
      # header warning with --debug
      ['header-2', '--debug --header --to=iec 4000', {OUT=>"4.0K"},
-	     {ERR=>"$prog: --header ignored with command-line input\n"}],
+             {ERR=>"$prog: --header ignored with command-line input\n"}],
 
      ['header-3', '--header=A',
              {ERR=>"$prog: invalid header value 'A'\n"},
@@ -240,14 +241,27 @@ my @Tests =
              {ERR=>"$prog: invalid header value '-6'\n"},
              {EXIT => 1},],
      ['header-6', '--debug --header --to=iec',
-	     {IN_PIPE=>"size\n5000\n90000\n"},
-	     {OUT=>"size\n4.9K\n88K"}],
+             {IN_PIPE=>"size\n5000\n90000\n"},
+             {OUT=>"size\n4.9K\n88K"}],
      ['header-7', '--debug --header=3 --to=iec',
-	     {IN_PIPE=>"hello\nworld\nsize\n5000\n90000\n"},
-	     {OUT=>"hello\n\world\nsize\n4.9K\n88K"}],
+             {IN_PIPE=>"hello\nworld\nsize\n5000\n90000\n"},
+             {OUT=>"hello\nworld\nsize\n4.9K\n88K"}],
+
 
     );
 
+my @Locale_Tests =
+  (
+     # Locale that supports grouping, but without '--grouping' parameter
+     ['lcl-grp-1', '--from=si 7M',   {OUT=>"7000000"},
+             {ENV=>"LC_ALL=$locale"}],
+
+     # Locale with grouping
+     ['lcl-grp-2', '--from=si --grouping 7M',   {OUT=>"7 000 000"},
+             {ENV=>"LC_ALL=$locale"}],
+  );
+push @Tests, @Locale_Tests if $locale ne "C";
+
 # Prepend the command line argument and append a newline to end
 # of each expected 'OUT' string.
 my $t;
-- 
1.7.7.4


From 6e82c8b2632243eeb23147a1cfa020cb0fdddc2a Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Wed, 12 Dec 2012 13:52:22 -0500
Subject: [PATCH 08/16] numfmt: rewrite internal human-strtod with doubles

---
 src/numfmt.c         |  275 +++++++++++++++++++++++++++++++++-----------------
 tests/misc/numfmt.pl |   86 ++++++++++++++--
 2 files changed, 258 insertions(+), 103 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 927f633..30baee7 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -141,42 +141,7 @@ static int debug=0;
 /* debugging for developers - to be removed in final version? */
 static int dev_debug=0;
 
-/*
-   Converts a string to a long-long-int, optionally handling suffixes.
-   Mostly copied from <gnulib>/lib/xstdtol.c, with modified functionality.
- */
-
-//The following #defines are copied from "xstrtoumax.c"
-#define __strtol strtoumax
 #define __strtol_t uintmax_t
-#define STRTOL_T_MINIMUM 0
-#define STRTOL_T_MAXIMUM UINTMAX_MAX
-
-static strtol_error
-bkm_scale (__strtol_t *x, int scale_factor)
-{
-  if (TYPE_SIGNED (__strtol_t) && *x < STRTOL_T_MINIMUM / scale_factor)
-    {
-      *x = STRTOL_T_MINIMUM;
-      return LONGINT_OVERFLOW;
-    }
-  if (STRTOL_T_MAXIMUM / scale_factor < *x)
-    {
-      *x = STRTOL_T_MAXIMUM;
-      return LONGINT_OVERFLOW;
-    }
-  *x *= scale_factor;
-  return LONGINT_OK;
-}
-
-static strtol_error
-bkm_scale_by_power (__strtol_t *x, int base, int power)
-{
-  strtol_error err = LONGINT_OK;
-  while (power--)
-    err |= bkm_scale (x, base);
-  return err;
-}
 
 static inline int
 valid_suffix ( const char suf )
@@ -220,65 +185,161 @@ suffix_power ( const char suf )
     }
 }
 
-static strtol_error
-human_xstrtol (const char *s, char **ptr,
-           __strtol_t *val, enum scale_type scaling)
+static long double
+powerld (long double base, unsigned int x)
 {
-  char *t_ptr;
-  char **p;
-  __strtol_t tmp;
-  strtol_error err = LONGINT_OK;
-  int base=(scaling==scale_IEC)?1024:1000; /* 'auto' is checked below */
-  int overflow=0;
-  int skip=1;
-  const int strtol_base = 10 ; //TODO: allow user-changable base?
+  long double result=base;
+  if (x==0)
+    return 1;
+  /* TODO: check for overflow, inf? */
+  while (--x)
+    result *= base;
+  return result;
+}
 
+enum human_strtod_error
+{
+  OK=0,
+  NO_DIGITS_FOUND,
+  INTEGRAL_OVERFLOW,
+  INVALID_NUMBER,
+  FRACTION_FORBIDDEN_WITHOUT_SCALING,
+  FRACTION_NO_DIGITS_FOUND,
+  FRACTION_OVERFLOW,
+  FRACTION_INVALID_NUMBER,
+  FRACTION_REQUIRES_SUFFIX,
+  VALID_BUT_FORBIDDEN_SUFFIX,
+  INVALID_SUFFIX,
+
+};
 
-  p = (ptr ? ptr : &t_ptr);
+static enum human_strtod_error
+human_strtod (const char *input_str,
+              char* /*output*/ *ptr, /*required, unlike in stdtod */
+              long double /*output*/ *value, /* required */
+              enum scale_type allowed_scaling)
+{
+  char *pi, *pf;
+  uintmax_t integral,fraction=0;
+  int have_fractions=0;
+  int power=0;
+  /* 'scale_auto' is checked below */
+  int scale_base=(allowed_scaling==scale_IEC)?1024:1000;
+  const int base = 10 ;
+
+  const char *decimal_point = nl_langinfo (RADIXCHAR);
+  if (decimal_point==NULL || strlen (decimal_point)==0)
+    decimal_point = ".";
+  const int decimal_point_length=strlen (decimal_point);
+
+  if (dev_debug)
+    fprintf (stderr,"string-to-double:\n  input string: '%s'\n  " \
+            "locale decimal-point: '%s'\n", input_str,decimal_point);
 
   errno = 0;
-  tmp = __strtol (s, p, strtol_base);
+  /* TODO: accept locale'd grouped values for the integral part */
+  integral = strtoumax (input_str, &pi, base);
+  if (pi==input_str)
+    return NO_DIGITS_FOUND;
+  if (errno==ERANGE)
+    return INTEGRAL_OVERFLOW;
+  if (errno!=0)
+    return INVALID_NUMBER; /* should never happen? */
 
-  if (*p == s)
-    {
-      /* No digits found */
-        return LONGINT_INVALID;
-    }
-  else if (errno != 0)
+  *value = integral;
+
+  *ptr = pi;
+
+  if (dev_debug)
+    fprintf (stderr,"  integral-part: %lu\n", integral);
+
+  /* optional decimal point + fraction */
+  if (STREQ_LEN (pi,decimal_point,decimal_point_length))
     {
-      if (errno != ERANGE)
-        return LONGINT_INVALID;
-      err = LONGINT_OVERFLOW;
+      if (allowed_scaling==scale_none)
+        return FRACTION_FORBIDDEN_WITHOUT_SCALING;
+
+      errno = 0 ;
+      pi += decimal_point_length;
+      if (!isdigit (*pi))
+        return FRACTION_NO_DIGITS_FOUND;
+      fraction = strtoumax (pi, &pf, base);
+      if (pf==pi)
+        return FRACTION_NO_DIGITS_FOUND;
+      if (errno==ERANGE)
+        return FRACTION_OVERFLOW;
+      if (errno!=0)
+        return FRACTION_INVALID_NUMBER;
+
+      int exponent = pf-pi;
+      long double f = ((long double)fraction)/powerld (10,exponent);
+
+      have_fractions=1;
+
+      if (dev_debug)
+        fprintf (stderr,"  fraction-part: %lu (exponent=%d)\n" \
+                       "  fraction-value: %Lf\n",
+              fraction,exponent,f);
+
+      *value += f;
+
+      if (dev_debug)
+        fprintf (stderr,"  combined-value: %Lf\n", *value);
+
+      *ptr= pf;
     }
 
-  if (**p == '\0' || scaling==scale_none || !valid_suffix (**p))
+  /* no suffix / trailing characters */
+  if (**ptr=='\0')
     {
-      /* no suffixes after the number, or no scaling requested,
-         or no valid suffixes found */
-      *val = tmp;
-      return err;
+      if (have_fractions)
+        return FRACTION_REQUIRES_SUFFIX;
+
+      if (dev_debug)
+        fprintf (stderr,"  no fraction,suffix detected\n" \
+                       "  returning value: %Lf\n", *value);
+
+      return OK;
     }
 
-  if (scaling==scale_auto && (p[0][1]=='i'))
+  /* process suffix */
+  if (!valid_suffix (**ptr))
+    return INVALID_SUFFIX;
+
+  if (allowed_scaling==scale_none)
+    return VALID_BUT_FORBIDDEN_SUFFIX;
+
+  power = suffix_power (**ptr);
+  (*ptr)++; /* skip first suffix character */
+
+  if (allowed_scaling==scale_auto && **ptr=='i')
     {
       /* auto-scaling enabled, and the first suffix character
          is followed by an 'i' (e.g. Ki, Mi, Gi) */
-      base = 1024 ;
-      ++skip;
+      scale_base = 1024 ;
+      (*ptr)++; /* skip second  ('i') suffix character */
+      if (dev_debug)
+        fprintf (stderr,"  Auto-scaling, found 'i', switching to base %d\n",
+                scale_base);
     }
 
-  overflow = bkm_scale_by_power (&tmp, base, suffix_power (**p));
+  long double multiplier = powerld (scale_base,power);
 
-  err |= overflow;
+  if (dev_debug)
+    fprintf (stderr, "  suffix power=%d^%d = %Lf\n",
+          scale_base,power,multiplier);
 
-  *p += skip; /* skip the suffix characters */
+  (*value) = (*value) * multiplier;
 
-  *val = tmp;
-  return err;
+  if (dev_debug)
+    fprintf (stderr, "  returning value: %Lf\n", *value);
+
+  return OK;
 }
 
+
 static void
-human_xstrtol_fatal (enum strtol_error err,
+human_strtod_fatal (enum human_strtod_error err,
                      char const *input_str)
 {
   char const *msgid;
@@ -288,17 +349,53 @@ human_xstrtol_fatal (enum strtol_error err,
     default:
       abort ();
 
-    case LONGINT_INVALID:
-      msgid = N_("invalid input number '%s'");
+    case NO_DIGITS_FOUND:
+      msgid = N_("no digits found: '%s'");
+      break;
+
+    case INTEGRAL_OVERFLOW:
+      msgid = N_("value too large to be converted: '%s'");
+      break;
+
+    case INVALID_NUMBER:
+      msgid = N_("invalid number: '%s'");
+      break;
+
+    case FRACTION_FORBIDDEN_WITHOUT_SCALING:
+      msgid = N_("cannot process decimal-point value without scaling: '%s'" \
+                 " (consider using --from)");
+      break;
+
+    case FRACTION_NO_DIGITS_FOUND:
+      msgid = N_("no digits in fraction: '%s'");
+      break;
+
+    case FRACTION_OVERFLOW:
+      msgid = N_("fraction value too large to be converted: '%s'");
+      break;
+
+    case FRACTION_INVALID_NUMBER:
+      msgid = N_("invalid fraction number: '%s'");
+      break;
+
+    case FRACTION_REQUIRES_SUFFIX:
+      msgid = N_("decimal-point values require a suffix (e.g. K/M/G/T): '%s'");
       break;
 
-    case LONGINT_OVERFLOW:
-      msgid = N_("input value too large '%s'");
+    case VALID_BUT_FORBIDDEN_SUFFIX:
+      msgid = N_("rejecting suffix in input: '%s' (consider using --from)");
       break;
+
+    case INVALID_SUFFIX:
+      msgid = N_("invalid suffix in input: '%s'");
+      break;
+
     }
+
   error (EXIT_FAILURE,0,gettext (msgid), input_str);
 }
 
+
 /* Convert a string of decimal digits, N_STRING, with an optional suffinx
    to an integral value.  Upon successful conversion,
    return that value.  If it cannot be converted, give a diagnostic and exit.
@@ -430,33 +527,23 @@ chomp (char *s)
 static __strtol_t
 parse_number (const char* str)
 {
-  char *ptr;
+  char *ptr=NULL;
   __strtol_t val=0;
 
-  strtol_error err = human_xstrtol (str,&ptr,&val,scale_from);
-  if (err != LONGINT_OK)
-    human_xstrtol_fatal (err, str);
+  long double dval=0;
+  enum human_strtod_error err = human_strtod (str, &ptr, &dval, scale_from);
+  if (err != OK)
+    human_strtod_fatal (err,str);
+  val=(__strtol_t)dval; /* NOTE: Possible data loss here, double->uintmax_t */
 
   if (dev_debug)
     fprintf (stderr,"Parsing number:\n  input string: '%s'\n  " \
             "remaining characters: '%s'\n  numeric value: %zu\n",
             str,ptr,val);
 
-  if ( *ptr!='\0' )
-    {
-      if ((strlen (ptr)==1) && valid_suffix (*ptr))
-        {
-          /* This is a potentially valid suffix, so try to be helpful */
-         error (EXIT_FAILURE,0,_("not accepting suffix '%s' in input '%s'" \
-                               " (consider using --from)"), ptr, str);
-        }
-      else
-        {
-         /* Invalid trailing characters */
-         error (EXIT_FAILURE,0,_("invalid suffix in input '%s': '%s'"),
-                str, ptr);
-        }
-    }
+  if ( ptr && *ptr!='\0' )
+       error (EXIT_FAILURE,0,_("invalid suffix in input '%s': '%s'"),
+              str, ptr);
   return val;
 }
 
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index b7058b0..44979a7 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -71,15 +71,15 @@ my @Tests =
      # Test Suffix logic
      ['suf-1', '4000',    {OUT=>'4000'}],
      ['suf-2', '4Q',
-             {ERR => "$prog: invalid suffix in input '4Q': 'Q'\n"},
+             {ERR => "$prog: invalid suffix in input: '4Q'\n"},
              {EXIT => '1'}],
      ['suf-2.1', '4M',
-             {ERR => "$prog: not accepting suffix 'M' " .
-             "in input '4M' (consider using --from)\n"},
+             {ERR => "$prog: rejecting suffix " .
+             "in input: '4M' (consider using --from)\n"},
              {EXIT => '1'}],
      ['suf-3', '--from=si 4M',  {OUT=>'4000000'}],
      ['suf-4', '--from=si 4Q',
-             {ERR => "$prog: invalid suffix in input '4Q': 'Q'\n"},
+             {ERR => "$prog: invalid suffix in input: '4Q'\n"},
              {EXIT => '1'}],
      ['suf-5', '--from=si 4MQ',
              {ERR => "$prog: invalid suffix in input '4MQ': 'Q'\n"},
@@ -92,7 +92,7 @@ my @Tests =
              {ERR => "$prog: invalid suffix in input '4MiQ': 'Q'\n"},
              {EXIT => '1'}],
      ['suf-10', '--from=auto 4QiQ',
-             {ERR => "$prog: invalid suffix in input '4QiQ': 'QiQ'\n"},
+             {ERR => "$prog: invalid suffix in input: '4QiQ'\n"},
              {EXIT => '1'}],
 
      # characters after a white space are OK - printed as-is
@@ -105,10 +105,10 @@ my @Tests =
      ['suf-15', '--suffix=Foo --from=si 70KFoo',    {OUT=>'70000Foo'}],
      ['suf-16', '--suffix=Foo --to=si   7000Foo',    {OUT=>'7.0kFoo'}],
      ['suf-17', '--suffix=Foo --to=si   7000Bar',
-              {ERR => "$prog: invalid suffix in input '7000Bar': 'Bar'\n"},
+              {ERR => "$prog: invalid suffix in input: '7000Bar'\n"},
               {EXIT => '1'}],
      ['suf-18', '--suffix=Foo --to=si   7000FooF',
-              {ERR => "$prog: invalid suffix in input '7000FooF': 'FooF'\n"},
+              {ERR => "$prog: invalid suffix in input: '7000FooF'\n"},
               {EXIT => '1'}],
 
      ## GROUPING
@@ -158,7 +158,7 @@ my @Tests =
              {OUT=>'Hello 40000000 World 90G'}],
      ['field-3', '--field 3 --from=auto "Hello 40M World 90G"',
              {OUT=>"Hello 40M "},
-             {ERR=>"$prog: invalid input number 'World'\n"},
+             {ERR=>"$prog: no digits found: 'World'\n"},
              {EXIT => 1},],
      # Last field - no text after number
      ['field-4', '--field 4 --from=auto "Hello 40M World 90G"',
@@ -175,7 +175,7 @@ my @Tests =
      ['field-7', '--field 3 --to=si "Hello World"', {OUT=>"Hello World"}],
      ['field-8', '--field 3 --debug --to=si "Hello World"',
              {OUT=>"Hello World"},
-             {ERR=>"$prog: Input line is too short, no numbers found " .
+             {ERR=>"$prog: input line is too short, no numbers found " .
                    "to convert in field 3\n"}],
 
 
@@ -248,6 +248,70 @@ my @Tests =
              {OUT=>"hello\nworld\nsize\n4.9K\n88K"}],
 
 
+     ## human_strtod testing
+
+     # NO_DIGITS_FOUND
+     ['strtod-1', '--from=si "foo"',
+             {ERR=>"$prog: no digits found: 'foo'\n"},
+             {EXIT=> 1}],
+     ['strtod-2', '--from=si ""',
+             {ERR=>"$prog: no digits found: ''\n"},
+             {EXIT=> 1}],
+
+     # INTEGRAL_OVERFLOW
+     ['strtod-3', '--from=si "1234567890123456789012345678901234567890'.
+                  '1234567890123456789012345678901234567890"',
+             {ERR=>"$prog: value too large to be converted: '" .
+                     "1234567890123456789012345678901234567890" .
+                     "1234567890123456789012345678901234567890'\n",
+                     },
+             {EXIT=> 1}],
+
+     #FRACTION_FORBIDDEN_WITHOUT_SCALING
+     ['strtod-4', '12.2',
+             {ERR=>"$prog: cannot process decimal-point value without " .
+                     "scaling: '12.2' (consider using --from)\n"},
+             {EXIT=>1}],
+
+     # FRACTION_NO_DIGITS_FOUND
+     ['strtod-5', '--from=si 12.',
+             {ERR=>"$prog: no digits in fraction: '12.'\n"},
+             {EXIT=>1}],
+     ['strtod-6', '--from=si 12.K',
+             {ERR=>"$prog: no digits in fraction: '12.K'\n"},
+             {EXIT=>1}],
+
+     # whitespace is not allowed after decimal-point
+     ['strtod-6.1', '--from=si --delimiter=, "12.  2"',
+             {ERR=>"$prog: no digits in fraction: '12.  2'\n"},
+             {EXIT=>1}],
+
+     # FRACTION_OVERFLOW
+     ['strtod-7', '--from=si "12.1234567890123456789012345678901234567890'.
+                  '1234567890123456789012345678901234567890"',
+             {ERR=>"$prog: fraction value too large to be converted: '" .
+                     "12.1234567890123456789012345678901234567890" .
+                     "1234567890123456789012345678901234567890'\n",
+                     },
+             {EXIT=> 1}],
+
+     # FRACTION_REQUIRES_SUFFIX
+     ['strtod-8', '--from=si 12.2',
+             {ERR=>"$prog: decimal-point values require a suffix" .
+                    " (e.g. K/M/G/T): '12.2'\n"},
+             {EXIT=>1}],
+
+     # INVALID_SUFFIX
+     ['strtod-9', '--from=si 12.2Q',
+             {ERR=>"$prog: invalid suffix in input: '12.2Q'\n"},
+             {EXIT=>1}],
+
+     # VALID_BUT_FORBIDDEN_SUFFIX
+     ['strtod-10', '12M',
+             {ERR => "$prog: rejecting suffix " .
+                     "in input: '12M' (consider using --from)\n"},
+             {EXIT=>1}],
+
     );
 
 my @Locale_Tests =
@@ -259,6 +323,10 @@ my @Locale_Tests =
      # Locale with grouping
      ['lcl-grp-2', '--from=si --grouping 7M',   {OUT=>"7 000 000"},
              {ENV=>"LC_ALL=$locale"}],
+
+     # Input with locale'd decimal-point
+     ['lcl-stdtod-1', '--from=si 12,2K', {OUT=>"12200"},
+             {ENV=>"LC_ALL=$locale"}],
   );
 push @Tests, @Locale_Tests if $locale ne "C";
 
-- 
1.7.7.4


From 369a55c0d2eee171091edb304d4ea960f885e6d2 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Wed, 12 Dec 2012 17:55:01 -0500
Subject: [PATCH 09/16] numfmt: rewrite output functions with double.

---
 src/numfmt.c         |  201 ++++++++++++++++++++++++++++++++++++++++++++++----
 tests/misc/numfmt.pl |   45 ++++++------
 2 files changed, 209 insertions(+), 37 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 30baee7..8e2ddac 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -141,8 +141,6 @@ static int debug=0;
 /* debugging for developers - to be removed in final version? */
 static int dev_debug=0;
 
-#define __strtol_t uintmax_t
-
 static inline int
 valid_suffix ( const char suf )
 {
@@ -185,6 +183,44 @@ suffix_power ( const char suf )
     }
 }
 
+static inline const char*
+suffix_power_character ( unsigned int power )
+{
+  switch (power)
+    {
+    case 0:
+      return "";
+
+    case 1:
+      return "K";
+
+    case 2:
+      return "M";
+
+    case 3:
+      return "G";
+
+    case 4:
+      return "T";
+
+    case 5:
+      return "P";
+
+    case 6:
+      return "E";
+
+    case 7:
+      return "Z";
+
+    case 8:
+      return "Y";
+
+    default:
+      return "(error)";
+    }
+}
+
+/* Similar to 'powl(3)' but without requiring 'libm' */
 static long double
 powerld (long double base, unsigned int x)
 {
@@ -197,6 +233,70 @@ powerld (long double base, unsigned int x)
   return result;
 }
 
+/* scales down 'val', returns 'updated val' and 'x', such that
+ *   val*base^X = original val
+ *   Similar to "frexpl(3)" but without requiring 'libm',
+ *   allowing only integer scale, limited functionlity and error checking.*/
+static long double
+expld (long double val, unsigned int base, unsigned int /*output*/ *x)
+{
+  unsigned int power=0;
+  while (val>=base)
+    {
+      ++power;
+      val /= base;
+    }
+  if (x)
+    *x = power;
+  return val;
+}
+
+/* EXTREMELY limited 'round' - without 'libm' .
+ * Assumes only positive, small  values */
+static inline int
+simple_round_nearest (long double val)
+{
+  return (int)(val+0.5);
+}
+
+/* EXTREMELY limited 'floor' - without 'libm' .
+ * Assumes only positive, small  values */
+static inline int
+simple_round_floor (long double val)
+{
+  return (int)val;
+}
+
+/* EXTREMELY limited 'ceil' - without 'libm' .
+ * Assumes only positive, small  values */
+static inline int
+simple_round_ceiling (long double val)
+{
+  return simple_round_floor (val)
+         + (((val - simple_round_floor (val))>0) ? 1 : 0);
+}
+
+static inline int
+__attribute ((pure))
+simple_round (long double val, enum round_type t)
+{
+  switch (t)
+    {
+    case round_ceiling:
+      return simple_round_ceiling (val);
+
+    case round_floor:
+      return simple_round_floor (val);
+
+    case round_nearest:
+      return simple_round_nearest (val);
+
+    default:
+      abort ();
+    }
+}
+
+
 enum human_strtod_error
 {
   OK=0,
@@ -395,6 +495,79 @@ human_strtod_fatal (enum human_strtod_error err,
   error (EXIT_FAILURE,0,gettext (msgid), input_str);
 }
 
+static char*
+double_to_human (long double val,
+                      char* buf, size_t buf_size,
+                      enum scale_type scale,
+                      int group,
+                      enum round_type round)
+{
+  if (dev_debug)
+    fprintf (stderr,"double_to_human:\n");
+
+  if (scale==scale_none)
+    {
+      if (dev_debug)
+          fprintf (stderr,
+                   (group)?"  no scaling, returning (grouped) value: %'.0Lf\n":
+                           "  no scaling, returning value: %.0Lf\n",
+                   val);
+
+      int i = snprintf (buf,buf_size,(group)?"%'.0Lf":"%.0Lf",val);
+      if (i<0 || i>=(int)buf_size)
+        error (EXIT_FAILURE,0,_("failed to prepare value '%Lf' for printing"),
+              val);
+      return buf;
+    }
+
+  /* Scaling requested by user */
+  double scale_base=(scale==scale_SI)?1000:1024;
+
+  /* Normalize val to scale */
+  unsigned int power=0;
+  val = expld (val, scale_base, &power);
+  if (dev_debug)
+    fprintf (stderr,"  scaled value to %Lf * %0.f ^ %d\n",
+             val, scale_base, power);
+
+  /* Perform rounding */
+  int ten_or_less = 0;
+  if (val<10)
+    {
+      /* for values less than 10, we allow one decimal-point digit,
+       * so adjust before rounding */
+      ten_or_less = 1;
+      val *= 10;
+    }
+  val = simple_round (val, round);
+  /* two special cases after rounding:
+   * 1. a "999.99" can turn into 1000 - so scale down
+   * 2. a "9.99" can turn into 10 - so don't display decimal-point
+   */
+  if (val>=scale_base)
+    {
+      val /= scale_base;
+      power++;
+    }
+  if (ten_or_less)
+    {
+      val /= 10;
+      if (val>=10)
+        ten_or_less = 0 ;
+    }
+  if (dev_debug)
+    fprintf (stderr,"  after rounding, value=%Lf * %0.f ^ %d\n",
+             val, scale_base, power);
+
+  snprintf (buf,buf_size, (ten_or_less)?"%.1Lf%s":"%.0Lf%s",
+           val,suffix_power_character (power));
+
+
+  if (dev_debug)
+    fprintf (stderr,"  returning value: '%s'\n", buf);
+
+  return buf;
+}
 
 /* Convert a string of decimal digits, N_STRING, with an optional suffinx
    to an integral value.  Upon successful conversion,
@@ -524,21 +697,19 @@ chomp (char *s)
    The input string must not contain any extra characters
    (except valid suffixes if 'from' scaling is enabled).
 */
-static __strtol_t
+static long double
 parse_number (const char* str)
 {
   char *ptr=NULL;
-  __strtol_t val=0;
 
-  long double dval=0;
-  enum human_strtod_error err = human_strtod (str, &ptr, &dval, scale_from);
+  long double val=0;
+  enum human_strtod_error err = human_strtod (str, &ptr, &val, scale_from);
   if (err != OK)
     human_strtod_fatal (err,str);
-  val=(__strtol_t)dval; /* NOTE: Possible data loss here, double->uintmax_t */
 
   if (dev_debug)
     fprintf (stderr,"Parsing number:\n  input string: '%s'\n  " \
-            "remaining characters: '%s'\n  numeric value: %zu\n",
+            "remaining characters: '%s'\n  numeric value: %Lf\n",
             str,ptr,val);
 
   if ( ptr && *ptr!='\0' )
@@ -554,15 +725,15 @@ parse_number (const char* str)
    with padding and alignment.
 */
 static void
-print_number (const __strtol_t val)
+print_number (const long double val)
 {
   /* Generate Output */
-  char buf[LONGEST_HUMAN_READABLE + 1];
-  char *s = human_readable (val, buf, human_print_options,
-                           from_unit_size,to_unit_size);
+  char buf[128];
+  char *s = double_to_human (val, buf, sizeof (buf),
+                             scale_to, grouping, _round);
 
   if (dev_debug)
-    fprintf (stderr,"Formatting output:\n  value: %zu\n  humanized: '%s'\n",
+    fprintf (stderr,"Formatting output:\n  value: %Lf\n  humanized: '%s'\n",
             val,s);
 
   if (padding_width)
@@ -610,7 +781,9 @@ process_suffixed_number (char* text)
         }
     }
 
-  const __strtol_t val = parse_number (text);
+  long double val = parse_number (text);
+  if (from_unit_size!=1 || to_unit_size != 1)
+          val = (val * from_unit_size) / to_unit_size;
   print_number (val);
 
   if (suffix)
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index 44979a7..d2e1c34 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -40,17 +40,16 @@ my @Tests =
      ['6', {IN_PIPE => "1234"},            {OUT => "1234"}],
      ['7', '--from=si', {IN_PIPE => "2K"}, {OUT => "2000"}],
 
-     #NOTE: "SI" outputs lower "k"?
-     ['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=floor   2001',   {OUT => "2.0k"}],
-     ['12', '--to=si --round=floor   1999',   {OUT => "1.9k"}],
-     ['13', '--to=si --round=ceiling 1901',   {OUT => "2.0k"}],
-     ['14', '--to=si --round=floor   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=floor   2001',   {OUT => "2.0K"}],
+     ['12', '--to=si --round=floor   1999',   {OUT => "1.9K"}],
+     ['13', '--to=si --round=ceiling 1901',   {OUT => "2.0K"}],
+     ['14', '--to=si --round=floor   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"}],
@@ -62,11 +61,11 @@ my @Tests =
      ['25', '--to=iec --round=nearest 1996',   {OUT => "1.9K"}],
      ['26', '--to=iec --round=nearest 1997',   {OUT => "2.0K"}],
 
-     ['27', '--from-unit=512 4',   {OUT => "2048"}],
-     ['28', '--to-unit=512 2048',   {OUT => "4"}],
+     ['unit-1', '--from-unit=512 4',   {OUT => "2048"}],
+     ['unit-2', '--to-unit=512 2048',   {OUT => "4"}],
 
-     ['29', '--from-unit=512 --from=si 4M',   {OUT => "2048000000"}],
-     ['30', '--from-unit=512 --from=iec --to=iec 4M',   {OUT => "2.0G"}],
+     ['unit-3', '--from-unit=512 --from=si 4M',   {OUT => "2048000000"}],
+     ['unit-4', '--from-unit=512 --from=iec --to=iec 4M',   {OUT => "2.0G"}],
 
      # Test Suffix logic
      ['suf-1', '4000',    {OUT=>'4000'}],
@@ -103,7 +102,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 => '1'}],
@@ -126,20 +125,20 @@ my @Tests =
      ['pad-3', '--padding=A 5',
              {ERR => "$prog: invalid padding value 'A'\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 causing truncation, data loss
      ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.'},
-             {ERR => "$prog: value '1.0k' truncated due to padding, " .
+             {ERR => "$prog: value '1.0K' truncated due to padding, " .
                 "possible data loss\n"}],
 
 
      # 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=>'50k       foo'}],
+             {OUT=>'50K       foo'}],
 
 
      # Delimiters
@@ -215,10 +214,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"}],
 
 
 
-- 
1.7.7.4


From d13ffdf6e1747b5a6af1a7d21ac872af127ab8ba Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Wed, 12 Dec 2012 19:02:30 -0500
Subject: [PATCH 10/16] numfmt: bugfix + many more tests

---
 src/numfmt.c         |   12 ++++---
 tests/misc/numfmt.pl |   81 ++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 86 insertions(+), 7 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 8e2ddac..6b086c6 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -550,16 +550,18 @@ double_to_human (long double val,
       power++;
     }
   if (ten_or_less)
-    {
       val /= 10;
-      if (val>=10)
-        ten_or_less = 0 ;
-    }
+
+  /* should "7.0" be printed as "7" ?
+   * if removing the ".0" is preffered, enable the third condition */
+  int show_decimal_point = (val!=0) && (val<10) ;
+                                /* && (val>simple_round_floor (val)))*/
+
   if (dev_debug)
     fprintf (stderr,"  after rounding, value=%Lf * %0.f ^ %d\n",
              val, scale_base, power);
 
-  snprintf (buf,buf_size, (ten_or_less)?"%.1Lf%s":"%.0Lf%s",
+  snprintf (buf,buf_size, (show_decimal_point)?"%.1Lf%s":"%.0Lf%s",
            val,suffix_power_character (power));
 
 
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index d2e1c34..983305a 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -224,10 +224,10 @@ my @Tests =
      ## Header testing
 
      # header - silently ignored with command line parameters
-     ['header-1', '--header --to=iec 4000', {OUT=>"4.0K"}],
+     ['header-1', '--header --to=iec 4096', {OUT=>"4.0K"}],
 
      # header warning with --debug
-     ['header-2', '--debug --header --to=iec 4000', {OUT=>"4.0K"},
+     ['header-2', '--debug --header --to=iec 4096', {OUT=>"4.0K"},
              {ERR=>"$prog: --header ignored with command-line input\n"}],
 
      ['header-3', '--header=A',
@@ -311,6 +311,80 @@ my @Tests =
                      "in input: '12M' (consider using --from)\n"},
              {EXIT=>1}],
 
+     #
+     # Test double_to_human()
+     #
+
+     # 1K and smaller
+     ['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"}],
+     #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"}],
+     ['dbl-to-human-2.5','--to=iec 1024',  {OUT=>"1.0K"}],
+     ['dbl-to-human-2.6','--to=iec 1025',  {OUT=>"1.1K"}],
+
+     # 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=floor 8001', {OUT=>"8.0K"}],
+
+     ['dbl-to-human-5','--to=si --round=floor 3500', {OUT=>"3.5K"}],
+     ['dbl-to-human-6','--to=si --round=nearest 3500', {OUT=>"3.5K"}],
+     ['dbl-to-human-7','--to=si --round=ceiling 3500', {OUT=>"3.5K"}],
+
+     ['dbl-to-human-8','--to=si --round=floor    3501', {OUT=>"3.5K"}],
+     ['dbl-to-human-9','--to=si --round=nearest  3501', {OUT=>"3.5K"}],
+     ['dbl-to-human-10','--to=si --round=ceiling 3501', {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-15','--to=iec --from=si 3.3K', {OUT=>"3.3K"}],
+     ['dbl-to-human-16','--to=iec --round=floor --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=floor 35000', {OUT=>"35K"}],
+     ['dbl-to-human-19','--to=iec 35000', {OUT=>"35K"}],
+     ['dbl-to-human-20','--to=iec --round=floor 35000', {OUT=>"34K"}],
+     ['dbl-to-human-21','--to=iec 35000000', {OUT=>"34M"}],
+     ['dbl-to-human-22','--to=iec --round=floor 35000000', {OUT=>"33M"}],
+     ['dbl-to-human-23','--to=si  35000001', {OUT=>"36M"}],
+     ['dbl-to-human-24','--to=si --from=si  9.99M', {OUT=>"10M"}],
+     ['dbl-to-human-25','--to=si --from=iec 9.99M', {OUT=>"11M"}],
+     ['dbl-to-human-25.1','--to=iec 99999', {OUT=>"98K"}],
+
+     # values resulting in 'NNNx' output
+     ['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-32','--to=iec 102399', {OUT=>"100K"}],
+
+
+     # Large Values
+     ['large-1','1000000000000000', {OUT=>"1000000000000000"}],
+     ['large-2','1000000000000000000', {OUT=>"1000000000000000000"}],
+
+     #FIX ME: can't read numbers so big - strtoumax fails
+#     ['large-3','1000000000000000000000', {OUT=>"1000000000000000000000"}],
+
+     ['large-4','--to=si 1000000000000000000', {OUT=>"1.0E"}],
+     ['large-5','--from=si --to=si 2E', {OUT=>"2.0E"}],
+     ['large-6','--from=si --to=si 3.4Z', {OUT=>"3.4Z"}],
+     ['large-7','--from=si --to=si 80Y', {OUT=>"80Y"}],
+     ['large-8','--from=si --to=si 9000Z', {OUT=>"9.0Y"}],
+     ['large-9','--from=si 9.7Y', {OUT=>"9700000000000000000000000"}],
+
+     #FIXME: can't handle such big numbres
+#     ['large-10','--from=si 999Y', {OUT=>"9700000000000000000000000"}],
+
     );
 
 my @Locale_Tests =
@@ -326,6 +400,9 @@ my @Locale_Tests =
      # Input with locale'd decimal-point
      ['lcl-stdtod-1', '--from=si 12,2K', {OUT=>"12200"},
              {ENV=>"LC_ALL=$locale"}],
+
+     ['lcl-dbl-to-human-1', '--to=si 1100', {OUT=>"1,1K"},
+             {ENV=>"LC_ALL=$locale"}],
   );
 push @Tests, @Locale_Tests if $locale ne "C";
 
-- 
1.7.7.4


From a95bc4f035615279f63f5951530826fc953a859c Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Wed, 12 Dec 2012 21:59:00 -0500
Subject: [PATCH 11/16] numfmt: rewrite of string-to-num.

Complete re-implementation of all 'strtoumax' and related
functions.
---
 src/numfmt.c         |  256 +++++++++++++++++++++++++++++---------------------
 tests/misc/numfmt.pl |   14 ++--
 2 files changed, 154 insertions(+), 116 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 6b086c6..1cf1b54 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -141,6 +141,10 @@ static int debug=0;
 /* debugging for developers - to be removed in final version? */
 static int dev_debug=0;
 
+/* will be set according to the current locale */
+static const char *decimal_point;
+static int decimal_point_length;
+
 static inline int
 valid_suffix ( const char suf )
 {
@@ -296,118 +300,159 @@ simple_round (long double val, enum round_type t)
     }
 }
 
+/* This function reads an *integer* string,
+ * but returns the integer value in a 'long double'
+ * hence, no 2^64 limitation.
+ * other limitations: no locale'd grouping, no skipping white-space
+ *
+ *      999,000,000,000,000,000,000,000
+ *         Y   Z   E   P   T   G   K
+ *
+ * Returns:
+ *    SSE_OK - valid number.
+ *    SSE_OK_PRECISION_LOSS - if more than 18 digits were used.
+ *    SSE_OVERFLOW          - if more than 24 digits (999Y) were used.
+ *    SSE_INVALID_NUMBER    - if no digits were found.
+ */
+enum simple_strtod_error
+{
+  SSE_OK=0,
+  SSE_OK_PRECISION_LOSS,
+  SSE_OVERFLOW,
+  SSE_INVALID_NUMBER,
+
+  /* the following are returned by 'simple_strtod_human' */
+  SSE_FRACTION_FORBIDDEN_WITHOUT_SCALING,
+  SSE_FRACTION_REQUIRES_SUFFIX,
+  SSE_VALID_BUT_FORBIDDEN_SUFFIX,
+  SSE_INVALID_SUFFIX
+};
 
-enum human_strtod_error
+static enum simple_strtod_error
+simple_strtod_int (const char *input_str,
+                   char* /*output*/ *ptr, /*required, unlike in stdtod */
+                   long double /*output*/ *value)
 {
-  OK=0,
-  NO_DIGITS_FOUND,
-  INTEGRAL_OVERFLOW,
-  INVALID_NUMBER,
-  FRACTION_FORBIDDEN_WITHOUT_SCALING,
-  FRACTION_NO_DIGITS_FOUND,
-  FRACTION_OVERFLOW,
-  FRACTION_INVALID_NUMBER,
-  FRACTION_REQUIRES_SUFFIX,
-  VALID_BUT_FORBIDDEN_SUFFIX,
-  INVALID_SUFFIX,
+  enum simple_strtod_error e = SSE_OK;
 
-};
+  long double val=0;
+  unsigned int digits=0;
+  *ptr = (char*)input_str;
+  while ( (*ptr) && isdigit ((**ptr)) )
+    {
+      int digit = (**ptr) - '0';
+      if (digit<0 || digit>9)
+        return SSE_INVALID_NUMBER; /* can this happen in some strange locale? */
+
+      if (digits>18)
+        e = SSE_OK_PRECISION_LOSS;
+      if (digits>24)
+        return SSE_OVERFLOW;
+
+      val *= 10;
+      val += digit;
+
+      ++digits;
+      ++(*ptr);
+    }
+  if (digits==0)
+    return SSE_INVALID_NUMBER;
+  if (value)
+    *value = val;
+
+  return e;
+}
+
+/*
+ * Accepts a floating-point value, represented as "NNNN[.NNNNN]" .
+ */
+static enum simple_strtod_error
+simple_strtod_float (const char *input_str,
+                   char* /*output*/ *ptr, /*required, unlike in stdtod */
+                   long double /*output*/ *value,
+                   int /*output*/ *have_fractions)
+{
+  enum simple_strtod_error e = SSE_OK;
+
+  *have_fractions = 0 ;
+
+  /* TODO: accept locale'd grouped values for the integral part */
+  e = simple_strtod_int (input_str, ptr, value);
+  if (e!=SSE_OK && e!=SSE_OK_PRECISION_LOSS)
+    return e;
+
+  /* optional decimal point + fraction */
+  if (STREQ_LEN (*ptr,decimal_point,decimal_point_length))
+    {
+      char *ptr2;
+      long double val_frac=0;
+
+      (*ptr) += decimal_point_length;
+      enum simple_strtod_error e2 = simple_strtod_int (*ptr,&ptr2,&val_frac);
+      if (e2!=SSE_OK && e!=SSE_OK_PRECISION_LOSS)
+        return e2;
+      if (e2==SSE_OK_PRECISION_LOSS)
+        e = e2 ; /* propegate warning */
+
+      int exponent = ptr2-*ptr; /* number of digits in the fractions */
+      val_frac = ((long double)val_frac)/powerld (10,exponent);
+
+      *value += val_frac;
+
+      *have_fractions = 1 ;
+
+      *ptr=ptr2;
+    }
+  return e;
+}
 
-static enum human_strtod_error
-human_strtod (const char *input_str,
+static enum simple_strtod_error
+simple_strtod_human (const char *input_str,
               char* /*output*/ *ptr, /*required, unlike in stdtod */
               long double /*output*/ *value, /* required */
               enum scale_type allowed_scaling)
 {
-  char *pi, *pf;
-  uintmax_t integral,fraction=0;
   int have_fractions=0;
   int power=0;
   /* 'scale_auto' is checked below */
   int scale_base=(allowed_scaling==scale_IEC)?1024:1000;
-  const int base = 10 ;
-
-  const char *decimal_point = nl_langinfo (RADIXCHAR);
-  if (decimal_point==NULL || strlen (decimal_point)==0)
-    decimal_point = ".";
-  const int decimal_point_length=strlen (decimal_point);
 
   if (dev_debug)
-    fprintf (stderr,"string-to-double:\n  input string: '%s'\n  " \
+    fprintf (stderr,"simple_stdtod_human:\n  input string: '%s'\n  " \
             "locale decimal-point: '%s'\n", input_str,decimal_point);
 
-  errno = 0;
-  /* TODO: accept locale'd grouped values for the integral part */
-  integral = strtoumax (input_str, &pi, base);
-  if (pi==input_str)
-    return NO_DIGITS_FOUND;
-  if (errno==ERANGE)
-    return INTEGRAL_OVERFLOW;
-  if (errno!=0)
-    return INVALID_NUMBER; /* should never happen? */
-
-  *value = integral;
-
-  *ptr = pi;
+  enum simple_strtod_error e =
+        simple_strtod_float (input_str, ptr, value,&have_fractions );
+  if (e!=SSE_OK && e!=SSE_OK_PRECISION_LOSS)
+    return e;
 
   if (dev_debug)
-    fprintf (stderr,"  integral-part: %lu\n", integral);
+    fprintf (stderr,"  parsed numeric value: %Lf\n" \
+                    "  have_fractions = %d\n", *value, have_fractions);
 
-  /* optional decimal point + fraction */
-  if (STREQ_LEN (pi,decimal_point,decimal_point_length))
-    {
-      if (allowed_scaling==scale_none)
-        return FRACTION_FORBIDDEN_WITHOUT_SCALING;
-
-      errno = 0 ;
-      pi += decimal_point_length;
-      if (!isdigit (*pi))
-        return FRACTION_NO_DIGITS_FOUND;
-      fraction = strtoumax (pi, &pf, base);
-      if (pf==pi)
-        return FRACTION_NO_DIGITS_FOUND;
-      if (errno==ERANGE)
-        return FRACTION_OVERFLOW;
-      if (errno!=0)
-        return FRACTION_INVALID_NUMBER;
-
-      int exponent = pf-pi;
-      long double f = ((long double)fraction)/powerld (10,exponent);
-
-      have_fractions=1;
-
-      if (dev_debug)
-        fprintf (stderr,"  fraction-part: %lu (exponent=%d)\n" \
-                       "  fraction-value: %Lf\n",
-              fraction,exponent,f);
+  if (have_fractions && allowed_scaling==scale_none)
+        return SSE_FRACTION_FORBIDDEN_WITHOUT_SCALING;
 
-      *value += f;
-
-      if (dev_debug)
-        fprintf (stderr,"  combined-value: %Lf\n", *value);
-
-      *ptr= pf;
-    }
 
   /* no suffix / trailing characters */
   if (**ptr=='\0')
     {
       if (have_fractions)
-        return FRACTION_REQUIRES_SUFFIX;
+        return SSE_FRACTION_REQUIRES_SUFFIX;
 
       if (dev_debug)
         fprintf (stderr,"  no fraction,suffix detected\n" \
-                       "  returning value: %Lf\n", *value);
+                       "  returning value: %Lf (%LG)\n", *value, *value);
 
-      return OK;
+      return SSE_OK;
     }
 
   /* process suffix */
   if (!valid_suffix (**ptr))
-    return INVALID_SUFFIX;
+    return SSE_INVALID_SUFFIX;
 
   if (allowed_scaling==scale_none)
-    return VALID_BUT_FORBIDDEN_SUFFIX;
+    return SSE_VALID_BUT_FORBIDDEN_SUFFIX;
 
   power = suffix_power (**ptr);
   (*ptr)++; /* skip first suffix character */
@@ -429,64 +474,50 @@ human_strtod (const char *input_str,
     fprintf (stderr, "  suffix power=%d^%d = %Lf\n",
           scale_base,power,multiplier);
 
+  /* TODO: detect loss of precision and overflows */
   (*value) = (*value) * multiplier;
 
   if (dev_debug)
-    fprintf (stderr, "  returning value: %Lf\n", *value);
+    fprintf (stderr,"  returning value: %Lf (%LG)\n", *value, *value);
 
-  return OK;
+  return SSE_OK;
 }
 
 
 static void
-human_strtod_fatal (enum human_strtod_error err,
+simple_strtod_fatal (enum simple_strtod_error err,
                      char const *input_str)
 {
-  char const *msgid;
+  char const *msgid=NULL;
 
   switch (err)
     {
-    default:
+    case SSE_OK_PRECISION_LOSS:
+    case SSE_OK:
       abort ();
 
-    case NO_DIGITS_FOUND:
-      msgid = N_("no digits found: '%s'");
-      break;
-
-    case INTEGRAL_OVERFLOW:
+    case SSE_OVERFLOW:
       msgid = N_("value too large to be converted: '%s'");
       break;
 
-    case INVALID_NUMBER:
+    case SSE_INVALID_NUMBER:
       msgid = N_("invalid number: '%s'");
       break;
 
-    case FRACTION_FORBIDDEN_WITHOUT_SCALING:
+    case SSE_FRACTION_FORBIDDEN_WITHOUT_SCALING:
       msgid = N_("cannot process decimal-point value without scaling: '%s'" \
                  " (consider using --from)");
       break;
 
-    case FRACTION_NO_DIGITS_FOUND:
-      msgid = N_("no digits in fraction: '%s'");
-      break;
-
-    case FRACTION_OVERFLOW:
-      msgid = N_("fraction value too large to be converted: '%s'");
-      break;
-
-    case FRACTION_INVALID_NUMBER:
-      msgid = N_("invalid fraction number: '%s'");
-      break;
-
-    case FRACTION_REQUIRES_SUFFIX:
+    case SSE_FRACTION_REQUIRES_SUFFIX:
       msgid = N_("decimal-point values require a suffix (e.g. K/M/G/T): '%s'");
       break;
 
-    case VALID_BUT_FORBIDDEN_SUFFIX:
+    case SSE_VALID_BUT_FORBIDDEN_SUFFIX:
       msgid = N_("rejecting suffix in input: '%s' (consider using --from)");
       break;
 
-    case INVALID_SUFFIX:
+    case SSE_INVALID_SUFFIX:
       msgid = N_("invalid suffix in input: '%s'");
       break;
 
@@ -705,14 +736,15 @@ parse_number (const char* str)
   char *ptr=NULL;
 
   long double val=0;
-  enum human_strtod_error err = human_strtod (str, &ptr, &val, scale_from);
-  if (err != OK)
-    human_strtod_fatal (err,str);
+  enum simple_strtod_error e =
+    simple_strtod_human (str, &ptr, &val, scale_from);
+  if (e!= SSE_OK && e!=SSE_OK_PRECISION_LOSS)
+    simple_strtod_fatal (e,str);
 
   if (dev_debug)
     fprintf (stderr,"Parsing number:\n  input string: '%s'\n  " \
-            "remaining characters: '%s'\n  numeric value: %Lf\n",
-            str,ptr,val);
+            "remaining characters: '%s'\n  numeric value: %Lf (%LG)\n",
+            str,ptr,val,val);
 
   if ( ptr && *ptr!='\0' )
        error (EXIT_FAILURE,0,_("invalid suffix in input '%s': '%s'"),
@@ -919,6 +951,11 @@ main (int argc, char **argv)
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
 
+  decimal_point = nl_langinfo (RADIXCHAR);
+  if (decimal_point==NULL || strlen (decimal_point)==0)
+    decimal_point = ".";
+  decimal_point_length=strlen (decimal_point);
+
   atexit (close_stdout);
 
   while (true)
@@ -992,6 +1029,7 @@ main (int argc, char **argv)
 
         case DEV_DEBUG_OPTION:
           dev_debug=1;
+          debug=1;
           break;
 
         case HEADER_OPTION:
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index 983305a..3508275 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -157,7 +157,7 @@ my @Tests =
              {OUT=>'Hello 40000000 World 90G'}],
      ['field-3', '--field 3 --from=auto "Hello 40M World 90G"',
              {OUT=>"Hello 40M "},
-             {ERR=>"$prog: no digits found: 'World'\n"},
+             {ERR=>"$prog: invalid number: 'World'\n"},
              {EXIT => 1},],
      # Last field - no text after number
      ['field-4', '--field 4 --from=auto "Hello 40M World 90G"',
@@ -251,10 +251,10 @@ my @Tests =
 
      # NO_DIGITS_FOUND
      ['strtod-1', '--from=si "foo"',
-             {ERR=>"$prog: no digits found: 'foo'\n"},
+             {ERR=>"$prog: invalid number: 'foo'\n"},
              {EXIT=> 1}],
      ['strtod-2', '--from=si ""',
-             {ERR=>"$prog: no digits found: ''\n"},
+             {ERR=>"$prog: invalid number: ''\n"},
              {EXIT=> 1}],
 
      # INTEGRAL_OVERFLOW
@@ -274,21 +274,21 @@ my @Tests =
 
      # FRACTION_NO_DIGITS_FOUND
      ['strtod-5', '--from=si 12.',
-             {ERR=>"$prog: no digits in fraction: '12.'\n"},
+             {ERR=>"$prog: invalid number: '12.'\n"},
              {EXIT=>1}],
      ['strtod-6', '--from=si 12.K',
-             {ERR=>"$prog: no digits in fraction: '12.K'\n"},
+             {ERR=>"$prog: invalid number: '12.K'\n"},
              {EXIT=>1}],
 
      # whitespace is not allowed after decimal-point
      ['strtod-6.1', '--from=si --delimiter=, "12.  2"',
-             {ERR=>"$prog: no digits in fraction: '12.  2'\n"},
+             {ERR=>"$prog: invalid number: '12.  2'\n"},
              {EXIT=>1}],
 
      # FRACTION_OVERFLOW
      ['strtod-7', '--from=si "12.1234567890123456789012345678901234567890'.
                   '1234567890123456789012345678901234567890"',
-             {ERR=>"$prog: fraction value too large to be converted: '" .
+             {ERR=>"$prog: value too large to be converted: '" .
                      "12.1234567890123456789012345678901234567890" .
                      "1234567890123456789012345678901234567890'\n",
                      },
-- 
1.7.7.4


From 043d55acb8d5b55e04da510291d52068e58e3c03 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Wed, 12 Dec 2012 23:29:13 -0500
Subject: [PATCH 12/16] numfmt: bugfixes, and many more tests.

---
 src/numfmt.c         |   75 ++++++++++++++++++++-----------
 tests/misc/numfmt.pl |  120 +++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 162 insertions(+), 33 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 1cf1b54..ffa3ea8 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -117,6 +117,16 @@ static struct option const longopts[] =
 /* If delimtier has this value, blanks separate fields.  */
 enum { DELIMITER_DEFAULT = CHAR_MAX + 1 };
 
+/* Maximum number of digits we can safely handle
+ * without precision loss, if scaling is 'none' */
+enum { MAX_UNSCALED_DIGITS = 18 };
+
+/* Maximum number of digits we can work with.
+ * This is equivalent to 999Y.
+ * NOTE: 'long double' can handle more than that, but there's
+ *       no official suffix assigned beyond Yotta (1000^8) */
+enum { MAX_ACCEPTABLE_DIGITS = 27 } ;
+
 static enum scale_type scale_from=scale_none;
 static enum scale_type scale_to=scale_none;
 static enum round_type _round=round_ceiling;
@@ -311,7 +321,7 @@ simple_round (long double val, enum round_type t)
  * Returns:
  *    SSE_OK - valid number.
  *    SSE_OK_PRECISION_LOSS - if more than 18 digits were used.
- *    SSE_OVERFLOW          - if more than 24 digits (999Y) were used.
+ *    SSE_OVERFLOW          - if more than 27 digits (999Y) were used.
  *    SSE_INVALID_NUMBER    - if no digits were found.
  */
 enum simple_strtod_error
@@ -344,15 +354,16 @@ simple_strtod_int (const char *input_str,
       if (digit<0 || digit>9)
         return SSE_INVALID_NUMBER; /* can this happen in some strange locale? */
 
-      if (digits>18)
-        e = SSE_OK_PRECISION_LOSS;
-      if (digits>24)
+      if (digits>MAX_UNSCALED_DIGITS)
+           e = SSE_OK_PRECISION_LOSS;
+
+      ++digits;
+      if (digits>MAX_ACCEPTABLE_DIGITS)
         return SSE_OVERFLOW;
 
       val *= 10;
       val += digit;
 
-      ++digits;
       ++(*ptr);
     }
   if (digits==0)
@@ -381,6 +392,7 @@ simple_strtod_float (const char *input_str,
   if (e!=SSE_OK && e!=SSE_OK_PRECISION_LOSS)
     return e;
 
+
   /* optional decimal point + fraction */
   if (STREQ_LEN (*ptr,decimal_point,decimal_point_length))
     {
@@ -389,7 +401,7 @@ simple_strtod_float (const char *input_str,
 
       (*ptr) += decimal_point_length;
       enum simple_strtod_error e2 = simple_strtod_int (*ptr,&ptr2,&val_frac);
-      if (e2!=SSE_OK && e!=SSE_OK_PRECISION_LOSS)
+      if (e2!=SSE_OK && e2!=SSE_OK_PRECISION_LOSS)
         return e2;
       if (e2==SSE_OK_PRECISION_LOSS)
         e = e2 ; /* propegate warning */
@@ -444,7 +456,7 @@ simple_strtod_human (const char *input_str,
         fprintf (stderr,"  no fraction,suffix detected\n" \
                        "  returning value: %Lf (%LG)\n", *value, *value);
 
-      return SSE_OK;
+      return e;
     }
 
   /* process suffix */
@@ -480,7 +492,7 @@ simple_strtod_human (const char *input_str,
   if (dev_debug)
     fprintf (stderr,"  returning value: %Lf (%LG)\n", *value, *value);
 
-  return SSE_OK;
+  return e;
 }
 
 
@@ -584,8 +596,8 @@ double_to_human (long double val,
       val /= 10;
 
   /* should "7.0" be printed as "7" ?
-   * if removing the ".0" is preffered, enable the third condition */
-  int show_decimal_point = (val!=0) && (val<10) ;
+   * if removing the ".0" is preffered, enable the fourth condition */
+  int show_decimal_point = (val!=0) && (val<10) && (power>0) ;
                                 /* && (val>simple_round_floor (val)))*/
 
   if (dev_debug)
@@ -722,34 +734,29 @@ chomp (char *s)
 
 
 /*
-   Parsesa numeric value from a string.
-   Returns the integer value.
+   Parses a numeric value (with optional suffix) from a string.
+   Returns a long double value.
 
-   On error, exits with a proper error message.
+   If there's an error converting the string to value - exits with
+   an error.
 
-   The input string must not contain any extra characters
-   (except valid suffixes if 'from' scaling is enabled).
+   If there are any trailing characters after the number
+   (besides a valid suffix) - exits with an error.
 */
-static long double
-parse_number (const char* str)
+static enum simple_strtod_error
+parse_human_number (const char* str, long double /*output*/ *value)
 {
   char *ptr=NULL;
 
-  long double val=0;
   enum simple_strtod_error e =
-    simple_strtod_human (str, &ptr, &val, scale_from);
+    simple_strtod_human (str, &ptr, value, scale_from);
   if (e!= SSE_OK && e!=SSE_OK_PRECISION_LOSS)
     simple_strtod_fatal (e,str);
 
-  if (dev_debug)
-    fprintf (stderr,"Parsing number:\n  input string: '%s'\n  " \
-            "remaining characters: '%s'\n  numeric value: %Lf (%LG)\n",
-            str,ptr,val,val);
-
   if ( ptr && *ptr!='\0' )
        error (EXIT_FAILURE,0,_("invalid suffix in input '%s': '%s'"),
               str, ptr);
-  return val;
+  return e;
 }
 
 
@@ -763,6 +770,17 @@ print_number (const long double val)
 {
   /* Generate Output */
   char buf[128];
+
+  /* Can't reliably print too-large values without auto-scaling */
+  unsigned int x;
+  expld (val,10,&x);
+  if (scale_to==scale_none && x>MAX_UNSCALED_DIGITS)
+    error (EXIT_FAILURE,0,_("value too large to be printed: '%Lg'" \
+                             " (consider using --to)"), val);
+  if (x>MAX_ACCEPTABLE_DIGITS-1)
+    error (EXIT_FAILURE,0,_("value too large to be printed: '%Lg'" \
+                             " (cannot handle values > 999Y)"), val);
+
   char *s = double_to_human (val, buf, sizeof (buf),
                              scale_to, grouping, _round);
 
@@ -815,9 +833,14 @@ process_suffixed_number (char* text)
         }
     }
 
-  long double val = parse_number (text);
+  long double val=0;
+  enum simple_strtod_error e = parse_human_number (text,&val);
+  if (e==SSE_OK_PRECISION_LOSS && debug)
+    error (0,0,_("large input value '%s': possible precision loss"),text);
+
   if (from_unit_size!=1 || to_unit_size != 1)
           val = (val * from_unit_size) / to_unit_size;
+
   print_number (val);
 
   if (suffix)
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index 3508275..772e9b7 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -370,20 +370,126 @@ my @Tests =
 
      # Large Values
      ['large-1','1000000000000000', {OUT=>"1000000000000000"}],
+     # 18 digits is OK
      ['large-2','1000000000000000000', {OUT=>"1000000000000000000"}],
+     # 19 digits is too much (without output scaling)
+     ['large-3','10000000000000000000',
+             {ERR => "$prog: value too large to be printed: '1e+19' " .
+                     "(consider using --to)\n"},
+             {EXIT=>1}],
 
-     #FIX ME: can't read numbers so big - strtoumax fails
-#     ['large-3','1000000000000000000000', {OUT=>"1000000000000000000000"}],
-
-     ['large-4','--to=si 1000000000000000000', {OUT=>"1.0E"}],
+     # Test input:
+     # Up to 27 digits is OK.
+     ['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.7', '--to=si                     1000000', {OUT=>"1.0M"}],
+     ['large-3.8', '--to=si                    10000000', {OUT=> "10M"}],
+     ['large-3.9', '--to=si                   100000000', {OUT=>"100M"}],
+     ['large-3.10','--to=si                  1000000000', {OUT=>"1.0G"}],
+     ['large-3.11','--to=si                 10000000000', {OUT=> "10G"}],
+     ['large-3.12','--to=si                100000000000', {OUT=>"100G"}],
+     ['large-3.13','--to=si               1000000000000', {OUT=>"1.0T"}],
+     ['large-3.14','--to=si              10000000000000', {OUT=> "10T"}],
+     ['large-3.15','--to=si             100000000000000', {OUT=>"100T"}],
+     ['large-3.16','--to=si            1000000000000000', {OUT=>"1.0P"}],
+     ['large-3.17','--to=si           10000000000000000', {OUT=> "10P"}],
+     ['large-3.18','--to=si          100000000000000000', {OUT=>"100P"}],
+     ['large-3.19','--to=si         1000000000000000000', {OUT=>"1.0E"}],
+     ['large-3.20','--to=si        10000000000000000000', {OUT=> "10E"}],
+     ['large-3.21','--to=si       210000000000000000000', {OUT=>"210E"}],
+     ['large-3.22','--to=si      3210000000000000000000', {OUT=>"3.3Z"}],
+     ['large-3.23','--to=si     43210000000000000000000', {OUT=> "44Z"}],
+     ['large-3.24','--to=si    543210000000000000000000', {OUT=>"544Z"}],
+     ['large-3.25','--to=si   6543210000000000000000000', {OUT=>"6.6Y"}],
+     ['large-3.26','--to=si  76543210000000000000000000', {OUT=> "77Y"}],
+     ['large-3.27','--to=si 876543210000000000000000000', {OUT=>"877Y"}],
+
+     # More than 27 digits is not OK
+     ['large-3.28','--to=si 9876543210000000000000000000',
+             {ERR => "$prog: value too large to be converted: " .
+                     "'9876543210000000000000000000'\n"},
+             {EXIT => 1}],
+
+     # Test Output
+     ['large-4.1', '--from=si  9.7M',               {OUT=>"9700000"}],
+     ['large-4.2', '--from=si  10M',              {OUT =>"10000000"}],
+     ['large-4.3', '--from=si  200M',            {OUT =>"200000000"}],
+     ['large-4.4', '--from=si  3G',             {OUT =>"3000000000"}],
+     ['large-4.5', '--from=si  40G',           {OUT =>"40000000000"}],
+     ['large-4.6', '--from=si  500G',         {OUT =>"500000000000"}],
+     ['large-4.7', '--from=si  6T',          {OUT =>"6000000000000"}],
+     ['large-4.8', '--from=si  70T',        {OUT =>"70000000000000"}],
+     ['large-4.9', '--from=si  800T',      {OUT =>"800000000000000"}],
+     ['large-4.10','--from=si  9P',       {OUT =>"9000000000000000"}],
+     ['large-4.11','--from=si  10P',     {OUT =>"10000000000000000"}],
+     ['large-4.12','--from=si  200P',   {OUT =>"200000000000000000"}],
+     ['large-4.13','--from=si  3E',    {OUT =>"3000000000000000000"}],
+
+     # More than 18 digits of output without scaling - no good.
+     ['large-4.14','--from=si  40E',
+             {ERR => "$prog: value too large to be printed: '4e+19' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.15','--from=si  500E',
+             {ERR => "$prog: value too large to be printed: '5e+20' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.16','--from=si  6Z',
+             {ERR => "$prog: value too large to be printed: '6e+21' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.17','--from=si  70Z',
+             {ERR => "$prog: value too large to be printed: '7e+22' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.18','--from=si  800Z',
+             {ERR => "$prog: value too large to be printed: '8e+23' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.19','--from=si  9Y',
+             {ERR => "$prog: value too large to be printed: '9e+24' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.20','--from=si  10Y',
+             {ERR => "$prog: value too large to be printed: '1e+25' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-4.21','--from=si  200Y',
+             {ERR => "$prog: value too large to be printed: '2e+26' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+
+     ['large-5.1','--to=si 1000000000000000000', {OUT=>"1.0E"}],
      ['large-5','--from=si --to=si 2E', {OUT=>"2.0E"}],
      ['large-6','--from=si --to=si 3.4Z', {OUT=>"3.4Z"}],
      ['large-7','--from=si --to=si 80Y', {OUT=>"80Y"}],
      ['large-8','--from=si --to=si 9000Z', {OUT=>"9.0Y"}],
-     ['large-9','--from=si 9.7Y', {OUT=>"9700000000000000000000000"}],
 
-     #FIXME: can't handle such big numbres
-#     ['large-10','--from=si 999Y', {OUT=>"9700000000000000000000000"}],
+     ['large-10','--from=si --to=si 999Y', {OUT=>"999Y"}],
+     ['large-11','--from=si --to=iec 999Y', {OUT=>"827Y"}],
+     ['large-12','--from=si --round=floor --to=iec 999Y', {OUT=>"826Y"}],
+
+     # units can also affect the output
+     ['large-13','--from=si --from-unit=1000000 9P',
+             {ERR => "$prog: value too large to be printed: '9e+21' " .
+                     "(consider using --to)\n"},
+             {EXIT => 1}],
+     ['large-13.1','--from=si --from-unit=1000000 --to=si 9P', {OUT=>"9.0Z"}],
+
+     # Numbers>999Y are never acceptable, regardless of scaling
+     ['large-14','--from=si --to=si 999Y', {OUT=>"999Y"}],
+     ['large-14.1','--from=si --to=si 1000Y',
+             {ERR => "$prog: value too large to be printed: '1e+27' " .
+                     "(cannot handle values > 999Y)\n"},
+             {EXIT => 1}],
+     ['large-14.2','--from=si --to=si --from-unit=10000 1Y',
+             {ERR => "$prog: value too large to be printed: '1e+28' " .
+                     "(cannot handle values > 999Y)\n"},
+             {EXIT => 1}],
 
     );
 
-- 
1.7.7.4


From ad99e6678604c1fe3febbc74c85dee8817795321 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 13 Dec 2012 00:16:11 -0500
Subject: [PATCH 13/16] numfmt: implement auto-padding

---
 src/numfmt.c         |   52 ++++++++++++++++++++++++++++++++++++++++++-------
 tests/misc/numfmt.pl |   17 ++++++++++++---
 2 files changed, 57 insertions(+), 12 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index ffa3ea8..3d05c1a 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -136,7 +136,9 @@ static uintmax_t to_unit_size=1;
 static int human_print_options=0; /* see enum in 'human.c' */
 static int grouping=0;
 static char *padding_buffer=NULL;
+static size_t padding_buffer_size=0;
 static long int padding_width=0;
+static int auto_padding=0; /* auto-pad each line based on skipped whitespace*/
 static mbs_align_t padding_alignment=MBS_ALIGN_RIGHT;
 static long int field=1;
 static int delimiter = DELIMITER_DEFAULT;
@@ -641,6 +643,18 @@ string_to_integer (const char *n_string)
 }
 
 
+static void
+setup_padding_buffer ( size_t min_size )
+{
+  if (padding_buffer_size > min_size)
+    return ;
+
+  padding_buffer_size = min_size+1;
+  padding_buffer = realloc (padding_buffer, padding_buffer_size);
+  if (!padding_buffer)
+    error (EXIT_FAILURE,0, _("out of memory (requested %ld bytes)"),
+           padding_width+1);
+}
 
 void
 usage (int status)
@@ -788,10 +802,10 @@ print_number (const long double val)
     fprintf (stderr,"Formatting output:\n  value: %Lf\n  humanized: '%s'\n",
             val,s);
 
-  if (padding_width)
+  if (padding_width && strlen (s)<padding_width)
     {
       size_t w = padding_width;
-      mbsalign (s,padding_buffer,padding_width+1,&w,
+      mbsalign (s,padding_buffer,padding_buffer_size,&w,
                padding_alignment,  MBA_UNIBYTE_ONLY );
       if (strlen (padding_buffer)<strlen (s))
         error (0,0,_("value '%s' truncated due to padding, possible data loss"),
@@ -833,10 +847,33 @@ process_suffixed_number (char* text)
         }
     }
 
+  /* Skip white space - always*/
+  char *p = text;
+  while ( *p && isblank (*p) )
+    ++p;
+  const unsigned int skip_count = text-p;
+
+  /* setup auto-padding */
+  if (auto_padding)
+    {
+      if (skip_count>0 || field>1)
+        {
+          padding_width = strlen (text);
+          setup_padding_buffer (padding_width);
+        }
+      else
+       {
+         padding_width = 0;
+       }
+      if (dev_debug)
+        fprintf (stderr, "Setting Auto-Padding to %ld characters\n",
+                padding_width);
+    }
+
   long double val=0;
-  enum simple_strtod_error e = parse_human_number (text,&val);
+  enum simple_strtod_error e = parse_human_number (p,&val);
   if (e==SSE_OK_PRECISION_LOSS && debug)
-    error (0,0,_("large input value '%s': possible precision loss"),text);
+    error (0,0,_("large input value '%s': possible precision loss"),p);
 
   if (from_unit_size!=1 || to_unit_size != 1)
           val = (val * from_unit_size) / to_unit_size;
@@ -1024,10 +1061,7 @@ main (int argc, char **argv)
               padding_alignment = MBS_ALIGN_LEFT;
               padding_width = -padding_width;
             }
-          padding_buffer = malloc (padding_width+1);
-          if (!padding_buffer)
-            error (EXIT_FAILURE,0, _("out of memory (requested %ld bytes)"),
-                    padding_width+1);
+          setup_padding_buffer (padding_width);
           break;
 
         case FIELD_OPTION:
@@ -1076,6 +1110,8 @@ main (int argc, char **argv)
         }
     }
 
+  auto_padding = (padding_width==0 && delimiter==DELIMITER_DEFAULT);
+
   switch (_round)
     {
     case round_ceiling:
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
index 772e9b7..c3ca70e 100644
--- a/tests/misc/numfmt.pl
+++ b/tests/misc/numfmt.pl
@@ -128,10 +128,8 @@ my @Tests =
      ['pad-4', '--padding=10 --to=si 50000',             {OUT=>'       50K'}],
      ['pad-5', '--padding=-10 --to=si 50000',            {OUT=>'50K       '}],
 
-     # padding causing truncation, data loss
-     ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.'},
-             {ERR => "$prog: value '1.0K' truncated due to padding, " .
-                "possible data loss\n"}],
+     # padding too narrow
+     ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.0K'}],
 
 
      # Padding + suffix
@@ -177,6 +175,17 @@ my @Tests =
              {ERR=>"$prog: input line is too short, no numbers found " .
                    "to convert in field 3\n"}],
 
+     # 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-3', '--to=si "  500"', {OUT=>"  500"}],
+     ['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"}],
+     # but if there is whitespace, assume auto-padding is desired.
+     ['whitespace-6', '--to=si " 6000000"', {OUT=>"    6.0M"}],
+
 
      # Corner-cases:
      # weird mix of identical suffix,delimiters
-- 
1.7.7.4


From 67ee0504f4014ebe93af9159fdf41e1fbef3339b Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 13 Dec 2012 00:17:21 -0500
Subject: [PATCH 14/16] numfmt: cleanup - remove unused leftovers.

---
 src/numfmt.c |   32 --------------------------------
 1 files changed, 0 insertions(+), 32 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 3d05c1a..15bb820 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -25,7 +25,6 @@
 #include "error.h"
 #include "system.h"
 #include "xstrtol.h"
-#include "human.h"
 
 /* The official name of this program (e.g., no 'g' prefix).  */
 #define PROGRAM_NAME "numfmt"
@@ -133,7 +132,6 @@ static enum round_type _round=round_ceiling;
 static const char *suffix = NULL;
 static uintmax_t from_unit_size=1;
 static uintmax_t to_unit_size=1;
-static int human_print_options=0; /* see enum in 'human.c' */
 static int grouping=0;
 static char *padding_buffer=NULL;
 static size_t padding_buffer_size=0;
@@ -1112,40 +1110,10 @@ main (int argc, char **argv)
 
   auto_padding = (padding_width==0 && delimiter==DELIMITER_DEFAULT);
 
-  switch (_round)
-    {
-    case round_ceiling:
-      human_print_options |= human_ceiling;
-      break;
-    case round_floor:
-      human_print_options |= human_floor;
-      break;
-    case round_nearest:
-      human_print_options |= human_round_to_nearest;
-      break;
-    }
-
-  switch (scale_to)
-    {
-    case scale_SI:
-      human_print_options |= human_autoscale | human_SI;
-      break;
-    case scale_IEC:
-      human_print_options |= human_autoscale | human_base_1024 | human_SI ;
-      break;
-
-    case scale_auto:
-      /* should never happen. assert? */
-
-    case scale_none:
-      break;
-    }
-
   if (grouping)
     {
       if (scale_to!=scale_none)
         error (EXIT_FAILURE,0,_("--grouping cannot be combined with --to"));
-      human_print_options |= human_group_digits;
       if (debug && (strlen (nl_langinfo (THOUSEP))==0))
             error (0,0,_("--grouping has not effect in this locale"));
     }
-- 
1.7.7.4


From d2bacc4e34f0655cedec21d0e9e72796eeb8be2e Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 13 Dec 2012 00:25:35 -0500
Subject: [PATCH 15/16] numfmt: improve --help

---
 src/numfmt.c |   22 +++++++++++++---------
 1 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 15bb820..6567f82 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -682,15 +682,15 @@ Numbers can be processed either from stdin or command arguments.\n\
   --suffix=SUFFIX add SUFFIX to output numbers, and accept optional SUFFIX\n\
                   in input numbers.\n\
   --padding=N     pad the output to N characters.\n\
-                  Default is right-aligned. Negative N will left-align.\n\
-                  Note: if N is too small, the output will be truncated,\n\
-                  and a warning will be printed to stderr.\n\
+                  Positive N will right-aligned. Negative N will left-align.\n\
+                  Note: if the output is wider than N, padding is ignored.\n\
+                  Default is to automatically pad if whitespace is found.\n\
   --grouping      group digits together (e.g. 1,000,000).\n\
                   Uses the locale-defined grouping (i.e. have no effect\n\
                   in C/POSIX locales).\n\
   --field N       replace the number in input field N (default is 1)\n\
   -d, --delimiter=X  use X instead of whitespace for field delimiter\n\
-  --debug         print warning about possible errors.\n\
+  --debug         print warnings about invalid input.\n\
   \n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -706,11 +706,11 @@ UNIT options:\n\
       1Ki = 1024\n\
       1G  = 1000000\n\
       1Gi = 1048576\n\
- SI:\n\
+ si:\n\
       1K = 1000\n\
       1G  = 1000000\n\
       ...\n\
- IEC:\n\
+ iec:\n\
       1K = 1024\n\
       1G = 1048576\n\
       ...\n\
@@ -720,10 +720,14 @@ UNIT options:\n\
       printf (_("\
 \n\
 Examples:\n\
-  %s --to=SI 1000           -> \"1K\"\n\
-  echo 1K | %s --from=SI    -> \"1000\"\n\
-  echo 1K | %s --from=IEC   -> \"1024\"\n\
+  %s --to=si 1000           -> \"1K\"\n\
+  echo 1K | %s --from=si    -> \"1000\"\n\
+  echo 1K | %s --from=iec   -> \"1024\"\n\
+  df | %s --header --field 2 --to=si\n\
+  ls -l | %s --header --field 5 --to=iec\n\
+  ls -lh | %s --header --field 5 --from=iec --padding=10\n\
 "),
+              program_name, program_name, program_name,
               program_name, program_name, program_name);
       emit_ancillary_info ();
     }
-- 
1.7.7.4


From f4ef3dca6f165866f735959d63a3953d3ce0ae73 Mon Sep 17 00:00:00 2001
From: Assaf Gordon <assafgordon@gmail.com>
Date: Thu, 13 Dec 2012 00:40:38 -0500
Subject: [PATCH 16/16] numfmt: mention --header in help.

---
 src/numfmt.c |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/src/numfmt.c b/src/numfmt.c
index 6567f82..c0d701a 100644
--- a/src/numfmt.c
+++ b/src/numfmt.c
@@ -688,6 +688,8 @@ Numbers can be processed either from stdin or command arguments.\n\
   --grouping      group digits together (e.g. 1,000,000).\n\
                   Uses the locale-defined grouping (i.e. have no effect\n\
                   in C/POSIX locales).\n\
+  --header[=N]    print (without converting) the first N header lines.\n\
+                  N defaults to 1 if not specified.\n\
   --field N       replace the number in input field N (default is 1)\n\
   -d, --delimiter=X  use X instead of whitespace for field delimiter\n\
   --debug         print warnings about invalid input.\n\
-- 
1.7.7.4

