Fix implementation of "e" and "g" formats for printing floating points
authorMatt Caswell <matt@openssl.org>
Wed, 25 May 2016 14:33:15 +0000 (15:33 +0100)
committerMatt Caswell <matt@openssl.org>
Fri, 27 May 2016 09:23:18 +0000 (10:23 +0100)
The previous commit which "fixed" the "e" and "g" floating point formats
just printed them in the same way as "f". This is wrong. This commit
provides the correct formatting.

Reviewed-by: Richard Levitte <levitte@openssl.org>
crypto/bio/b_print.c

index 0ae4f25af152f6191cbdb30959579ae48abb10ca..36400cda5e9deff690878fdfcf18fab56240e82a 100644 (file)
@@ -52,7 +52,7 @@ static int fmtstr(char **, char **, size_t *, size_t *,
 static int fmtint(char **, char **, size_t *, size_t *,
                   LLONG, int, int, int, int);
 static int fmtfp(char **, char **, size_t *, size_t *,
-                 LDOUBLE, int, int, int);
+                 LDOUBLE, int, int, int, int);
 static int doapr_outch(char **, char **, size_t *, size_t *, int);
 static int _dopr(char **sbuffer, char **buffer,
                  size_t *maxlen, size_t *retlen, int *truncated,
@@ -90,6 +90,11 @@ static int _dopr(char **sbuffer, char **buffer,
 #define DP_C_LDOUBLE    3
 #define DP_C_LLONG      4
 
+/* Floating point formats */
+#define F_FORMAT        0
+#define E_FORMAT        1
+#define G_FORMAT        2
+
 /* some handy macros */
 #define char_to_int(p) (p - '0')
 #define OSSL_MAX(p,q) ((p >= q) ? p : q)
@@ -268,7 +273,7 @@ _dopr(char **sbuffer,
                 else
                     fvalue = va_arg(args, double);
                 if (!fmtfp(sbuffer, buffer, &currlen, maxlen, fvalue, min, max,
-                           flags))
+                           flags, F_FORMAT))
                     return 0;
                 break;
             case 'E':
@@ -279,7 +284,7 @@ _dopr(char **sbuffer,
                 else
                     fvalue = va_arg(args, double);
                 if (!fmtfp(sbuffer, buffer, &currlen, maxlen, fvalue, min, max,
-                           flags))
+                           flags, E_FORMAT))
                     return 0;
                 break;
             case 'G':
@@ -290,7 +295,7 @@ _dopr(char **sbuffer,
                 else
                     fvalue = va_arg(args, double);
                 if (!fmtfp(sbuffer, buffer, &currlen, maxlen, fvalue, min, max,
-                           flags))
+                           flags, G_FORMAT))
                     return 0;
                 break;
             case 'c':
@@ -542,23 +547,28 @@ static int
 fmtfp(char **sbuffer,
       char **buffer,
       size_t *currlen,
-      size_t *maxlen, LDOUBLE fvalue, int min, int max, int flags)
+      size_t *maxlen, LDOUBLE fvalue, int min, int max, int flags, int style)
 {
     int signvalue = 0;
     LDOUBLE ufvalue;
+    LDOUBLE tmpvalue;
     char iconvert[20];
     char fconvert[20];
+    char econvert[20];
     int iplace = 0;
     int fplace = 0;
+    int eplace = 0;
     int padlen = 0;
     int zpadlen = 0;
+    long exp = 0;
     long intpart;
     long fracpart;
     long max10;
+    int realstyle;
 
     if (max < 0)
         max = 6;
-    ufvalue = abs_val(fvalue);
+
     if (fvalue < 0)
         signvalue = '-';
     else if (flags & DP_F_PLUS)
@@ -566,6 +576,68 @@ fmtfp(char **sbuffer,
     else if (flags & DP_F_SPACE)
         signvalue = ' ';
 
+    /*
+     * G_FORMAT sometimes prints like E_FORMAT and sometimes like F_FORMAT
+     * depending on the number to be printed. Work out which one it is and use
+     * that from here on.
+     */
+    if (style == G_FORMAT) {
+        if (fvalue == 0.0) {
+            realstyle = F_FORMAT;
+        } else if (fvalue < 0.0001) {
+            realstyle = E_FORMAT;
+        } else if ((max == 0 && fvalue >= 10)
+                    || (max > 0 && fvalue >= pow_10(max))) {
+            realstyle = E_FORMAT;
+        } else {
+            realstyle = F_FORMAT;
+        }
+    } else {
+        realstyle = style;
+    }
+
+    if (style != F_FORMAT) {
+        tmpvalue = fvalue;
+        /* Calculate the exponent */
+        if (fvalue != 0.0) {
+            while (tmpvalue < 1) {
+                tmpvalue *= 10;
+                exp--;
+            }
+            while (tmpvalue > 10) {
+                tmpvalue /= 10;
+                exp++;
+            }
+        }
+        if (style == G_FORMAT) {
+            /*
+             * In G_FORMAT the "precision" represents significant digits. We
+             * always have at least 1 significant digit.
+             */
+            if (max == 0)
+                max = 1;
+            /* Now convert significant digits to decimal places */
+            if (realstyle == F_FORMAT) {
+                max -= (exp + 1);
+                if (max < 0) {
+                    /*
+                     * Should not happen. If we're in F_FORMAT then exp < max?
+                     */
+                    return 0;
+                }
+            } else {
+                /*
+                 * In E_FORMAT there is always one significant digit in front
+                 * of the decimal point, so:
+                 * significant digits == 1 + decimal places
+                 */
+                max--;
+            }
+        }
+        if (realstyle == E_FORMAT)
+            fvalue = tmpvalue;
+    }
+    ufvalue = abs_val(fvalue);
     intpart = (long)ufvalue;
 
     /*
@@ -597,16 +669,51 @@ fmtfp(char **sbuffer,
     iconvert[iplace] = 0;
 
     /* convert fractional part */
-    do {
+    while (fplace < max) {
+        if (style == G_FORMAT && fplace == 0 && (fracpart % 10) == 0) {
+            /* We strip trailing zeros in G_FORMAT */
+            max--;
+            fracpart = fracpart / 10;
+            if (fplace < max)
+                continue;
+            break;
+        }
         fconvert[fplace++] = "0123456789"[fracpart % 10];
         fracpart = (fracpart / 10);
-    } while (fplace < max);
+    }
+
     if (fplace == sizeof fconvert)
         fplace--;
     fconvert[fplace] = 0;
 
-    /* -1 for decimal point, another -1 if we are printing a sign */
-    padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
+    /* convert exponent part */
+    if (realstyle == E_FORMAT) {
+        int tmpexp;
+        if (exp < 0)
+            tmpexp = -exp;
+        else
+            tmpexp = exp;
+
+        do {
+            econvert[eplace++] = "0123456789"[tmpexp % 10];
+            tmpexp = (tmpexp / 10);
+        } while (tmpexp > 0 && eplace < (int)sizeof(econvert));
+        /* Exponent is huge!! Too big to print */
+        if (tmpexp > 0)
+            return 0;
+        /* Add a leading 0 for single digit exponents */
+        if (eplace == 1)
+            econvert[eplace++] = '0';
+    }
+
+    /*
+     * -1 for decimal point (if we have one, i.e. max > 0),
+     * another -1 if we are printing a sign
+     */
+    padlen = min - iplace - max - (max > 0 ? 1 : 0) - ((signvalue) ? 1 : 0);
+    /* Take some off for exponent prefix "+e" and exponent */
+    if (realstyle == E_FORMAT)
+        padlen -= 2 + eplace;
     zpadlen = max - fplace;
     if (zpadlen < 0)
         zpadlen = 0;
@@ -660,6 +767,28 @@ fmtfp(char **sbuffer,
             return 0;
         --zpadlen;
     }
+    if (realstyle == E_FORMAT) {
+        char ech;
+
+        if ((flags & DP_F_UP) == 0)
+            ech = 'e';
+        else
+            ech = 'E';
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, ech))
+                return 0;
+        if (exp < 0) {
+            if (!doapr_outch(sbuffer, buffer, currlen, maxlen, '-'))
+                    return 0;
+        } else {
+            if (!doapr_outch(sbuffer, buffer, currlen, maxlen, '+'))
+                    return 0;
+        }
+        while (eplace > 0) {
+            if (!doapr_outch(sbuffer, buffer, currlen, maxlen,
+                             econvert[--eplace]))
+                return 0;
+        }
+    }
 
     while (padlen < 0) {
         if (!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))