Add limited support for printing floating point numbers to
VG_(debugLog_vprintf). 
Remove function VG_(percentify) and fix up its call sites (part of
fixing BZ #337869.
Allow the width in a format specification to be '*', i.e. the width is
given as an additional function argument.

The limitations for printing floating point numbers are:
(1) %f is the only supported format. Width and precision can be
    specified.
(2) Funny numbers (NaN and such) are not supported.
(3) Floating point numbers need to be benign in the sense that their
    integral part fits into an ULong.
This is good enough for our purposes.


git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14806 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/cachegrind/cg_main.c b/cachegrind/cg_main.c
index 20fafbc..559eea9 100644
--- a/cachegrind/cg_main.c
+++ b/cachegrind/cg_main.c
@@ -1563,7 +1563,6 @@
 
 static void cg_fini(Int exitcode)
 {
-   static HChar buf1[128], buf2[128], buf3[128], buf4[123];  // FIXME
    static HChar fmt[128];   // OK; large enough
 
    CacheCC  D_total;
@@ -1599,11 +1598,10 @@
       VG_(umsg)(fmt, "LLi misses:   ", Ir_total.mL);
 
       if (0 == Ir_total.a) Ir_total.a = 1;
-      VG_(percentify)(Ir_total.m1, Ir_total.a, 2, l1+1, buf1);
-      VG_(umsg)("I1  miss rate: %s\n", buf1);
-
-      VG_(percentify)(Ir_total.mL, Ir_total.a, 2, l1+1, buf1);
-      VG_(umsg)("LLi miss rate: %s\n", buf1);
+      VG_(umsg)("I1  miss rate: %*.2f%%\n", l1,
+                Ir_total.m1 * 100.0 / Ir_total.a);
+      VG_(umsg)("LLi miss rate: %*.2f%%\n", l1,
+                Ir_total.mL * 100.0 / Ir_total.a);
       VG_(umsg)("\n");
 
       /* D cache results.  Use the D_refs.rd and D_refs.wr values to
@@ -1626,15 +1624,14 @@
       if (0 == D_total.a)  D_total.a = 1;
       if (0 == Dr_total.a) Dr_total.a = 1;
       if (0 == Dw_total.a) Dw_total.a = 1;
-      VG_(percentify)( D_total.m1,  D_total.a, 1, l1+1, buf1);
-      VG_(percentify)(Dr_total.m1, Dr_total.a, 1, l2+1, buf2);
-      VG_(percentify)(Dw_total.m1, Dw_total.a, 1, l3+1, buf3);
-      VG_(umsg)("D1  miss rate: %s (%s     + %s  )\n", buf1, buf2,buf3);
-
-      VG_(percentify)( D_total.mL,  D_total.a, 1, l1+1, buf1);
-      VG_(percentify)(Dr_total.mL, Dr_total.a, 1, l2+1, buf2);
-      VG_(percentify)(Dw_total.mL, Dw_total.a, 1, l3+1, buf3);
-      VG_(umsg)("LLd miss rate: %s (%s     + %s  )\n", buf1, buf2,buf3);
+      VG_(umsg)("D1  miss rate: %*.1f%% (%*.1f%%     + %*.1f%%  )\n",
+                l1, D_total.m1  * 100.0 / D_total.a,
+                l2, Dr_total.m1 * 100.0 / Dr_total.a,
+                l3, Dw_total.m1 * 100.0 / Dw_total.a);
+      VG_(umsg)("LLd miss rate: %*.1f%% (%*.1f%%     + %*.1f%%  )\n",
+                l1, D_total.mL  * 100.0 / D_total.a,
+                l2, Dr_total.mL * 100.0 / Dr_total.a,
+                l3, Dw_total.mL * 100.0 / Dw_total.a);
       VG_(umsg)("\n");
 
       /* LL overall results */
@@ -1651,10 +1648,10 @@
       VG_(umsg)(fmt, "LL misses:    ",
                      LL_total_m, LL_total_mr, LL_total_mw);
 
-      VG_(percentify)(LL_total_m,  (Ir_total.a + D_total.a),  1, l1+1, buf1);
-      VG_(percentify)(LL_total_mr, (Ir_total.a + Dr_total.a), 1, l2+1, buf2);
-      VG_(percentify)(LL_total_mw, Dw_total.a,                1, l3+1, buf3);
-      VG_(umsg)("LL miss rate:  %s (%s     + %s  )\n", buf1, buf2,buf3);
+      VG_(umsg)("LL miss rate:  %*.1f%% (%*.1f%%     + %*.1f%%  )\n",
+                l1, LL_total_m  * 100.0 / (Ir_total.a + D_total.a),
+                l2, LL_total_mr * 100.0 / (Ir_total.a + Dr_total.a),
+                l3, LL_total_mw * 100.0 / Dw_total.a);
    }
 
    /* If branch profiling is enabled, show branch overall results. */
@@ -1675,11 +1672,10 @@
       VG_(umsg)(fmt, "Mispredicts:  ",
                      B_total.mp, Bc_total.mp, Bi_total.mp);
 
-      VG_(percentify)(B_total.mp,  B_total.b,  1, l1+1, buf1);
-      VG_(percentify)(Bc_total.mp, Bc_total.b, 1, l2+1, buf2);
-      VG_(percentify)(Bi_total.mp, Bi_total.b, 1, l3+1, buf3);
-
-      VG_(umsg)("Mispred rate:  %s (%s     + %s   )\n", buf1, buf2,buf3);
+      VG_(umsg)("Mispred rate:  %*.1f%% (%*.1f%%     + %*.1f%%   )\n",
+                l1, B_total.mp  * 100.0 / B_total.b,
+                l2, Bc_total.mp * 100.0 / Bc_total.b,
+                l3, Bi_total.mp * 100.0 / Bi_total.b);
    }
 
    // Various stats
@@ -1695,18 +1691,14 @@
       VG_(dmsg)("cachegrind: distinct instrs Gen: %d\n", distinct_instrsGen);
       VG_(dmsg)("cachegrind: debug lookups      : %d\n", debug_lookups);
       
-      VG_(percentify)(full_debugs,      debug_lookups, 1, 6, buf1);
-      VG_(percentify)(file_line_debugs, debug_lookups, 1, 6, buf2);
-      VG_(percentify)(fn_debugs,        debug_lookups, 1, 6, buf3);
-      VG_(percentify)(no_debugs,        debug_lookups, 1, 6, buf4);
-      VG_(dmsg)("cachegrind: with full      info:%s (%d)\n", 
-                buf1, full_debugs);
-      VG_(dmsg)("cachegrind: with file/line info:%s (%d)\n", 
-                buf2, file_line_debugs);
-      VG_(dmsg)("cachegrind: with fn name   info:%s (%d)\n", 
-                buf3, fn_debugs);
-      VG_(dmsg)("cachegrind: with zero      info:%s (%d)\n", 
-                buf4, no_debugs);
+      VG_(dmsg)("cachegrind: with full      info:%6.1f%% (%d)\n", 
+                full_debugs * 100.0 / debug_lookups, full_debugs);
+      VG_(dmsg)("cachegrind: with file/line info:%6.1f%% (%d)\n", 
+                file_line_debugs * 100.0 / debug_lookups, file_line_debugs);
+      VG_(dmsg)("cachegrind: with fn name   info:%6.1f%% (%d)\n", 
+                fn_debugs * 100.0 / debug_lookups, fn_debugs);
+      VG_(dmsg)("cachegrind: with zero      info:%6.1f%% (%d)\n", 
+                no_debugs * 100.0 / debug_lookups, no_debugs);
 
       VG_(dmsg)("cachegrind: string table size: %lu\n",
                 VG_(OSetGen_Size)(stringTable));
diff --git a/callgrind/main.c b/callgrind/main.c
index 7402131..0180ec6 100644
--- a/callgrind/main.c
+++ b/callgrind/main.c
@@ -1775,7 +1775,6 @@
 static
 void branchsim_printstat(int l1, int l2, int l3)
 {
-    static HChar buf1[128], buf2[128], buf3[128];
     static HChar fmt[128];    // large enough
     FullCost total;
     ULong Bc_total_b, Bc_total_mp, Bi_total_b, Bi_total_mp;
@@ -1803,11 +1802,10 @@
     VG_(umsg)(fmt, "Mispredicts:  ",
               B_total_mp, Bc_total_mp, Bi_total_mp);
 
-    VG_(percentify)(B_total_mp,  B_total_b,  1, l1+1, buf1);
-    VG_(percentify)(Bc_total_mp, Bc_total_b, 1, l2+1, buf2);
-    VG_(percentify)(Bi_total_mp, Bi_total_b, 1, l3+1, buf3);
-
-    VG_(umsg)("Mispred rate:  %s (%s     + %s   )\n", buf1, buf2,buf3);
+    VG_(umsg)("Mispred rate:  %*.1f%% (%*.1f%%     + %*.1f%%   )\n",
+              l1, B_total_mp  * 100.0 / B_total_b,
+              l2, Bc_total_mp * 100.0 / Bc_total_b,
+              l3, Bi_total_mp * 100.0 / Bi_total_b);
 }
 
 static
diff --git a/coregrind/m_debuglog.c b/coregrind/m_debuglog.c
index 5ae2a5d..47a2750 100644
--- a/coregrind/m_debuglog.c
+++ b/coregrind/m_debuglog.c
@@ -1,3 +1,4 @@
+/* -*- mode: C; c-basic-offset: 3; -*- */
 
 /*--------------------------------------------------------------------*/
 /*--- Debug (not-for-user) logging; also vprintf.     m_debuglog.c ---*/
@@ -730,7 +731,7 @@
    UInt ret = 0;
    Int  i;
    Int  flags;
-   Int  width;
+   Int  width, precision;
    Int  n_ls = 0;
    Bool is_long, caps;
 
@@ -758,6 +759,7 @@
       flags = 0;
       n_ls  = 0;
       width = 0; /* length of the field. */
+      precision = -1;  /* unspecified precision */
       while (1) {
          switch (format[i]) {
          case '(':
@@ -787,9 +789,29 @@
       }
      parse_fieldwidth:
       /* Compute the field length. */
-      while (format[i] >= '0' && format[i] <= '9') {
-         width *= 10;
-         width += format[i++] - '0';
+      if (format[i] == '*') {
+         width = va_arg(vargs, Int);
+         ++i;
+      } else {
+         while (format[i] >= '0' && format[i] <= '9') {
+            width *= 10;
+            width += format[i++] - '0';
+         }
+      }
+      /* Parse precision, if any. Only meaningful for %f. For all other
+         format specifiers the precision will be silently ignored. */
+      if (format[i] == '.') {
+         ++i;
+         if (format[i] == '*') {
+            precision = va_arg(vargs, Int);
+            ++i;
+         } else {
+            precision = 0;
+            while (format[i] >= '0' && format[i] <= '9') {
+               precision *= 10;
+               precision += format[i++] - '0';
+            }
+         }
       }
       while (format[i] == 'l') {
          i++;
@@ -888,6 +910,96 @@
                                  flags, width, str, format[i]=='S');
             break;
          }
+         case 'f': {
+            /* Print a floating point number in the format x.y without
+               any exponent. Capabilities are extremely limited, basically
+               a joke, but good enough for our needs. */
+            Double val = va_arg (vargs, Double);
+            Bool is_negative = False;
+            Int cnt;
+
+            if (val < 0.0) {
+               is_negative = True;
+               val = - val;
+            }
+            /* If the integral part of the floating point number cannot be
+               represented by an ULONG_MAX, print '*' characters */
+            if (val > (Double)(~0ULL)) {
+               if (width == 0) width = 6;    // say
+               for (cnt = 0; cnt < width; ++cnt)
+                  send('*', send_arg2);
+               ret += width;
+               break;
+            }
+            /* The integral part of the floating point number is representable
+               by an ULong. */
+            ULong ipval = val;
+            Double frac = val - ipval;
+
+            if (precision == -1) precision = 6;   // say
+
+            /* Silently limit the precision to 10 digits. */
+            if (precision > 10) precision = 10;
+
+            /* If fracional part is not printed (precision == 0), may have to
+               round up */
+            if (precision == 0 && frac >= 0.5)
+               ipval += 1;
+
+            /* Find out how many characters are needed to print the number */
+
+            /* The integral part... */
+            UInt ipwidth, num_digit = 1;   // at least one digit
+            ULong x, old_x = 0;
+            for (x = 10; ; old_x = x, x *= 10, ++num_digit) {
+               if (x <= old_x) break;    // overflow occurred
+               if (ipval < x) break;
+            }
+            ipwidth = num_digit;   // width of integral part.
+            if (is_negative) ++num_digit;
+            if (precision != 0)
+               num_digit += 1 + precision;
+
+            // Print the number
+
+            // Fill in blanks on the left
+            if (num_digit < width && (flags & VG_MSG_LJUSTIFY) == 0) {
+               for (cnt = 0; cnt < width - num_digit; ++cnt)
+                  send(' ', send_arg2);
+               ret += width - num_digit;
+            }
+            // Sign, maybe
+            if (is_negative) {
+               send('-', send_arg2);
+               ret += 1;
+            }
+            // Integral part
+            ret += myvprintf_int64(send, send_arg2, 0, 10, ipwidth, False,
+                                   ipval);
+            // Decimal point and fractional part
+            if (precision != 0) {
+               send('.', send_arg2);
+               ret += 1;
+
+               // Fractional part
+               ULong factor = 1;
+               for (cnt = 0; cnt < precision; ++cnt)
+                  factor *= 10;
+               ULong frval = frac * factor;
+               if ((frac * factor - frval) > 0.5)    // round up
+                  frval += 1;
+               frval %= factor;
+               ret += myvprintf_int64(send, send_arg2, VG_MSG_ZJUSTIFY, 10,
+                                      precision, False, frval);
+            }
+            // Fill in blanks on the right
+            if (num_digit < width && (flags & VG_MSG_LJUSTIFY) != 0) {
+               for (cnt = 0; cnt < width - num_digit; ++cnt)
+                  send(' ', send_arg2);
+               ret += width - num_digit;
+            }
+            break;
+         }
 
 //         case 'y': { /* %y - print symbol */
 //            Addr a = va_arg(vargs, Addr);
diff --git a/coregrind/m_libcprint.c b/coregrind/m_libcprint.c
index 3617c03..fbd3d4c 100644
--- a/coregrind/m_libcprint.c
+++ b/coregrind/m_libcprint.c
@@ -362,58 +362,6 @@
    VG_(free)(fp);
 }
 
-/* ---------------------------------------------------------------------
-   percentify()
-   ------------------------------------------------------------------ */
-
-// Percentify n/m with d decimal places.  Includes the '%' symbol at the end.
-// Right justifies in 'buf'.
-void VG_(percentify)(ULong n, ULong m, UInt d, Int n_buf, HChar buf[]) 
-{
-   Int i, len, space;
-   ULong p1;
-   HChar fmt[32];   // large enough
-
-   if (m == 0) {
-      // Have to generate the format string in order to be flexible about
-      // the width of the field.
-      VG_(sprintf)(fmt, "%%%ds", n_buf);
-      // fmt is now "%<n_buf>s" where <d> is 1,2,3...
-      VG_(sprintf)(buf, fmt, "--%");
-      return;
-   }
-   
-   p1 = (100*n) / m;
-    
-   if (d == 0) {
-      VG_(sprintf)(buf, "%llu%%", p1);  // FIXME: unsafe
-   } else {
-      ULong p2;
-      UInt  ex;
-      switch (d) {
-      case 1: ex = 10;    break;
-      case 2: ex = 100;   break;
-      case 3: ex = 1000;  break;
-      default: VG_(core_panic)("Currently can only handle 3 decimal places");
-      }
-      p2 = ((100*n*ex) / m) % ex;
-      // Have to generate the format string in order to be flexible about
-      // the width of the post-decimal-point part.
-      VG_(sprintf)(fmt, "%%llu.%%0%ullu%%%%", d);
-      // fmt is now "%llu.%0<d>llu%%" where <d> is 1,2,3...
-      VG_(sprintf)(buf, fmt, p1, p2);   // FIXME: unsafe
-   }
-
-   len = VG_(strlen)(buf);
-   space = n_buf - len;
-   if (space < 0) space = 0;     /* Allow for v. small field_width */
-   i = len;
-
-   /* Right justify in field */
-   for (     ; i >= 0;    i--)  buf[i + space] = buf[i];
-   for (i = 0; i < space; i++)  buf[i] = ' ';
-}
-
 
 /* ---------------------------------------------------------------------
    elapsed_wallclock_time()
diff --git a/coregrind/m_sbprofile.c b/coregrind/m_sbprofile.c
index 0be07dd..7ed9950 100644
--- a/coregrind/m_sbprofile.c
+++ b/coregrind/m_sbprofile.c
@@ -50,7 +50,6 @@
                        ULong score_total, ULong ecs_done )
 {
    ULong score_cumul, score_cumul_saved, score_here;
-   HChar buf_cumul[10], buf_here[10];
    Int   r; /* must be signed */
 
    HChar ecs_txt[50];
@@ -89,12 +88,18 @@
 
       score_here = tops[r].score;
       score_cumul += score_here;
-      VG_(percentify)(score_cumul, score_total, 2, 6, buf_cumul);
-      VG_(percentify)(score_here,  score_total, 2, 6, buf_here);
-      VG_(printf)("%3d: (%9lld %s)   %9lld %s      0x%llx %s\n",
+
+      /* Careful: do not divide by zero. score_total == 0 implies
+         score_cumul == 0 and also score_here == 0. */
+      Double percent_cumul =
+         score_total == 0 ? 100.0 : score_cumul * 100.0 / score_total;
+      Double percent_here =
+         score_total == 0 ? 100.0 : score_here * 100.0 / score_total;
+        
+      VG_(printf)("%3d: (%9lld %5.2f%%)   %9lld %5.2f%%      0x%llx %s\n",
                   r,
-                  score_cumul, buf_cumul,
-                  score_here,  buf_here, tops[r].addr, name );
+                  score_cumul, percent_cumul,
+                  score_here,  percent_here, tops[r].addr, name);
    }
    score_cumul_saved = score_cumul;
 
@@ -122,15 +127,21 @@
 
          score_here = tops[r].score;
          score_cumul += score_here;
-         VG_(percentify)(score_cumul, score_total, 2, 6, buf_cumul);
-         VG_(percentify)(score_here,  score_total, 2, 6, buf_here);
+
+         /* Careful: do not divide by zero. score_total == 0 implies
+            score_cumul == 0 and also score_here == 0. */
+         Double percent_cumul =
+           score_total == 0 ? 100.0 : score_cumul * 100.0 / score_total;
+         Double percent_here =
+           score_total == 0 ? 100.0 : score_here * 100.0 / score_total;
+
          VG_(printf)("\n");
          VG_(printf)("=-=-=-=-=-=-=-=-=-=-=-=-=-= begin SB rank %d "
                      "=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n", r);
-         VG_(printf)("%3d: (%9lld %s)   %9lld %s      0x%llx %s\n",
+         VG_(printf)("%3d: (%9lld %5.2f%%)   %9lld %5.2f%%      0x%llx %s\n",
                      r,
-                     score_cumul, buf_cumul,
-                     score_here,  buf_here, tops[r].addr, name );
+                     score_cumul, percent_cumul,
+                     score_here,  percent_here, tops[r].addr, name );
          VG_(printf)("\n");
          VG_(discard_translations)(tops[r].addr, 1, "bb profile");
          VG_(translate)(0, tops[r].addr, True, VG_(clo_profyle_flags), 0, True);
@@ -151,12 +162,18 @@
          VG_(get_fnname_w_offset)(tops[r].addr, &name);
 
          score_here = tops[r].score;
-         VG_(percentify)(score_cumul, score_total, 2, 6, buf_cumul);
-         VG_(percentify)(score_here,  score_total, 2, 6, buf_here);
-         VG_(printf)("%3d: (%9lld %s)   %9lld %s      0x%llx %s\n",
+
+         /* Careful: do not divide by zero. score_total == 0 implies
+            score_cumul == 0 and also score_here == 0. */
+         Double percent_cumul =
+           score_total == 0 ? 100.0 : score_cumul * 100.0 / score_total;
+         Double percent_here =
+           score_total == 0 ? 100.0 : score_here * 100.0 / score_total;
+
+         VG_(printf)("%3d: (%9lld %5.2f%%)   %9lld %5.2f%%      0x%llx %s\n",
                      r,
-                     score_cumul, buf_cumul,
-                     score_here,  buf_here, tops[r].addr, name );
+                     score_cumul, percent_cumul,
+                     score_here,  percent_here, tops[r].addr, name );
          score_cumul -= score_here;
       }
       VG_(printf)("rank  ---cumulative---      -----self-----\n");
diff --git a/coregrind/m_translate.c b/coregrind/m_translate.c
index b5d10fb..816b5f2 100644
--- a/coregrind/m_translate.c
+++ b/coregrind/m_translate.c
@@ -73,23 +73,25 @@
 
 void VG_(print_translation_stats) ( void )
 {
-   HChar buf[7];
    UInt n_SP_updates = n_SP_updates_fast + n_SP_updates_generic_known
                                          + n_SP_updates_generic_unknown;
-   VG_(percentify)(n_SP_updates_fast, n_SP_updates, 1, 6, buf);
+   if (n_SP_updates == 0) {
+      VG_(message)(Vg_DebugMsg, "translate: no SP updates identified\n");
+      return;
+   }
    VG_(message)(Vg_DebugMsg,
-      "translate:            fast SP updates identified: %'u (%s)\n",
-      n_SP_updates_fast, buf );
+      "translate:            fast SP updates identified: %'u (%3.1f%%)\n",
+      n_SP_updates_fast, n_SP_updates_fast * 100.0 / n_SP_updates );
 
-   VG_(percentify)(n_SP_updates_generic_known, n_SP_updates, 1, 6, buf);
    VG_(message)(Vg_DebugMsg,
-      "translate:   generic_known SP updates identified: %'u (%s)\n",
-      n_SP_updates_generic_known, buf );
+      "translate:   generic_known SP updates identified: %'u (%3.1f%%)\n",
+      n_SP_updates_generic_known,
+      n_SP_updates_generic_known * 100.0 / n_SP_updates );
 
-   VG_(percentify)(n_SP_updates_generic_unknown, n_SP_updates, 1, 6, buf);
    VG_(message)(Vg_DebugMsg,
-      "translate: generic_unknown SP updates identified: %'u (%s)\n",
-      n_SP_updates_generic_unknown, buf );
+      "translate: generic_unknown SP updates identified: %'u (%3.1f%%)\n",
+      n_SP_updates_generic_unknown,
+      n_SP_updates_generic_unknown * 100.0 / n_SP_updates );
 }
 
 /*------------------------------------------------------------*/
diff --git a/include/pub_tool_libcprint.h b/include/pub_tool_libcprint.h
index f71469a..adfedb6 100644
--- a/include/pub_tool_libcprint.h
+++ b/include/pub_tool_libcprint.h
@@ -63,11 +63,6 @@
                                        const HChar *format, va_list vargs )
                           PRINTF_CHECK(3, 0);
 
-// Percentify n/m with d decimal places.  Includes the '%' symbol at the end.
-// Right justifies in 'buf'.
-extern void VG_(percentify)(ULong n, ULong m, UInt d, Int n_buf, HChar buf[]);
-
-
 /* ---------------------------------------------------------------------
    Output-printing functions
    ------------------------------------------------------------------ */
diff --git a/lackey/lk_main.c b/lackey/lk_main.c
index 485707c..b2ca1ff 100644
--- a/lackey/lk_main.c
+++ b/lackey/lk_main.c
@@ -997,10 +997,6 @@
 
 static void lk_fini(Int exitcode)
 {
-   HChar percentify_buf[5]; /* Two digits, '%' and 0. */
-   const int percentify_size = sizeof(percentify_buf) - 1;
-   const int percentify_decs = 0;
-   
    tl_assert(clo_fnname);
    tl_assert(clo_fnname[0]);
 
@@ -1014,10 +1010,8 @@
       VG_(umsg)("\n");
       VG_(umsg)("Jccs:\n");
       VG_(umsg)("  total:         %'llu\n", total_Jccs);
-      VG_(percentify)(taken_Jccs, (total_Jccs ? total_Jccs : 1),
-         percentify_decs, percentify_size, percentify_buf);
-      VG_(umsg)("  taken:         %'llu (%s)\n",
-         taken_Jccs, percentify_buf);
+      VG_(umsg)("  taken:         %'llu (%.0f%%)\n",
+                taken_Jccs, taken_Jccs * 100.0 / total_Jccs ?: 1);
       
       VG_(umsg)("\n");
       VG_(umsg)("Executed:\n");
diff --git a/lackey/tests/true.stderr.exp b/lackey/tests/true.stderr.exp
index 9a14875..0f1c364 100644
--- a/lackey/tests/true.stderr.exp
+++ b/lackey/tests/true.stderr.exp
@@ -4,7 +4,7 @@
 
 Jccs:
   total:         ...
-  taken:         ... ( ...%)
+  taken:         ... (...%)
 
 Executed:
   SBs entered:   ...
diff --git a/massif/ms_main.c b/massif/ms_main.c
index 450379d..01daaf0 100644
--- a/massif/ms_main.c
+++ b/massif/ms_main.c
@@ -2105,18 +2105,6 @@
 
 #define FP(format, args...) ({ VG_(fprintf)(fp, format, ##args); })
 
-// Nb: uses a static buffer, each call trashes the last string returned.
-static const HChar* make_perc(double x)
-{
-   static HChar mbuf[32];
-
-   VG_(percentify)((ULong)(x * 100), 10000, 2, 6, mbuf);
-   // XXX: this is bogus if the denominator was zero -- resulting string is
-   // something like "0 --%")
-   if (' ' == mbuf[0]) mbuf[0] = '0';
-   return mbuf;
-}
-
 static void pp_snapshot_SXPt(VgFile *fp, SXPt* sxpt, Int depth,
                              HChar* depth_str, Int depth_str_len,
                              SizeT snapshot_heap_szB, SizeT snapshot_total_szB)
@@ -2225,9 +2213,8 @@
 
     case InsigSXPt: {
       const HChar* s = ( 1 == sxpt->Insig.n_xpts ? "," : "s, all" );
-      FP("%sn0: %lu in %d place%s below massif's threshold (%s)\n",
-         depth_str, sxpt->szB, sxpt->Insig.n_xpts, s,
-         make_perc(clo_threshold));
+      FP("%sn0: %lu in %d place%s below massif's threshold (%.2f%%)\n",
+         depth_str, sxpt->szB, sxpt->Insig.n_xpts, s, clo_threshold);
       break;
     }
 
diff --git a/massif/tests/insig.post.exp b/massif/tests/insig.post.exp
index 5963dec..5a4abd5 100644
--- a/massif/tests/insig.post.exp
+++ b/massif/tests/insig.post.exp
@@ -70,7 +70,7 @@
 87.28% (16,688B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
 ->83.68% (16,000B) 0x........: main (insig.c:8)
 | 
-->01.34% (256B) in 16 places, all below massif's threshold (00.99%)
+->01.34% (256B) in 16 places, all below massif's threshold (0.99%)
 | 
 ->01.26% (240B) 0x........: main (insig.c:9)
 | 
diff --git a/massif/tests/thresholds_5_0.post.exp b/massif/tests/thresholds_5_0.post.exp
index b54bd88..96ee9df 100644
--- a/massif/tests/thresholds_5_0.post.exp
+++ b/massif/tests/thresholds_5_0.post.exp
@@ -52,7 +52,7 @@
 | |   
 | ->05.00% (4,000B) 0x........: main (thresholds.c:54)
 | | 
-| ->01.50% (1,200B) in 2 places, all below massif's threshold (05.00%)
+| ->01.50% (1,200B) in 2 places, all below massif's threshold (5.00%)
 |   
 ->20.00% (16,000B) 0x........: main (thresholds.c:55)
 | 
@@ -60,7 +60,7 @@
 | ->09.00% (7,200B) 0x........: a7550 (thresholds.c:39)
 | | ->09.00% (7,200B) 0x........: main (thresholds.c:52)
 | |   
-| ->04.00% (3,200B) in 2 places, all below massif's threshold (05.00%)
+| ->04.00% (3,200B) in 2 places, all below massif's threshold (5.00%)
 |   
-->00.50% (400B) in 1 place, below massif's threshold (05.00%)
+->00.50% (400B) in 1 place, below massif's threshold (5.00%)
   
diff --git a/massif/tests/zero1.post.exp b/massif/tests/zero1.post.exp
index db640cb..cddd70e 100644
--- a/massif/tests/zero1.post.exp
+++ b/massif/tests/zero1.post.exp
@@ -46,7 +46,7 @@
   8              0                0                0             0            0
   9              0                0                0             0            0
 00.00% (0B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
-->00.00% (0B) in 5 places, all below massif's threshold (01.00%)
+->00.00% (0B) in 5 places, all below massif's threshold (1.00%)
   
 --------------------------------------------------------------------------------
   n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
@@ -62,7 +62,7 @@
  18              0                0                0             0            0
  19              0                0                0             0            0
 00.00% (0B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
-->00.00% (0B) in 10 places, all below massif's threshold (01.00%)
+->00.00% (0B) in 10 places, all below massif's threshold (1.00%)
   
 --------------------------------------------------------------------------------
   n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
diff --git a/memcheck/mc_main.c b/memcheck/mc_main.c
index 86a9574..5cd2414 100644
--- a/memcheck/mc_main.c
+++ b/memcheck/mc_main.c
@@ -965,11 +965,9 @@
    VG_(OSetGen_Destroy)(secVBitTable);
    secVBitTable = secVBitTable2;
 
-   if (VG_(clo_verbosity) > 1) {
-      HChar percbuf[7];
-      VG_(percentify)(n_survivors, n_nodes, 1, 6, percbuf);
-      VG_(message)(Vg_DebugMsg, "memcheck GC: %d nodes, %d survivors (%s)\n",
-                   n_nodes, n_survivors, percbuf);
+   if (VG_(clo_verbosity) > 1 && n_nodes != 0) {
+      VG_(message)(Vg_DebugMsg, "memcheck GC: %d nodes, %d survivors (%.1f%%)\n",
+                   n_nodes, n_survivors, n_survivors * 100.0 / n_nodes);
    }
 
    // Increase table size if necessary.
diff --git a/none/tests/Makefile.am b/none/tests/Makefile.am
index daa8c8b..b9a1767 100644
--- a/none/tests/Makefile.am
+++ b/none/tests/Makefile.am
@@ -175,6 +175,7 @@
 	threadederrno.vgtest \
 	timestamp.stderr.exp timestamp.vgtest \
 	tls.vgtest tls.stderr.exp tls.stdout.exp  \
+	unit_debuglog.stderr.exp unit_debuglog.vgtest \
 	vgprintf.stderr.exp vgprintf.vgtest \
 	process_vm_readv_writev.stderr.exp process_vm_readv_writev.vgtest
 
@@ -211,6 +212,7 @@
 	tls \
 	tls.so \
 	tls2.so \
+	unit_debuglog \
 	valgrind_cpp_test \
 	vgprintf \
 	coolo_sigaction \
diff --git a/none/tests/unit_debuglog.c b/none/tests/unit_debuglog.c
new file mode 100644
index 0000000..1fecb30
--- /dev/null
+++ b/none/tests/unit_debuglog.c
@@ -0,0 +1,148 @@
+/* Test %f format specifier */
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pub_core_basics.h"
+#include "pub_core_libcbase.h"
+#include "pub_core_libcassert.h"
+#include "pub_core_libcprint.h"
+
+#undef vg_assert
+#define vg_assert(e)                   assert(e)
+#undef vg_assert2
+#define vg_assert2(e, fmt, args...)    assert(e)
+
+#include "coregrind/m_debuglog.c"
+
+void run(const char *format, ...)
+{
+  int n, num_stars, i1, i2;
+  const char *p;
+  printf_buf buf;
+  va_list vargs;
+
+  // Count number of '*' in format
+  num_stars = 0;
+  for (p = format; *p; ++p)
+    if (*p == '*') ++num_stars;
+
+  va_start(vargs, format);
+  fprintf(stderr, "%s\tprintf =   ", format);
+  switch (num_stars) {
+  case 0:
+    n = fprintf(stderr, format, va_arg(vargs, Double));
+    break;
+  case 1:
+    i1 = va_arg(vargs, int);
+    n = fprintf(stderr, format, i1, va_arg(vargs, Double));
+    break;
+  case 2:
+    i1 = va_arg(vargs, int);
+    i2 = va_arg(vargs, int);
+    n = fprintf(stderr, format, i1, i2, va_arg(vargs, Double));
+    break;
+  }
+  fprintf(stderr, "\twrote %3d chars\n", n);
+  va_end(vargs);
+
+  buf.n = 0;
+  buf.buf[0] = 0;
+
+  fprintf(stderr, "%s\tdebuglog = ", format);
+  va_start(vargs, format);
+  n = VG_(debugLog_vprintf)(add_to_buf, &buf, format, vargs);
+  va_end(vargs);
+
+  emit(buf.buf, strlen(buf.buf));
+  fprintf(stderr, "\twrote %3d chars\n", n);
+}
+
+
+int main(int argc, char *argv[])
+{
+  double value;
+
+  fprintf(stderr, "...testing value 0\n");
+  value = 0.0;
+  run("|%f|", value);
+  run("|%2f|", value);
+  run("|%9f|", value);
+  run("|%8.0f|", value);
+  run("|%8.1f|", value);
+  run("|%8.2f|", value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing value 3.7  (with rounding)\n");
+  value = 3.7;
+  run("|%f|", value);
+  run("|%4f|", value);
+  run("|%9f|", value);
+  run("|%4.0f|", value);
+  run("|%4.1f|", value);
+  run("|%4.2f|", value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing value 123.01\n");
+  value = 123.01;
+  run("|%f|", value);
+  run("|%4f|", value);
+  run("|%9f|", value);
+  run("|%8.0f|", value);
+  run("|%8.1f|", value);
+  run("|%8.2f|", value);
+  run("|%8.3f|", value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing value 3.0019  (with rounding)\n");
+  value = 3.0019;
+  run("|%f|", value);
+  run("|%10f|", value);
+  run("|%10.0f|", value);
+  run("|%10.3f|", value);
+  run("|%10.4f|", value);
+  run("|%.4f|", value);
+  run("|%.9f|", value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing value -123.456 (with rounding)\n");
+  value = -123.456;
+  run("|%f|", value);
+  run("|%10f|", value);
+  run("|%10.0f|", value);
+  run("|%10.1f|", value);
+  run("|%10.2f|", value);
+  run("|%10.3f|", value);
+  run("|%10.4f|", value);
+  run("|%10.5f|", value);
+  run("|%.4f|", value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing value = -123.456 width = '*'\n");
+  value = -123.456;
+  run("|%*f|", 10, value);
+  run("|%*f|", 2, value);
+  run("|%*f.1|", 10, value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing precision = '*'\n");
+  value = -123.456;
+  run("|%.*f|", 10, value);
+  run("|%.*f|", 2, value);
+  run("|%10.*f|", 2, value);
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing width/precision = '*'\n");
+  value = -123.456;
+  run("|%*.*f|", 20, 5, value);
+  run("|%*.*f|", 1, 4, value);
+
+
+  fprintf(stderr, "\n");
+  fprintf(stderr, "...testing left justification\n");
+  value = 3.1415;
+  run("|%10f|", value);
+  run("|%-10f|", value);
+  return 0;
+}
diff --git a/none/tests/unit_debuglog.stderr.exp b/none/tests/unit_debuglog.stderr.exp
new file mode 100644
index 0000000..7e8e87a
--- /dev/null
+++ b/none/tests/unit_debuglog.stderr.exp
@@ -0,0 +1,107 @@
+...testing value 0
+|%f|	printf =   |0.000000|	wrote  10 chars
+|%f|	debuglog = |0.000000|	wrote  10 chars
+|%2f|	printf =   |0.000000|	wrote  10 chars
+|%2f|	debuglog = |0.000000|	wrote  10 chars
+|%9f|	printf =   | 0.000000|	wrote  11 chars
+|%9f|	debuglog = | 0.000000|	wrote  11 chars
+|%8.0f|	printf =   |       0|	wrote  10 chars
+|%8.0f|	debuglog = |       0|	wrote  10 chars
+|%8.1f|	printf =   |     0.0|	wrote  10 chars
+|%8.1f|	debuglog = |     0.0|	wrote  10 chars
+|%8.2f|	printf =   |    0.00|	wrote  10 chars
+|%8.2f|	debuglog = |    0.00|	wrote  10 chars
+
+...testing value 3.7  (with rounding)
+|%f|	printf =   |3.700000|	wrote  10 chars
+|%f|	debuglog = |3.700000|	wrote  10 chars
+|%4f|	printf =   |3.700000|	wrote  10 chars
+|%4f|	debuglog = |3.700000|	wrote  10 chars
+|%9f|	printf =   | 3.700000|	wrote  11 chars
+|%9f|	debuglog = | 3.700000|	wrote  11 chars
+|%4.0f|	printf =   |   4|	wrote   6 chars
+|%4.0f|	debuglog = |   4|	wrote   6 chars
+|%4.1f|	printf =   | 3.7|	wrote   6 chars
+|%4.1f|	debuglog = | 3.7|	wrote   6 chars
+|%4.2f|	printf =   |3.70|	wrote   6 chars
+|%4.2f|	debuglog = |3.70|	wrote   6 chars
+
+...testing value 123.01
+|%f|	printf =   |123.010000|	wrote  12 chars
+|%f|	debuglog = |123.010000|	wrote  12 chars
+|%4f|	printf =   |123.010000|	wrote  12 chars
+|%4f|	debuglog = |123.010000|	wrote  12 chars
+|%9f|	printf =   |123.010000|	wrote  12 chars
+|%9f|	debuglog = |123.010000|	wrote  12 chars
+|%8.0f|	printf =   |     123|	wrote  10 chars
+|%8.0f|	debuglog = |     123|	wrote  10 chars
+|%8.1f|	printf =   |   123.0|	wrote  10 chars
+|%8.1f|	debuglog = |   123.0|	wrote  10 chars
+|%8.2f|	printf =   |  123.01|	wrote  10 chars
+|%8.2f|	debuglog = |  123.01|	wrote  10 chars
+|%8.3f|	printf =   | 123.010|	wrote  10 chars
+|%8.3f|	debuglog = | 123.010|	wrote  10 chars
+
+...testing value 3.0019  (with rounding)
+|%f|	printf =   |3.001900|	wrote  10 chars
+|%f|	debuglog = |3.001900|	wrote  10 chars
+|%10f|	printf =   |  3.001900|	wrote  12 chars
+|%10f|	debuglog = |  3.001900|	wrote  12 chars
+|%10.0f|	printf =   |         3|	wrote  12 chars
+|%10.0f|	debuglog = |         3|	wrote  12 chars
+|%10.3f|	printf =   |     3.002|	wrote  12 chars
+|%10.3f|	debuglog = |     3.002|	wrote  12 chars
+|%10.4f|	printf =   |    3.0019|	wrote  12 chars
+|%10.4f|	debuglog = |    3.0019|	wrote  12 chars
+|%.4f|	printf =   |3.0019|	wrote   8 chars
+|%.4f|	debuglog = |3.0019|	wrote   8 chars
+|%.9f|	printf =   |3.001900000|	wrote  13 chars
+|%.9f|	debuglog = |3.001900000|	wrote  13 chars
+
+...testing value -123.456 (with rounding)
+|%f|	printf =   |-123.456000|	wrote  13 chars
+|%f|	debuglog = |-123.456000|	wrote  13 chars
+|%10f|	printf =   |-123.456000|	wrote  13 chars
+|%10f|	debuglog = |-123.456000|	wrote  13 chars
+|%10.0f|	printf =   |      -123|	wrote  12 chars
+|%10.0f|	debuglog = |      -123|	wrote  12 chars
+|%10.1f|	printf =   |    -123.5|	wrote  12 chars
+|%10.1f|	debuglog = |    -123.5|	wrote  12 chars
+|%10.2f|	printf =   |   -123.46|	wrote  12 chars
+|%10.2f|	debuglog = |   -123.46|	wrote  12 chars
+|%10.3f|	printf =   |  -123.456|	wrote  12 chars
+|%10.3f|	debuglog = |  -123.456|	wrote  12 chars
+|%10.4f|	printf =   | -123.4560|	wrote  12 chars
+|%10.4f|	debuglog = | -123.4560|	wrote  12 chars
+|%10.5f|	printf =   |-123.45600|	wrote  12 chars
+|%10.5f|	debuglog = |-123.45600|	wrote  12 chars
+|%.4f|	printf =   |-123.4560|	wrote  11 chars
+|%.4f|	debuglog = |-123.4560|	wrote  11 chars
+
+...testing value = -123.456 width = '*'
+|%*f|	printf =   |-123.456000|	wrote  13 chars
+|%*f|	debuglog = |-123.456000|	wrote  13 chars
+|%*f|	printf =   |-123.456000|	wrote  13 chars
+|%*f|	debuglog = |-123.456000|	wrote  13 chars
+|%*f.1|	printf =   |-123.456000.1|	wrote  15 chars
+|%*f.1|	debuglog = |-123.456000.1|	wrote  15 chars
+
+...testing precision = '*'
+|%.*f|	printf =   |-123.4560000000|	wrote  17 chars
+|%.*f|	debuglog = |-123.4560000000|	wrote  17 chars
+|%.*f|	printf =   |-123.46|	wrote   9 chars
+|%.*f|	debuglog = |-123.46|	wrote   9 chars
+|%10.*f|	printf =   |   -123.46|	wrote  12 chars
+|%10.*f|	debuglog = |   -123.46|	wrote  12 chars
+
+...testing width/precision = '*'
+|%*.*f|	printf =   |          -123.45600|	wrote  22 chars
+|%*.*f|	debuglog = |          -123.45600|	wrote  22 chars
+|%*.*f|	printf =   |-123.4560|	wrote  11 chars
+|%*.*f|	debuglog = |-123.4560|	wrote  11 chars
+
+...testing left justification
+|%10f|	printf =   |  3.141500|	wrote  12 chars
+|%10f|	debuglog = |  3.141500|	wrote  12 chars
+|%-10f|	printf =   |3.141500  |	wrote  12 chars
+|%-10f|	debuglog = |3.141500  |	wrote  12 chars
diff --git a/none/tests/unit_debuglog.vgtest b/none/tests/unit_debuglog.vgtest
new file mode 100644
index 0000000..1caaaf5
--- /dev/null
+++ b/none/tests/unit_debuglog.vgtest
@@ -0,0 +1,2 @@
+prog: unit_debuglog
+vgopts: -q