On 2010-08-16 10:51, Dag-Erling Smørgrav wrote:
> Bruce Cran <br...@cran.org.uk> writes:
>> Somewhat related, there are overflow bugs in humanize_number - for
>> example df(1) fails to display space from a 100PB filesystem
>> correctly.
> 
> Patch?  :)

Attached.  This makes humanize_number() work properly for all possible
int64_t values.

It has one significant behaviour change, though: with the original
version of humanize_number(), if you used a fixed scale parameter, and
the number was larger than the buffer could contain, it would simply
stomp over the end of the buffer.  With this fix, it will return -1
instead.  I hope you agree that is better. :)
diff --git a/lib/libutil/humanize_number.c b/lib/libutil/humanize_number.c
index de98587..a304137 100644
--- a/lib/libutil/humanize_number.c
+++ b/lib/libutil/humanize_number.c
@@ -42,13 +42,34 @@ __FBSDID("$FreeBSD$");
 #include <locale.h>
 #include <libutil.h>
 
+static inline void
+divide(int64_t num, int64_t den, int64_t *quotp, int64_t *quotrp, int *fracrp)
+{
+       int frac;
+
+       if (den == 1) {
+               *quotp = *quotrp = num;
+               *fracrp = 0;
+       } else {
+               *quotp = *quotrp = num / den;
+               frac = (5 * (num % den)) / (den / 2);
+               if (frac >= 5)
+                       ++*quotp;
+               *fracrp = (5 * (num % den) + den / 4) / (den / 2);
+               if (*fracrp == 10) {
+                       ++*quotrp;
+                       *fracrp = 0;
+               }
+       }
+}
+
 int
 humanize_number(char *buf, size_t len, int64_t bytes,
     const char *suffix, int scale, int flags)
 {
        const char *prefixes, *sep;
-       int     b, i, r, maxscale, s1, s2, sign;
-       int64_t divisor, max;
+       int     i, r, maxscale, sign, fracr;
+       int64_t divisor, max, fulldiv, quot, quotr;
        size_t  baselen;
 
        assert(buf != NULL);
@@ -88,11 +109,10 @@ humanize_number(char *buf, size_t len, int64_t bytes,
                buf[0] = '\0';
        if (bytes < 0) {
                sign = -1;
-               bytes *= -100;
+               bytes = -bytes;
                baselen = 3;            /* sign, digit, prefix */
        } else {
                sign = 1;
-               bytes *= 100;
                baselen = 2;            /* digit, prefix */
        }
        if (flags & HN_NOSPACE)
@@ -107,39 +127,42 @@ humanize_number(char *buf, size_t len, int64_t bytes,
        if (len < baselen + 1)
                return (-1);
 
+       /* Determine the maximum number that fits. */
+       for (max = 1, i = len - baselen; i-- > 0;)
+               max *= 10;
+
        if (scale & (HN_AUTOSCALE | HN_GETSCALE)) {
-               /* See if there is additional columns can be used. */
-               for (max = 100, i = len - baselen; i-- > 0;)
-                       max *= 10;
-
-               /*
-                * Divide the number until it fits the given column.
-                * If there will be an overflow by the rounding below,
-                * divide once more.
-                */
-               for (i = 0; bytes >= max - 50 && i < maxscale; i++)
-                       bytes /= divisor;
+               /* Divide the number until it fits the given length. */
+               for (i = 0, fulldiv = 1; i < maxscale; i++) {
+                       divide(bytes, fulldiv, &quot, &quotr, &fracr);
+                       if ((quotr < 10 && i > 0 && flags & HN_DECIMAL &&
+                           quotr * 100 < max) || quot < max)
+                               break;
+                       fulldiv *= divisor;
+               }
 
                if (scale & HN_GETSCALE)
                        return (i);
-       } else
-               for (i = 0; i < scale && i < maxscale; i++)
-                       bytes /= divisor;
+       } else {
+               for (i = 0, fulldiv = 1; i < scale; i++)
+                       fulldiv *= divisor;
+               divide(bytes, fulldiv, &quot, &quotr, &fracr);
+               if ((quotr < 10 && i > 0 && flags & HN_DECIMAL &&
+                   quotr * 100 >= max) || quot >= max)
+                       return (-1);
+       }
 
-       /* If a value <= 9.9 after rounding and ... */
-       if (bytes < 995 && i > 0 && flags & HN_DECIMAL) {
+       /* If quotient < 10 after rounding and ... */
+       if (quotr < 10 && i > 0 && flags & HN_DECIMAL) {
                /* baselen + \0 + .N */
                if (len < baselen + 1 + 2)
                        return (-1);
-               b = ((int)bytes + 5) / 10;
-               s1 = b / 10;
-               s2 = b % 10;
                r = snprintf(buf, len, "%d%s%d%s%s%s",
-                   sign * s1, localeconv()->decimal_point, s2,
+                   sign * (int) quotr, localeconv()->decimal_point, fracr,
                    sep, SCALE2PREFIX(i), suffix);
        } else
                r = snprintf(buf, len, "%" PRId64 "%s%s%s",
-                   sign * ((bytes + 50) / 100),
+                   sign * quot,
                    sep, SCALE2PREFIX(i), suffix);
 
        return (r);
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to