Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / lib / sh / snprintf.c
index f84d60c..87ca217 100644 (file)
@@ -1,3 +1,5 @@
+/* snprintf - formatted output to strings, with bounds checking and allocation */
+
 /*
  build a test version with
    gcc -g -DDRIVER -I../.. -I../../include -o test-snprintf snprintf.c fmtu*long.o
@@ -7,23 +9,24 @@
    Unix snprintf implementation.
    derived from inetutils/libinetutils/snprintf.c Version 1.1
 
-   Copyright (C) 2001 Free Software Foundation, Inc.
+   Copyright (C) 2001,2006,2010,2012 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
 
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   Bash 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,
+
+   Bash 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 License for more details.
-   
-   You should have received a copy of the GNU General License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
    
-   Revision History:
+   Original (pre-bash) Revision History:
 
    1.1:
       *  added changes from Miles Bader
 
 /*
  * Currently doesn't handle (and bash/readline doesn't use):
- *     *M$ width, precision specifications
- *     %N$ numbered argument conversions
- *     inf, nan floating values (could use isinf(), isnan())
- *     `,', `'' flags 
- *     `C', `S' conversions
- *     support for `F' is imperfect, since underlying printf may not handle it
+ *     * *M$ width, precision specifications
+ *     * %N$ numbered argument conversions
+ *     * support for `F' is imperfect with ldfallback(), since underlying
+ *       printf may not handle it -- should ideally have another autoconf test
  */
 
 #define FLOATING_POINT
 #  include <config.h>
 #endif
 
+/* GCC 4.2 on Snow Leopard doesn't like the snprintf prototype */
+#if defined(DEBUG) && !defined (MACOSX)
+#  undef HAVE_SNPRINTF
+#  undef HAVE_ASPRINTF
+
+#  define HAVE_SNPRINTF 0
+#  define HAVE_ASPRINTF 0
+#endif
+
 #if defined(DRIVER) && !defined(HAVE_CONFIG_H)
 #define HAVE_LONG_LONG
 #define HAVE_LONG_DOUBLE
 #ifdef __linux__
 #define HAVE_PRINTF_A_FORMAT
 #endif
+#define HAVE_ISINF_IN_LIBC
+#define HAVE_ISNAN_IN_LIBC
 #define PREFER_STDARG
 #define HAVE_STRINGIZE
 #define HAVE_LIMITS_H
 #define HAVE_STDDEF_H
+#define HAVE_LOCALE_H
 #define intmax_t long
 #endif
 
-#if !defined (HAVE_SNPRINTF) || !defined (HAVE_ASPRINTF)
+#if !HAVE_SNPRINTF || !HAVE_ASPRINTF
 
 #include <bashtypes.h>
 
 #endif
 
 #ifdef FLOATING_POINT
+#  include <float.h>   /* for manifest constants */
 #  include <stdio.h>   /* for sprintf */
 #endif
 
 #include <typemax.h>
 
+#ifdef HAVE_LOCALE_H
+#  include <locale.h>
+#endif
+
 #include "stdc.h"
+#include <shmbutil.h>
 
 #ifndef DRIVER
 #  include "shell.h"
@@ -114,6 +133,10 @@ extern char *fmtulong __P((unsigned long int, int, char *, size_t, int));
 extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int));
 #endif
 
+#ifndef FREE
+#  define FREE(x)      if (x) free (x)
+#endif
+
 /* Bound on length of the string representing an integer value of type T.
    Subtract one for the sign bit if T is signed;
    302 / 1000 is log10 (2) rounded up;
@@ -130,7 +153,7 @@ extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int));
 #define PF_ZEROPAD     0x00008         /* 0 */
 #define PF_PLUS                0x00010         /* + */
 #define PF_SPACE       0x00020         /* ' ' */
-#define PF_COMMA       0x00040         /* , */
+#define PF_THOUSANDS   0x00040         /* ' */
 
 #define PF_DOT         0x00080         /* `.precision' */
 #define PF_STAR_P      0x00100         /* `*' after precision */
@@ -158,6 +181,10 @@ extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int));
 
 static char intbuf[INT_STRLEN_BOUND(unsigned long) + 1];
 
+static int decpoint;
+static int thoussep;
+static char *grouping;
+
 /* 
  * For the FLOATING POINT FORMAT :
  *  the challenge was finding a way to
@@ -196,25 +223,27 @@ static char intbuf[INT_STRLEN_BOUND(unsigned long) + 1];
 
 #define SWAP_INT(a,b) {int t; t = (a); (a) = (b); (b) = t;}
 
+#define GETARG(type)   (va_arg(args, type))
+
 /* Macros that do proper sign extension and handle length modifiers.  Used
    for the integer conversion specifiers. */
 #define GETSIGNED(p) \
   (((p)->flags & PF_LONGINT) \
-       ? va_arg(args, long) \
-       : (((p)->flags & PF_SHORTINT) ? (long)(short)va_arg(args, int) \
-                                     : (long)va_arg(args, int)))
+       ? GETARG (long) \
+       : (((p)->flags & PF_SHORTINT) ? (long)(short)GETARG (int) \
+                                     : (long)GETARG (int)))
 
 #define GETUNSIGNED(p) \
   (((p)->flags & PF_LONGINT) \
-       ? va_arg(args, unsigned long) \
-       : (((p)->flags & PF_SHORTINT) ? (unsigned long)(unsigned short)va_arg(args, int) \
-                                     : (unsigned long)va_arg(args, unsigned int)))
+       ? GETARG (unsigned long) \
+       : (((p)->flags & PF_SHORTINT) ? (unsigned long)(unsigned short)GETARG (int) \
+                                     : (unsigned long)GETARG (unsigned int)))
 
 
 #ifdef HAVE_LONG_DOUBLE
-#define GETLDOUBLE(p) va_arg(args, long double)
+#define GETLDOUBLE(p) GETARG (long double)
 #endif
-#define GETDOUBLE(p) va_arg(args, double)
+#define GETDOUBLE(p) GETARG (double)
 
 #define SET_SIZE_FLAGS(p, type) \
   if (sizeof (type) > sizeof (int)) \
@@ -271,6 +300,34 @@ static void ldfallback __P((struct DATA *, const char *, const char *, long doub
 static void dfallback __P((struct DATA *, const char *, const char *, double));
 #endif
 
+static char *groupnum __P((char *));
+
+#if defined (HAVE_LONG_DOUBLE)
+#  define LONGDOUBLE long double
+#else
+#  define LONGDOUBLE double
+#endif
+
+#ifndef isnan
+  static inline int isnan_f  (float       x) { return x != x; }
+  static inline int isnan_d  (double      x) { return x != x; }
+  static inline int isnan_ld (LONGDOUBLE  x) { return x != x; }
+  # define isnan(x) \
+      (sizeof (x) == sizeof (LONGDOUBLE) ? isnan_ld (x) \
+       : sizeof (x) == sizeof (double) ? isnan_d (x) \
+       : isnan_f (x))
+#endif
+  
+#ifndef isinf
+  static inline int isinf_f  (float       x) { return !isnan (x) && isnan (x - x); }
+  static inline int isinf_d  (double      x) { return !isnan (x) && isnan (x - x); }
+  static inline int isinf_ld (LONGDOUBLE  x) { return !isnan (x) && isnan (x - x); }
+  # define isinf(x) \
+      (sizeof (x) == sizeof (LONGDOUBLE) ? isinf_ld (x) \
+       : sizeof (x) == sizeof (double) ? isinf_d (x) \
+       : isinf_f (x))
+#endif
+
 #ifdef DRIVER
 static void memory_error_and_abort ();
 static void *xmalloc __P((size_t));
@@ -317,8 +374,22 @@ static void xfree __P((void *));
          } \
        while (0)
 
+/* Output a string.  P->WIDTH has already been adjusted for padding. */
+#define PUT_STRING(string, len, p) \
+       do \
+         { \
+           PAD_RIGHT (p); \
+           while ((len)-- > 0) \
+             { \
+               PUT_CHAR (*(string), (p)); \
+               (string)++; \
+             } \
+           PAD_LEFT (p); \
+         } \
+       while (0)
+
 #define PUT_PLUS(d, p, zero) \
-           if ((d) > zero && (p)->justify == RIGHT) \
+           if (((p)->flags & PF_PLUS) && (d) > zero) \
              PUT_CHAR('+', p)
 
 #define PUT_SPACE(d, p, zero) \
@@ -337,12 +408,60 @@ static void xfree __P((void *));
              for (; (p)->width > 0; (p)->width--) \
                 PUT_CHAR((p)->pad, p)
 
+/* pad with zeros from decimal precision */
+#define PAD_ZERO(p) \
+       if ((p)->precision > 0) \
+         for (; (p)->precision > 0; (p)->precision--) \
+           PUT_CHAR('0', p)
+
 /* if width and prec. in the args */
 #define STAR_ARGS(p) \
+       do { \
            if ((p)->flags & PF_STAR_W) \
-             (p)->width = va_arg(args, int); \
+             { \
+               (p)->width = GETARG (int); \
+               if ((p)->width < 0) \
+                 { \
+                   (p)->flags |= PF_LADJUST; \
+                   (p)->justify = LEFT; \
+                   (p)->width = -(p)->width; \
+                 } \
+             } \
            if ((p)->flags & PF_STAR_P) \
-             (p)->precision = va_arg(args, int)
+             { \
+               (p)->precision = GETARG (int); \
+               if ((p)->precision < 0) \
+                 { \
+                   (p)->flags &= ~PF_STAR_P; \
+                   (p)->precision = NOT_FOUND; \
+                 } \
+             } \
+       } while (0)
+
+#if defined (HAVE_LOCALE_H) && defined (HAVE_LOCALECONV)
+#  define GETLOCALEDATA(d, t, g) \
+      do \
+       { \
+         struct lconv *lv; \
+         if ((d) == 0) { \
+         (d) = '.'; (t) = -1; (g) = 0; /* defaults */ \
+         lv = localeconv(); \
+         if (lv) \
+           { \
+             if (lv->decimal_point && lv->decimal_point[0]) \
+               (d) = lv->decimal_point[0]; \
+             if (lv->thousands_sep && lv->thousands_sep[0]) \
+               (t) = lv->thousands_sep[0]; \
+             (g) = lv->grouping ? lv->grouping : ""; \
+             if (*(g) == '\0' || *(g) == CHAR_MAX || (t) == -1) (g) = 0; \
+           } \
+         } \
+       } \
+      while (0);
+#else
+#  define GETLOCALEDATA(d, t, g) \
+      ( (d) = '.', (t) = ',', g = "\003" )
+#endif
 
 #ifdef FLOATING_POINT
 /*
@@ -389,6 +508,8 @@ pow_10(n)
          10^x ~= r
  * log_10(200) = 2;
  * log_10(250) = 2;
+ *
+ * NOTE: do not call this with r == 0 -- an infinite loop results.
  */
 static int
 log_10(r)
@@ -471,7 +592,7 @@ integral(real, ip)
 /* 
  * return an ascii representation of the integral part of the number
  * and set fract to be an ascii representation of the fraction part
- * the container for the fraction and the integral part or staticly
+ * the container for the fraction and the integral part or statically
  * declare with fix size 
  */
 static char *
@@ -483,10 +604,9 @@ numtoa(number, base, precision, fract)
   register int i, j;
   double ip, fp; /* integer and fraction part */
   double fraction;
-  int digits = MAX_INT - 1;
+  int digits, sign;
   static char integral_part[MAX_INT];
   static char fraction_part[MAX_FRACT];
-  double sign;
   int ch;
 
   /* taking care of the obvious case: 0.0 */
@@ -494,13 +614,22 @@ numtoa(number, base, precision, fract)
     { 
       integral_part[0] = '0';
       integral_part[1] = '\0';
-      fraction_part[0] = '0';
-      fraction_part[1] = '\0';
+      /* The fractional part has to take the precision into account */
+      for (ch = 0; ch < precision-1; ch++)
+       fraction_part[ch] = '0';
+      fraction_part[ch] = '0';
+      fraction_part[ch+1] = '\0';
+      if (fract)
+       *fract = fraction_part;
       return integral_part;
     }
 
+  /* -0 is tricky */
+  sign = (number == -0.) ? '-' : ((number < 0.) ? '-' : '+');
+  digits = MAX_INT - 1;
+
   /* for negative numbers */
-  if ((sign = number) < 0.)
+  if (sign == '-')
     {
       number = -number;
       digits--; /* sign consume one digit */
@@ -535,7 +664,7 @@ numtoa(number, base, precision, fract)
       integral_part[i] = '9';
 
   /* put the sign ? */
-  if (sign < 0.)
+  if (sign == '-')
     integral_part[i++] = '-';
 
   integral_part[i] = '\0';
@@ -570,19 +699,43 @@ number(p, d, base)
      unsigned long d;
      int base;
 {
-  char *tmp;
+  char *tmp, *t;
   long sd;
   int flags;
 
+  /* An explicit precision turns off the zero-padding flag and sets the
+     pad character back to space. */
+  if ((p->flags & PF_ZEROPAD) && p->precision >= 0 && (p->flags & PF_DOT))
+    {
+      p->flags &= ~PF_ZEROPAD;
+      p->pad = ' ';
+    }
+
   sd = d;      /* signed for ' ' padding in base 10 */
-  flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
+  flags = 0;
+  flags = (*p->pf == 'x' || *p->pf == 'X' || *p->pf == 'o' || *p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
   if (*p->pf == 'X')
     flags |= FL_HEXUPPER;
 
   tmp = fmtulong (d, base, intbuf, sizeof(intbuf), flags);
-  p->width -= strlen(tmp);
+  t = 0;
+  if ((p->flags & PF_THOUSANDS))
+    {
+      GETLOCALEDATA(decpoint, thoussep, grouping);
+      if (grouping && (t = groupnum (tmp)))
+       tmp = t;
+    }
+
+  /* need to add one for any `+', but we only add one in base 10 */
+  p->width -= strlen(tmp) + (base == 10 && d > 0 && (p->flags & PF_PLUS));
   PAD_RIGHT(p);
 
+  if ((p->flags & PF_DOT) && p->precision > 0)
+    {
+      p->precision -= strlen(tmp);
+      PAD_ZERO(p);
+    }
+
   switch (base)
     {
     case 10:
@@ -609,6 +762,7 @@ number(p, d, base)
     }
 
   PAD_LEFT(p);
+  FREE (t);
 }
 
 #ifdef HAVE_LONG_LONG
@@ -621,19 +775,42 @@ lnumber(p, d, base)
      unsigned long long d;
      int base;
 {
-  char *tmp;
+  char *tmp, *t;
   long long sd;
   int flags;
 
+  /* An explicit precision turns off the zero-padding flag and sets the
+     pad character back to space. */
+  if ((p->flags & PF_ZEROPAD) && p->precision >= 0 && (p->flags & PF_DOT))
+    {
+      p->flags &= ~PF_ZEROPAD;
+      p->pad = ' ';
+    }
+
   sd = d;      /* signed for ' ' padding in base 10 */
-  flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
+  flags = (*p->pf == 'x' || *p->pf == 'X' || *p->pf == 'o' || *p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
   if (*p->pf == 'X')
     flags |= FL_HEXUPPER;
 
   tmp = fmtullong (d, base, intbuf, sizeof(intbuf), flags);
-  p->width -= strlen(tmp);
+  t = 0;
+  if ((p->flags & PF_THOUSANDS))
+    {
+      GETLOCALEDATA(decpoint, thoussep, grouping);
+      if (grouping && (t = groupnum (tmp)))
+       tmp = t;
+    }
+
+  /* need to add one for any `+', but we only add one in base 10 */
+  p->width -= strlen(tmp) + (base == 10 && d > 0 && (p->flags & PF_PLUS));
   PAD_RIGHT(p);
 
+  if ((p->flags & PF_DOT) && p->precision > 0)
+    {
+      p->precision -= strlen(tmp);
+      PAD_ZERO(p);
+    }
+
   switch (base)
     {
     case 10:
@@ -660,6 +837,7 @@ lnumber(p, d, base)
     }
 
   PAD_LEFT(p);
+  FREE (t);
 }
 #endif
 
@@ -683,6 +861,7 @@ pointer(p, d)
       PUT_CHAR(*tmp, p);
       tmp++;
     }
+
   PAD_LEFT(p);
 }
 
@@ -692,53 +871,183 @@ strings(p, tmp)
      struct DATA *p;
      char *tmp;
 {
-  int i;
+  size_t len;
 
-  i = strlen(tmp);
+  len = strlen(tmp);
   if (p->precision != NOT_FOUND) /* the smallest number */
-    i = (i < p->precision ? i : p->precision);
-  p->width -= i;
-  PAD_RIGHT(p);
-  while (i-- > 0)
-    { /* put the sting */
-      PUT_CHAR(*tmp, p);
-      tmp++;
+    len = (len < p->precision ? len : p->precision);
+  p->width -= len;
+
+  PUT_STRING (tmp, len, p);
+}
+
+#if HANDLE_MULTIBYTE
+/* %ls wide-character strings */
+static void
+wstrings(p, tmp)
+     struct DATA *p;
+     wchar_t *tmp;
+{
+  size_t len;
+  mbstate_t mbs;
+  char *os;
+  const wchar_t *ws;
+
+  memset (&mbs, '\0', sizeof (mbstate_t));
+  ws = (const wchar_t *)tmp;
+
+  os = (char *)NULL;
+  if (p->precision != NOT_FOUND)
+    {
+      os = (char *)xmalloc (p->precision + 1);
+      len = wcsrtombs (os, &ws, p->precision, &mbs);
     }
-  PAD_LEFT(p);
+  else
+    {
+      len = wcsrtombs (NULL, &ws, 0, &mbs);
+      if (len != (size_t)-1)
+       {
+         memset (&mbs, '\0', sizeof (mbstate_t));
+         os = (char *)xmalloc (len + 1);
+         (void)wcsrtombs (os, &ws, len + 1, &mbs);
+       }
+    }
+  if (len == (size_t)-1)
+    {
+      /* invalid multibyte sequence; bail now. */
+      FREE (os);      
+      return;
+    }
+
+  p->width -= len;
+  PUT_STRING (os, len, p);
+  free (os);
 }
 
+static void
+wchars (p, wc)
+     struct DATA *p;
+     wint_t wc;
+{
+  char *lbuf, *l;
+  mbstate_t mbs;
+  size_t len;
+
+  lbuf = (char *)malloc (MB_CUR_MAX+1);
+  if (lbuf == 0)
+    return;
+  memset (&mbs, '\0', sizeof (mbstate_t));
+  len = wcrtomb (lbuf, wc, &mbs);
+  if (len == (size_t)-1)
+    /* conversion failed; bail now. */
+    return;
+  p->width -= len;
+  l = lbuf;
+  PUT_STRING (l, len, p);
+  free (lbuf);
+}
+#endif /* HANDLE_MULTIBYTE */
+
 #ifdef FLOATING_POINT
+
+/* Check for [+-]infinity and NaN.  If MODE == 1, we check for Infinity, else
+   (mode == 2) we check for NaN.  This does the necessary printing.  Returns
+   1 if Inf or Nan, 0 if not. */
+static int
+chkinfnan(p, d, mode)
+     struct DATA *p;
+     double d;
+     int mode;         /* == 1 for inf, == 2 for nan */
+{
+  int i;
+  char *tmp;
+  char *big, *small;
+
+  i = (mode == 1) ? isinf(d) : isnan(d);
+  if (i == 0)
+    return 0;
+  big = (mode == 1) ? "INF" : "NAN";
+  small = (mode == 1) ? "inf" : "nan";
+
+  tmp = (*p->pf == 'F' || *p->pf == 'G' || *p->pf == 'E') ? big : small;
+
+  if (i < 0)
+    PUT_CHAR('-', p);
+
+  while (*tmp)
+    {
+      PUT_CHAR (*tmp, p);
+      tmp++;
+    }
+
+  return 1;
+}
+
 /* %f %F %g %G floating point representation */
 static void
 floating(p, d)
      struct DATA *p;
      double d;
 {
-  char *tmp, *tmp2;
+  char *tmp, *tmp2, *t;
   int i;
 
+  if (d != 0 && (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)))
+    return;    /* already printed nan or inf */
+
+  GETLOCALEDATA(decpoint, thoussep, grouping);
   DEF_PREC(p);
   d = ROUND(d, p);
   tmp = dtoa(d, p->precision, &tmp2);
+  t = 0;
+  if ((p->flags & PF_THOUSANDS) && grouping && (t = groupnum (tmp)))
+    tmp = t;
+
+  if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
+    {
+      /* smash the trailing zeros unless altform */
+      for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
+       tmp2[i] = '\0'; 
+      if (tmp2[0] == '\0')
+       p->precision = 0;
+    }
+
   /* calculate the padding. 1 for the dot */
   p->width = p->width -
+           /* XXX - should this be d>0. && (p->flags & PF_PLUS) ? */
+#if 0
            ((d > 0. && p->justify == RIGHT) ? 1:0) -
+#else
+           ((d > 0. && (p->flags & PF_PLUS)) ? 1:0) -
+#endif
            ((p->flags & PF_SPACE) ? 1:0) -
-           strlen(tmp) - p->precision - 1;
-  PAD_RIGHT(p);  
-  PUT_PLUS(d, p, 0.);
+           strlen(tmp) - p->precision -
+           ((p->precision != 0 || (p->flags & PF_ALTFORM)) ? 1 : 0);   /* radix char */
+
+  if (p->pad == ' ')
+    {
+      PAD_RIGHT(p);
+      PUT_PLUS(d, p, 0.);
+    }
+  else
+    {
+      if (*tmp == '-')
+       PUT_CHAR(*tmp++, p);
+      PUT_PLUS(d, p, 0.);
+      PAD_RIGHT(p);
+    }
   PUT_SPACE(d, p, 0.);
+
   while (*tmp)
-    { /* the integral */
-      PUT_CHAR(*tmp, p);
+    {
+      PUT_CHAR(*tmp, p);       /* the integral */
       tmp++;
     }
+  FREE (t);
+
   if (p->precision != 0 || (p->flags & PF_ALTFORM))
-    PUT_CHAR('.', p);  /* put the '.' */
-  if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
-    /* smash the trailing zeros unless altform */
-    for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
-       tmp2[i] = '\0'; 
+    PUT_CHAR(decpoint, p);  /* put the '.' */
+
   for (; *tmp2; tmp2++)
     PUT_CHAR(*tmp2, p); /* the fraction */
   
@@ -752,69 +1061,76 @@ exponent(p, d)
      double d;
 {
   char *tmp, *tmp2;
-  int j, i, nsig, ndig;
+  int j, i;
+
+  if (d != 0 && (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)))
+    return;    /* already printed nan or inf */
 
+  GETLOCALEDATA(decpoint, thoussep, grouping);
   DEF_PREC(p);
-  j = log_10(d);
-  d = d / pow_10(j);  /* get the Mantissa */
-  d = ROUND(d, p);               
+  if (d == 0.)
+    j = 0;
+  else
+    {
+      j = log_10(d);
+      d = d / pow_10(j);  /* get the Mantissa */
+      d = ROUND(d, p);           
+    }
   tmp = dtoa(d, p->precision, &tmp2);
+
   /* 1 for unit, 1 for the '.', 1 for 'e|E',
-   * 1 for '+|-', 2 for 'exp' */
+   * 1 for '+|-', 2 for 'exp'  (but no `.' if precision == 0 */
   /* calculate how much padding need */
   p->width = p->width - 
+           /* XXX - should this be d>0. && (p->flags & PF_PLUS) ? */
+#if 0
             ((d > 0. && p->justify == RIGHT) ? 1:0) -
-            ((p->flags & PF_SPACE) ? 1:0) - p->precision - 6;
-  PAD_RIGHT(p);
-  PUT_PLUS(d, p, 0.);
+#else
+            ((d > 0. && (p->flags & PF_PLUS)) ? 1:0) -
+#endif
+            (p->precision != 0 || (p->flags & PF_ALTFORM)) -
+            ((p->flags & PF_SPACE) ? 1:0) - p->precision - 5;
+
+  if (p->pad == ' ')
+    {
+      PAD_RIGHT(p);
+      PUT_PLUS(d, p, 0.);
+    }
+  else
+    {
+      if (*tmp == '-')
+       PUT_CHAR(*tmp++, p);
+      PUT_PLUS(d, p, 0.);
+      PAD_RIGHT(p);
+    }
   PUT_SPACE(d, p, 0.);
-  /*
-   * When supplied %g or %G, an optional precision is the number of
-   * significant digits to print.
-   *
-   * nsig = number of significant digits we've printed (leading zeros are
-   *       never significant)
-   * ndig = if non-zero, max number of significant digits to print (only
-   *       applicable to %g/%G)
-   */
-  nsig = ndig = 0;
-  if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_DOT))
-    ndig = (p->precision == 0) ? 1 : p->precision;
 
   while (*tmp)
     {
       PUT_CHAR(*tmp, p);
       tmp++;
-      if (ndig && (++nsig >= ndig))
-       break;
     }
 
-  if ((p->precision != 0 || (p->flags & PF_ALTFORM)) && (ndig == 0 || nsig < ndig))
-      PUT_CHAR('.', p);  /* the '.' */
+  if (p->precision != 0 || (p->flags & PF_ALTFORM))
+      PUT_CHAR(decpoint, p);  /* the '.' */
+
   if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
     /* smash the trailing zeros unless altform */
     for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
-       tmp2[i] = '\0'; 
+      tmp2[i] = '\0'; 
+
   for (; *tmp2; tmp2++)
-    {
-      if (ndig && (nsig++ >= ndig))
-       break;
-      PUT_CHAR(*tmp2, p); /* the fraction */
-    }
+    PUT_CHAR(*tmp2, p); /* the fraction */
 
   /* the exponent put the 'e|E' */
   if (*p->pf == 'g' || *p->pf == 'e')
-    {
-      PUT_CHAR('e', p);
-    }
+    PUT_CHAR('e', p);
   else
-     PUT_CHAR('E', p);
+    PUT_CHAR('E', p);
 
   /* the sign of the exp */
-  if (j > 0)
-    {
-      PUT_CHAR('+', p);
-    }
+  if (j >= 0)
+    PUT_CHAR('+', p);
   else
     {
       PUT_CHAR('-', p);
@@ -825,9 +1141,7 @@ exponent(p, d)
    /* pad out to at least two spaces.  pad with `0' if the exponent is a
       single digit. */
    if (j <= 9)
-     {
-       PUT_CHAR('0', p);
-     }
+     PUT_CHAR('0', p);
 
    /* the exponent */
    while (*tmp)
@@ -835,10 +1149,74 @@ exponent(p, d)
        PUT_CHAR(*tmp, p);
        tmp++;
      }
+
    PAD_LEFT(p);
 }
 #endif
 
+/* Return a new string with the digits in S grouped according to the locale's
+   grouping info and thousands separator.  If no grouping should be performed,
+   this returns NULL; the caller needs to check for it. */
+static char *
+groupnum (s)
+     char *s;
+{
+  char *se, *ret, *re, *g;
+  int len, slen;
+
+  if (grouping == 0 || *grouping <= 0 || *grouping == CHAR_MAX)
+    return ((char *)NULL);
+
+  /* find min grouping to size returned string */
+  for (len = *grouping, g = grouping; *g; g++)
+      if (*g > 0 && *g < len)
+       len = *g;
+
+  slen = strlen (s);
+  len = slen / len + 1;
+  ret = (char *)xmalloc (slen + len + 1);
+  re = ret + slen + len;
+  *re = '\0';
+
+  g = grouping;
+  se = s + slen;
+  len = *g;
+
+  while (se > s)
+    {
+      *--re = *--se;
+
+      /* handle `-' inserted by numtoa() and the fmtu* family here. */
+      if (se > s && se[-1] == '-')
+       continue;
+
+      /* begin new group. */
+      if (--len == 0 && se > s)
+       {
+         *--re = thoussep;
+         len = *++g;           /* was g++, but that uses first char twice (glibc bug, too) */
+         if (*g == '\0')
+           len = *--g;         /* use previous grouping */
+         else if (*g == CHAR_MAX)
+           {
+             do
+               *--re = *--se;
+             while (se > s);
+             break;
+           }
+       }
+    }
+
+  if (re > ret)
+#ifdef HAVE_MEMMOVE
+    memmove (ret, re, strlen (re) + 1);
+#else
+    strcpy (ret, re);
+#endif
+   
+  return ret;
+}
+
 /* initialize the conversion specifiers */
 static void
 init_conv_flag (p)
@@ -887,18 +1265,28 @@ vsnprintf_internal(data, string, length, format, args)
 #endif
   int state, i, c, n;
   char *s;
+#if HANDLE_MULTIBYTE
+  wchar_t *ws;
+  wint_t wc;
+#endif
   const char *convstart;
+  int negprec;
 
-  /* Sanity check, the string must be > 1.  C99 actually says that LENGTH
-     can be zero here, in the case of snprintf/vsnprintf (it's never 0 in
-     the case of asprintf/vasprintf), and the return value is the number
+  /* Sanity check, the string length must be >= 0.  C99 actually says that
+     LENGTH can be zero here, in the case of snprintf/vsnprintf (it's never
+     0 in the case of asprintf/vasprintf), and the return value is the number
      of characters that would have been written. */
-  if (length < 1)
+  if (length < 0)
     return -1;
 
   if (format == 0)
     return 0;
 
+  /* Reset these for each call because the locale might have changed. */
+  decpoint = thoussep = 0;
+  grouping = 0;
+
+  negprec = 0;
   for (; c = *(data->pf); data->pf++)
     {
       if (c != '%')
@@ -944,10 +1332,6 @@ vsnprintf_internal(data, string, length, format, args)
              case '#':
                data->flags |= PF_ALTFORM;
                continue;
-             case '0':
-               data->flags |= PF_ZEROPAD;
-               data->pad = '0';
-               continue;
              case '*':
                if (data->flags & PF_DOT)
                  data->flags |= PF_STAR_P;
@@ -955,21 +1339,41 @@ vsnprintf_internal(data, string, length, format, args)
                  data->flags |= PF_STAR_W;
                continue;
              case '-':
-               data->flags |= PF_LADJUST;
-               data->justify = LEFT;
+               if ((data->flags & PF_DOT) == 0)
+                 {
+                   data->flags |= PF_LADJUST;
+                   data->justify = LEFT;
+                 }
+               else
+                 negprec = 1;
                continue;
              case ' ':
                if ((data->flags & PF_PLUS) == 0)
                  data->flags |= PF_SPACE;
                continue;
              case '+':
-               data->flags |= PF_PLUS;
-               data->justify = RIGHT;
+               if ((data->flags & PF_DOT) == 0)
+                 {
+                   data->flags |= PF_PLUS;
+                   if ((data->flags & PF_LADJUST) == 0)
+                     data->justify = RIGHT;
+                 }
                continue;
-             case ',':
-               data->flags |= PF_COMMA;                /* not implemented yet */
+             case '\'':
+               data->flags |= PF_THOUSANDS;
                continue;
 
+             case '0':
+               /* If we're not specifying precision (in which case we've seen
+                  a `.') and we're not performing left-adjustment (in which
+                  case the `0' is ignored), a `0' is taken as the zero-padding
+                  flag. */
+               if ((data->flags & (PF_DOT|PF_LADJUST)) == 0)
+                 {
+                   data->flags |= PF_ZEROPAD;
+                   data->pad = '0';
+                   continue;
+                 }
              case '1': case '2': case '3':
              case '4': case '5': case '6':
              case '7': case '8': case '9':
@@ -984,7 +1388,7 @@ vsnprintf_internal(data, string, length, format, args)
                if (n < 0)
                  n = 0;
                if (data->flags & PF_DOT)
-                 data->precision = n;
+                 data->precision = negprec ? NOT_FOUND : n;
                else
                  data->width = n;
                continue;
@@ -1036,16 +1440,26 @@ conv_break:
                STAR_ARGS(data);
                DEF_PREC(data);
                d = GETDOUBLE(data);
-               i = log_10(d);
+               i = (d != 0.) ? log_10(d) : -1;
                /*
                 * for '%g|%G' ANSI: use f if exponent
                 * is in the range or [-4,p] exclusively
                 * else use %e|%E
                 */
                if (-4 < i && i < data->precision)
-                 floating(data, d);
+                 {
+                   /* reset precision */
+                   data->precision -= i + 1;
+                   floating(data, d);
+                 }
                else
-                 exponent(data, d);
+                 {
+                   /* reduce precision by 1 because of leading digit before
+                      decimal point in e format, unless specified as 0. */
+                   if (data->precision > 0)
+                     data->precision--;
+                   exponent(data, d);
+                 }
                state = 0;
                break;
              case 'e':
@@ -1073,7 +1487,7 @@ conv_break:
 #ifdef HAVE_LONG_LONG
                if (data->flags & PF_LONGLONG)
                  {
-                   ull = va_arg(args, unsigned long long);
+                   ull = GETARG (unsigned long long);
                    lnumber(data, ull, 10);
                  }
                else
@@ -1093,7 +1507,7 @@ conv_break:
 #ifdef HAVE_LONG_LONG
                if (data->flags & PF_LONGLONG)
                  {
-                   ull = va_arg(args, long long);
+                   ull = GETARG (long long);
                    lnumber(data, ull, 10);
                  }
                else
@@ -1109,7 +1523,7 @@ conv_break:
 #ifdef HAVE_LONG_LONG
                if (data->flags & PF_LONGLONG)
                  {
-                   ull = va_arg(args, unsigned long long);
+                   ull = GETARG (unsigned long long);
                    lnumber(data, ull, 8);
                  }
                else
@@ -1126,7 +1540,7 @@ conv_break:
 #ifdef HAVE_LONG_LONG
                if (data->flags & PF_LONGLONG)
                  {
-                   ull = va_arg(args, unsigned long long);
+                   ull = GETARG (unsigned long long);
                    lnumber(data, ull, 16);
                  }
                else
@@ -1139,33 +1553,64 @@ conv_break:
                break;
              case 'p':
                STAR_ARGS(data);
-               ul = (unsigned long)va_arg(args, void *);
+               ul = (unsigned long)GETARG (void *);
                pointer(data, ul);
                state = 0;
                break;
+#if HANDLE_MULTIBYTE
+             case 'C':
+               data->flags |= PF_LONGINT;
+               /* FALLTHROUGH */
+#endif
              case 'c': /* character */
-               ul = va_arg(args, int);
-               PUT_CHAR(ul, data);
+               STAR_ARGS(data);
+#if HANDLE_MULTIBYTE
+               if (data->flags & PF_LONGINT)
+                 {
+                   wc = GETARG (wint_t);
+                   wchars (data, wc);
+                 }
+               else
+#endif
+                 {             
+                   ul = GETARG (int);
+                   PUT_CHAR(ul, data);
+                 }
                state = 0;
                break;
+#if HANDLE_MULTIBYTE
+             case 'S':
+               data->flags |= PF_LONGINT;
+               /* FALLTHROUGH */
+#endif
              case 's':  /* string */
                STAR_ARGS(data);
-               s = va_arg(args, char *);
-               strings(data, s);
+#if HANDLE_MULTIBYTE
+               if (data->flags & PF_LONGINT)
+                 {
+                   ws = GETARG (wchar_t *);
+                   wstrings (data, ws);
+                 }
+               else
+#endif
+                 {
+                   s = GETARG (char *);
+                   strings(data, s);
+                 }
                state = 0;
                break;
              case 'n':
 #ifdef HAVE_LONG_LONG
                if (data->flags & PF_LONGLONG)
-                 *(va_arg(args, long long *)) = data->counter;
+                 *(GETARG (long long *)) = data->counter;
                else
 #endif
                if (data->flags & PF_LONGINT)
-                 *(va_arg(args, long *)) = data->counter;
+                 *(GETARG (long *)) = data->counter;
                else if (data->flags & PF_SHORTINT)
-                 *(va_arg(args, short *)) = data->counter;
+                 *(GETARG (short *)) = data->counter;
                else
-                 *(va_arg(args, int *)) = data->counter;
+                 *(GETARG (int *)) = data->counter;
                state = 0;
                break;
              case '%':  /* nothing just % */
@@ -1201,11 +1646,21 @@ ldfallback (data, fs, fe, ld)
   char fmtbuf[FALLBACK_FMTSIZE], *obuf;
   int fl;
 
-  obuf = xmalloc(LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2);
+  fl = LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2;
+  obuf = (char *)xmalloc (fl);
   fl = fe - fs + 1;
   strncpy (fmtbuf, fs, fl);
   fmtbuf[fl] = '\0';
-  sprintf (obuf, fmtbuf, ld);
+
+  if ((data->flags & PF_STAR_W) && (data->flags & PF_STAR_P))
+    sprintf (obuf, fmtbuf, data->width, data->precision, ld);
+  else if (data->flags & PF_STAR_W)
+    sprintf (obuf, fmtbuf, data->width, ld);
+  else if (data->flags & PF_STAR_P)
+    sprintf (obuf, fmtbuf, data->precision, ld);
+  else
+    sprintf (obuf, fmtbuf, ld);
+
   for (x = obuf; *x; x++)
     PUT_CHAR (*x, data);    
   xfree (obuf);
@@ -1227,13 +1682,22 @@ dfallback (data, fs, fe, d)
   fl = fe - fs + 1;
   strncpy (fmtbuf, fs, fl);
   fmtbuf[fl] = '\0';
-  sprintf (obuf, fmtbuf, d);
+
+  if ((data->flags & PF_STAR_W) && (data->flags & PF_STAR_P))
+    sprintf (obuf, fmtbuf, data->width, data->precision, d);
+  else if (data->flags & PF_STAR_W)
+    sprintf (obuf, fmtbuf, data->width, d);
+  else if (data->flags & PF_STAR_P)
+    sprintf (obuf, fmtbuf, data->precision, d);
+  else
+    sprintf (obuf, fmtbuf, d);
+
   for (x = obuf; *x; x++)
     PUT_CHAR (*x, data);    
 }
 #endif /* FLOATING_POINT */
 
-#ifndef HAVE_SNPRINTF
+#if !HAVE_SNPRINTF
 
 int
 #if defined (__STDC__)
@@ -1248,6 +1712,8 @@ vsnprintf(string, length, format, args)
 {
   struct DATA data;
 
+  if (string == 0 && length != 0)
+    return 0;
   init_data (&data, string, length, format, PFM_SN);
   return (vsnprintf_internal(&data, string, length, format, args));
 }
@@ -1267,12 +1733,10 @@ snprintf(string, length, format, va_alist)
   int rval;
   va_list args;
 
-#if defined(PREFER_STDARG)
-  va_start(args, format);
-#else
-  va_start(args);
-#endif
+  SH_VA_START(args, format);
 
+  if (string == 0 && length != 0)
+    return 0;
   init_data (&data, string, length, format, PFM_SN);
   rval = vsnprintf_internal (&data, string, length, format, args);
 
@@ -1283,7 +1747,7 @@ snprintf(string, length, format, va_alist)
 
 #endif /* HAVE_SNPRINTF */
 
-#ifndef HAVE_ASPRINTF
+#if !HAVE_ASPRINTF
 
 int
 #if defined (__STDC__)
@@ -1319,11 +1783,7 @@ asprintf(stringp, format, va_alist)
   int rval;
   va_list args;
 
-#if defined(PREFER_STDARG)
-  va_start(args, format);
-#else
-  va_start(args);
-#endif
+  SH_VA_START(args, format);
 
   rval = vasprintf (stringp, format, args);
 
@@ -1332,9 +1792,9 @@ asprintf(stringp, format, va_alist)
   return rval;
 }
 
-#endif
+#endif /* !HAVE_ASPRINTF */
 
-#endif
+#endif /* !HAVE_SNPRINTF || !HAVE_ASPRINTF */
 
 #ifdef DRIVER
 
@@ -1378,10 +1838,6 @@ xfree(x)
     free (x);
 }
 
-#ifdef FLOATING_POINT
-#  include <float.h>
-#endif
-
 /* set of small tests for snprintf() */
 main()
 {
@@ -1389,6 +1845,18 @@ main()
   char *h;
   int i, si, ai;
 
+#ifdef HAVE_LOCALE_H
+  setlocale(LC_ALL, "");
+#endif
+
+#if 1
+  si = snprintf((char *)NULL, 0, "abcde\n");
+  printf("snprintf returns %d with NULL first argument and size of 0\n", si);
+  si = snprintf(holder, 0, "abcde\n");
+  printf("snprintf returns %d with non-NULL first argument and size of 0\n", si);
+  si = snprintf((char *)NULL, 16, "abcde\n");
+  printf("snprintf returns %d with NULL first argument and non-zero size\n", si);
+  
 /*
   printf("Suite of test for snprintf:\n");
   printf("a_format\n");
@@ -1660,6 +2128,92 @@ main()
   printf ("<%d> <%s>\n", si, holder);
   printf ("<%d> <%s>\n\n", ai, h);
 
+  /* huh? */
+  printf("/%%g/, 421.2345\n");
+  snprintf(holder, sizeof holder, "/%g/\n", 421.2345);
+  asprintf(&h, "/%g/\n", 421.2345);
+  printf("/%g/\n", 421.2345);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%g/, 4214.2345\n");
+  snprintf(holder, sizeof holder, "/%g/\n", 4214.2345);
+  asprintf(&h, "/%g/\n", 4214.2345);
+  printf("/%g/\n", 4214.2345);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%.5g/, 4214.2345\n");
+  snprintf(holder, sizeof holder, "/%.5g/\n", 4214.2345);
+  asprintf(&h, "/%.5g/\n", 4214.2345);
+  printf("/%.5g/\n", 4214.2345);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%.4g/, 4214.2345\n");
+  snprintf(holder, sizeof holder, "/%.4g/\n", 4214.2345);
+  asprintf(&h, "/%.4g/\n", 4214.2345);
+  printf("/%.4g/\n", 4214.2345);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'ld %%'ld/, 12345, 1234567\n");
+  snprintf(holder, sizeof holder, "/%'ld %'ld/\n", 12345, 1234567);
+  asprintf(&h, "/%'ld %'ld/\n", 12345, 1234567);
+  printf("/%'ld %'ld/\n", 12345, 1234567);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'ld %%'ld/, 336, 3336\n");
+  snprintf(holder, sizeof holder, "/%'ld %'ld/\n", 336, 3336);
+  asprintf(&h, "/%'ld %'ld/\n", 336, 3336);
+  printf("/%'ld %'ld/\n", 336, 3336);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'ld %%'ld/, -42786, -142786\n");
+  snprintf(holder, sizeof holder, "/%'ld %'ld/\n", -42786, -142786);
+  asprintf(&h, "/%'ld %'ld/\n", -42786, -142786);
+  printf("/%'ld %'ld/\n", -42786, -142786);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'f %%'f/, 421.2345, 421234.56789\n");
+  snprintf(holder, sizeof holder, "/%'f %'f/\n", 421.2345, 421234.56789);
+  asprintf(&h, "/%'f %'f/\n", 421.2345, 421234.56789);
+  printf("/%'f %'f/\n", 421.2345, 421234.56789);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'f %%'f/, -421.2345, -421234.56789\n");
+  snprintf(holder, sizeof holder, "/%'f %'f/\n", -421.2345, -421234.56789);
+  asprintf(&h, "/%'f %'f/\n", -421.2345, -421234.56789);
+  printf("/%'f %'f/\n", -421.2345, -421234.56789);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'g %%'g/, 421.2345, 421234.56789\n");
+  snprintf(holder, sizeof holder, "/%'g %'g/\n", 421.2345, 421234.56789);
+  asprintf(&h, "/%'g %'g/\n", 421.2345, 421234.56789);
+  printf("/%'g %'g/\n", 421.2345, 421234.56789);
+  printf("%s", holder);
+  printf("%s\n", h);
+
+  printf("/%%'g %%'g/, -421.2345, -421234.56789\n");
+  snprintf(holder, sizeof holder, "/%'g %'g/\n", -421.2345, -421234.56789);
+  asprintf(&h, "/%'g %'g/\n", -421.2345, -421234.56789);
+  printf("/%'g %'g/\n", -421.2345, -421234.56789);
+  printf("%s", holder);
+  printf("%s\n", h);
+#endif
+
+  printf("/%%'g/, 4213455.8392\n");
+  snprintf(holder, sizeof holder, "/%'g/\n", 4213455.8392);
+  asprintf(&h, "/%'g/\n", 4213455.8392);
+  printf("/%'g/\n", 4213455.8392);
+  printf("%s", holder);
+  printf("%s\n", h);
+
   exit (0);
 }
 #endif