]> sjero.net Git - wget/blobdiff - src/snprintf.c
[svn] Remove K&R support.
[wget] / src / snprintf.c
index f8dffa4ab12a4a380146c8e405acc10181ce62b4..78228e6879c0edf16f61ba539a9e2a4def2ecf42 100644 (file)
  *    don't declare argument types to (v)snprintf if stdarg is not used.
  *    use int instead of short int as 2nd arg to va_arg.
  *
+ *  alexk (INN) 2002-08-21
+ *    use LLONG in fmtfp to handle more characters during floating
+ *    point conversion.
+ *
+ *  herb (Samba) 2002-12-19
+ *    actually print args for %g and %e
+ *
+ *  Hrvoje Niksic <hniksic@xemacs.org> 2005-04-15
+ *    write function definitions in the ansi2knr-friendly way.
+ *    if string precision is specified, don't read VALUE past it.
+ *    fix bug in fmtfp that caused 0.01 to be printed as 0.1.
+ *    don't include <ctype.h> because none of it is used.
+ *    interpret precision as number of significant digits with %g
+ *    omit trailing decimal zeros with %g
+ *
  **************************************************************/
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
 
+/* For testing purposes, always compile in the code. */
+#ifdef TEST_SNPRINTF
+# undef HAVE_SNPRINTF
+# undef HAVE_VSNPRINTF
+# ifndef SIZEOF_LONG_LONG
+#  ifdef __GNUC__
+#   define SIZEOF_LONG_LONG 8
+#  endif
+# endif
+#endif
+
 #if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF)
 
 #include <string.h>
 #include <sys/types.h>
 #include <stdio.h>             /* for NULL */
-#include <safe-ctype.h>
 
-/* varargs declarations: */
-
-#if defined(HAVE_STDARG_H)
-# include <stdarg.h>
-# define HAVE_STDARGS    /* let's hope that works everywhere (mj) */
-# define VA_LOCAL_DECL   va_list ap
-# define VA_START(f)     va_start(ap, f)
-# define VA_SHIFT(v,t)  ;   /* no-op for ANSI */
-# define VA_END          va_end(ap)
-#else
-# include <varargs.h>
-# undef HAVE_STDARGS
-# define VA_LOCAL_DECL   va_list ap
-# define VA_START(f)     va_start(ap)      /* f is ignored! */
-# define VA_SHIFT(v,t) v = va_arg(ap,t)
-# define VA_END        va_end(ap)
-#endif
+#include <stdarg.h>
 
 #ifdef HAVE_LONG_DOUBLE
 #define LDOUBLE long double
 #define LDOUBLE double
 #endif
 
-#ifdef HAVE_LONG_LONG
+#if SIZEOF_LONG_LONG != 0
 # define LLONG long long
 #else
 # define LLONG long
 #endif
 
-#ifdef HAVE_STDARGS
+/* If we're running the test suite, rename snprintf and vsnprintf to
+   avoid conflicts with the system version.  */
+#ifdef TEST_SNPRINTF
+# define snprintf test_snprintf
+# define vsnprintf test_vsnprintf
+#endif
+
 int snprintf (char *str, size_t count, const char *fmt, ...);
 int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
-#else
-int snprintf ();
-int vsnprintf ();
-#endif
 
-static int dopr (char *buffer, size_t maxlen, const char *format, 
-                 va_list args);
+static int dopr (char *buffer, size_t maxlen, const char *format, va_list args);
 static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
-                  char *value, int flags, int min, int max);
+                  const char *value, int flags, int min, int max);
 static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
                   LLONG value, int base, int min, int max, int flags);
 static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
                  LDOUBLE fvalue, int min, int max, int flags);
-static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
+static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c);
 
 /*
  * dopr(): poor man's version of doprintf
@@ -154,6 +164,7 @@ static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
 #define DP_F_ZERO      (1 << 4)
 #define DP_F_UP        (1 << 5)
 #define DP_F_UNSIGNED  (1 << 6)
+#define DP_F_FP_G      (1 << 7)
 
 /* Conversion Flags */
 #define DP_C_SHORT   1
@@ -165,7 +176,8 @@ static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
 #define MAX(p,q) ((p >= q) ? p : q)
 #define MIN(p,q) ((p <= q) ? p : q)
 
-static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
+static int
+dopr (char *buffer, size_t maxlen, const char *format, va_list args)
 {
   char ch;
   LLONG value;
@@ -309,7 +321,7 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
       case 'd':
       case 'i':
        if (cflags == DP_C_SHORT) 
-         value = (short int)va_arg (args, int);
+         value = (short int) va_arg (args, int);
        else if (cflags == DP_C_LONG)
          value = va_arg (args, long int);
        else if (cflags == DP_C_LLONG)
@@ -321,7 +333,7 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
       case 'o':
        flags |= DP_F_UNSIGNED;
        if (cflags == DP_C_SHORT)
-         value = (unsigned short int)va_arg (args, unsigned int);
+         value = (unsigned short int) va_arg (args, unsigned int);
        else if (cflags == DP_C_LONG)
          value = va_arg (args, unsigned long int);
        else if (cflags == DP_C_LLONG)
@@ -333,7 +345,7 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
       case 'u':
        flags |= DP_F_UNSIGNED;
        if (cflags == DP_C_SHORT)
-         value = (unsigned short int)va_arg (args, unsigned int);
+         value = (unsigned short int) va_arg (args, unsigned int);
        else if (cflags == DP_C_LONG)
          value = va_arg (args, unsigned long int);
        else if (cflags == DP_C_LLONG)
@@ -347,7 +359,7 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
       case 'x':
        flags |= DP_F_UNSIGNED;
        if (cflags == DP_C_SHORT)
-         value = (unsigned short int)va_arg (args, unsigned int);
+         value = (unsigned short int) va_arg (args, unsigned int);
        else if (cflags == DP_C_LONG)
          value = va_arg (args, unsigned long int);
        else if (cflags == DP_C_LLONG)
@@ -361,7 +373,6 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
          fvalue = va_arg (args, LDOUBLE);
        else
          fvalue = va_arg (args, double);
-       /* um, floating point? */
        total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
        break;
       case 'E':
@@ -371,14 +382,20 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
          fvalue = va_arg (args, LDOUBLE);
        else
          fvalue = va_arg (args, double);
+       total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
        break;
       case 'G':
        flags |= DP_F_UP;
       case 'g':
+       flags |= DP_F_FP_G;
        if (cflags == DP_C_LDOUBLE)
          fvalue = va_arg (args, LDOUBLE);
        else
          fvalue = va_arg (args, double);
+       if (max == 0)
+         /* C99 says: if precision [for %g] is zero, it is taken as one */
+         max = 1;
+       total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
        break;
       case 'c':
        total += dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
@@ -451,8 +468,9 @@ static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
   return total;
 }
 
-static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
-                   char *value, int flags, int min, int max)
+static int
+fmtstr (char *buffer, size_t *currlen, size_t maxlen,
+       const char *value, int flags, int min, int max)
 {
   int padlen, strln;     /* amount to pad */
   int cnt = 0;
@@ -460,12 +478,15 @@ static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
   
   if (value == 0)
   {
-    value = "<NULL>";
+    value = "(null)";
   }
 
-  for (strln = 0; value[strln]; ++strln); /* strlen */
-  if (max >= 0 && max < strln)
-    strln = max;
+  if (max < 0)
+    strln = strlen (value);
+  else
+    /* When precision is specified, don't read VALUE past precision. */
+    /*strln = strnlen (value, max);*/
+    for (strln = 0; strln < max && value[strln]; ++strln);
   padlen = min - strln;
   if (padlen < 0) 
     padlen = 0;
@@ -492,8 +513,9 @@ static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
 
 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
 
-static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
-                  LLONG value, int base, int min, int max, int flags)
+static int
+fmtint (char *buffer, size_t *currlen, size_t maxlen,
+       LLONG value, int base, int min, int max, int flags)
 {
   int signvalue = 0;
   unsigned LLONG uvalue;
@@ -587,7 +609,8 @@ static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
   return total;
 }
 
-static LDOUBLE abs_val (LDOUBLE value)
+static LDOUBLE
+abs_val (LDOUBLE value)
 {
   LDOUBLE result = value;
 
@@ -597,7 +620,8 @@ static LDOUBLE abs_val (LDOUBLE value)
   return result;
 }
 
-static LDOUBLE pow10 (int exp)
+static LDOUBLE
+pow10 (int exp)
 {
   LDOUBLE result = 1;
 
@@ -610,9 +634,10 @@ static LDOUBLE pow10 (int exp)
   return result;
 }
 
-static long round (LDOUBLE value)
+static LLONG
+round (LDOUBLE value)
 {
-  long intpart;
+  LLONG intpart;
 
   intpart = value;
   value = value - intpart;
@@ -622,21 +647,25 @@ static long round (LDOUBLE value)
   return intpart;
 }
 
-static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
-                 LDOUBLE fvalue, int min, int max, int flags)
+static int
+fmtfp (char *buffer, size_t *currlen, size_t maxlen,
+       LDOUBLE fvalue, int min, int max, int flags)
 {
   int signvalue = 0;
   LDOUBLE ufvalue;
-  char iconvert[20];
-  char fconvert[20];
+  char iconvert[24];
+  char fconvert[24];
   int iplace = 0;
   int fplace = 0;
   int padlen = 0; /* amount to pad */
   int zpadlen = 0; 
-  int caps = 0;
   int total = 0;
-  long intpart;
-  long fracpart;
+  LLONG intpart;
+  LLONG fracpart;
+  LLONG mask10;
+  int leadingfrac0s = 0; /* zeros at the start of fractional part */
+  int omitzeros = 0;
+  int omitcount = 0;
   
   /* 
    * AIX manpage says the default is 0, but Solaris says the default
@@ -662,23 +691,70 @@ static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
 
   intpart = ufvalue;
 
+  /* With %g precision is the number of significant digits, which
+     includes the digits in intpart. */
+  if (flags & DP_F_FP_G)
+    {
+      if (intpart != 0)
+       {
+         /* For each digit of INTPART, print one less fractional digit. */
+         LLONG temp = intpart;
+         for (temp = intpart; temp != 0; temp /= 10)
+           --max;
+         if (max < 0)
+           max = 0;
+       }
+      else
+       {
+         /* For each leading 0 in fractional part, print one more
+            fractional digit. */
+         LDOUBLE temp;
+         if (ufvalue != 0)
+           for (temp = ufvalue; temp < 0.1; temp *= 10)
+             ++max;
+       }
+    }
+
+  /* C99: trailing zeros are removed from the fractional portion of the
+     result unless the # flag is specified */
+  if ((flags & DP_F_FP_G) && !(flags & DP_F_NUM))
+    omitzeros = 1;
+
+#if SIZEOF_LONG_LONG > 0
+# define MAX_DIGITS 18         /* grok more digits with long long */
+#else
+# define MAX_DIGITS 9          /* just long */
+#endif
+
   /* 
-   * Sorry, we only support 9 digits past the decimal because of our 
-   * conversion method
+   * Sorry, we only support several digits past the decimal because of
+   * our conversion method
    */
-  if (max > 9)
-    max = 9;
+  if (max > MAX_DIGITS)
+    max = MAX_DIGITS;
+
+  /* Factor of 10 with the needed number of digits, e.g. 1000 for max==3 */
+  mask10 = pow10 (max);
 
   /* We "cheat" by converting the fractional part to integer by
    * multiplying by a factor of 10
    */
-  fracpart = round ((pow10 (max)) * (ufvalue - intpart));
+  fracpart = round (mask10 * (ufvalue - intpart));
 
-  if (fracpart >= pow10 (max))
+  if (fracpart >= mask10)
   {
     intpart++;
-    fracpart -= pow10 (max);
+    fracpart -= mask10;
   }
+  else if (fracpart != 0)
+    /* If fracpart has less digits than the 10* mask, we need to
+       manually insert leading 0s.  For example 2.01's fractional part
+       requires one leading zero to distinguish it from 2.1. */
+    while (fracpart < mask10 / 10)
+      {
+       ++leadingfrac0s;
+       mask10 /= 10;
+      }
 
 #ifdef DEBUG_SNPRINTF
   dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart));
@@ -686,25 +762,29 @@ static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
 
   /* Convert integer part */
   do {
-    iconvert[iplace++] =
-      (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10];
+    iconvert[iplace++] = '0' + intpart % 10;
     intpart = (intpart / 10);
-  } while(intpart && (iplace < 20));
-  if (iplace == 20) iplace--;
+  } while(intpart && (iplace < sizeof(iconvert)));
+  if (iplace == sizeof(iconvert)) iplace--;
   iconvert[iplace] = 0;
 
   /* Convert fractional part */
   do {
-    fconvert[fplace++] =
-      (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10];
+    fconvert[fplace++] = '0' + fracpart % 10;
     fracpart = (fracpart / 10);
-  } while(fracpart && (fplace < 20));
-  if (fplace == 20) fplace--;
+  } while(fracpart && (fplace < sizeof(fconvert)));
+  while (leadingfrac0s-- > 0 && fplace < sizeof(fconvert))
+    fconvert[fplace++] = '0';
+  if (fplace == sizeof(fconvert)) fplace--;
   fconvert[fplace] = 0;
+  if (omitzeros)
+    while (omitcount < fplace && fconvert[omitcount] == '0')
+      ++omitcount;
 
   /* -1 for decimal point, another -1 if we are printing a sign */
-  padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); 
-  zpadlen = max - fplace;
+  padlen = min - iplace - (max - omitcount) - 1 - ((signvalue) ? 1 : 0);
+  if (!omitzeros)
+    zpadlen = max - fplace;
   if (zpadlen < 0)
     zpadlen = 0;
   if (padlen < 0) 
@@ -741,11 +821,11 @@ static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
    * Decimal point.  This should probably use locale to find the correct
    * char to print out.
    */
-  if (max > 0)
+  if (max > 0 && (fplace > omitcount || zpadlen > 0))
   {
     total += dopr_outch (buffer, currlen, maxlen, '.');
 
-    while (fplace > 0
+    while (fplace > omitcount
       total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
   }
 
@@ -764,7 +844,8 @@ static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
   return total;
 }
 
-static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
+static int
+dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
 {
   if (*currlen + 1 < maxlen)
     buffer[(*currlen)++] = c;
@@ -772,7 +853,8 @@ static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
 }
 
 #ifndef HAVE_VSNPRINTF
-int vsnprintf (char *str, size_t count, const char *fmt, va_list args)
+int
+vsnprintf (char *str, size_t count, const char *fmt, va_list args)
 {
   if (str != NULL)
     str[0] = 0;
@@ -781,27 +863,15 @@ int vsnprintf (char *str, size_t count, const char *fmt, va_list args)
 #endif /* !HAVE_VSNPRINTF */
 
 #ifndef HAVE_SNPRINTF
-/* VARARGS3 */
-#ifdef HAVE_STDARGS
-int snprintf (char *str,size_t count,const char *fmt,...)
-#else
-int snprintf (va_alist) va_dcl
-#endif
+int
+snprintf (char *str, size_t count, const char *fmt,...)
 {
-#ifndef HAVE_STDARGS
-  char *str;
-  size_t count;
-  char *fmt;
-#endif
-  VA_LOCAL_DECL;
+  va_list ap;
   int total;
-    
-  VA_START (fmt);
-  VA_SHIFT (str, char *);
-  VA_SHIFT (count, size_t );
-  VA_SHIFT (fmt, char *);
-  total = vsnprintf(str, count, fmt, ap);
-  VA_END;
+
+  va_start (ap, fmt);
+  total = vsnprintf (str, count, fmt, ap);
+  va_end (ap);
   return total;
 }
 #endif /* !HAVE_SNPRINTF */
@@ -809,16 +879,18 @@ int snprintf (va_alist) va_dcl
 
 #ifdef TEST_SNPRINTF
 
-#include <stdio.h>
+# ifndef LONG_STRING
+#  define LONG_STRING 1024
+# endif
 
-#ifndef LONG_STRING
-#define LONG_STRING 1024
-#endif
-int main (void)
+int
+main (void)
 {
   char buf1[LONG_STRING];
   char buf2[LONG_STRING];
   char *fp_fmt[] = {
+    /* %f formats */
+    "%f",
     "%-1.5f",
     "%1.5f",
     "%123.9f",
@@ -832,10 +904,28 @@ int main (void)
     "%3.2f",
     "%.0f",
     "%.1f",
+    "%#10.1f",
+#if SIZEOF_LONG_LONG != 0
+    "%.16f",
+    "%18.16f",
+    "%-16.16f",
+#endif
+    /* %g formats */
+    "%g",
+    "%1.5g",
+    "%-1.5g",
+    "%.9g",
+    "%123.9g",
+    "%#123.9g",
+#if SIZEOF_LONG_LONG != 0
+    "%.16g",
+    "%20.16g",
+#endif
     NULL
   };
   double fp_nums[] = { -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996, 
-    0.9996, 1.996, 4.136, 0};
+                      0.9996, 1.996, 4.136, 0.00205, 0.0001, 321.000009,
+                      0};
   char *int_fmt[] = {
     "%-1.5d",
     "%1.5d",
@@ -849,7 +939,7 @@ int main (void)
     NULL
   };
   long int_nums[] = { -1, 134, 91340, 341, 0203, 0};
-#ifdef HAVE_LONG_LONG
+#if SIZEOF_LONG_LONG != 0
   char *llong_fmt[] = {
     "%lld",            "%llu",
     "%-1.5lld",                "%-1.5llu",
@@ -905,7 +995,7 @@ int main (void)
       num++;
     }
 
-#ifdef HAVE_LONG_LONG
+#if SIZEOF_LONG_LONG != 0
   for (x = 0; llong_fmt[x] != NULL ; x++)
     for (y = 0; llong_nums[y] != 0 ; y++)
     {
@@ -922,5 +1012,6 @@ int main (void)
 #endif
 
   printf ("%d tests failed out of %d.\n", fail, num);
+  return 0;
 }
-#endif /* SNPRINTF_TEST */
+#endif /* TEST_SNPRINTF */