From: James Bottomley <James.Bottomley@HansenPartnership.com>
To: Vitaly Kuznetsov <vkuznets@redhat.com>
Cc: linux-scsi <linux-scsi@vger.kernel.org>,
"ulf.hansson@linaro.org" <ulf.hansson@linaro.org>,
"linux@rasmusvillemoes.dk" <linux@rasmusvillemoes.dk>,
"andriy.shevchenko@linux.intel.com"
<andriy.shevchenko@linux.intel.com>,
"keescook@chromium.org" <keescook@chromium.org>,
"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
"akpm@linux-foundation.org" <akpm@linux-foundation.org>
Subject: Re: [PATCH v2] string_helpers: fix precision loss for some inputs
Date: Tue, 03 Nov 2015 13:21:48 -0800 [thread overview]
Message-ID: <1446585708.6440.47.camel@HansenPartnership.com> (raw)
In-Reply-To: <1446582810.6440.36.camel@HansenPartnership.com>
From: James Bottomley <JBottomley@Odin.com>
It was noticed that we lose precision in the final calculation for some
inputs. The most egregious example is size=3000 blk_size=1900 in units of 10
should yield 5.70 MB but in fact yields 3.00 MB (oops). This is because the
current algorithm doesn't correctly account for all the remainders in the
logarithms. Fix this by doing a correct calculation in the remainders based
on napier's algorithm. Additionally, now we have the correct result, we have
to account for arithmetic rounding because we're printing 3 digits of
precision. This means that if the fourth digit is five or greater, we have to
round up, so add a section to ensure correct rounding. Finally account for
all possible inputs correctly, including zero for block size.
Reported-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Cc: stable@vger.kernel.org # delay backport by two months for testing
Fixes: b9f28d863594c429e1df35a0474d2663ca28b307
Signed-off-by: James Bottomley <JBottomley@Odin.com>
--
v2: updated with a recommendation from Rasmus Villemoes to truncate the
initial precision at just under 32 bits
diff --git a/lib/string_helpers.c b/lib/string_helpers.c
index 5939f63..363faca 100644
--- a/lib/string_helpers.c
+++ b/lib/string_helpers.c
@@ -43,38 +43,40 @@ void string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
[STRING_UNITS_10] = 1000,
[STRING_UNITS_2] = 1024,
};
- int i, j;
- u32 remainder = 0, sf_cap, exp;
+ static const unsigned int rounding[] = { 500, 50, 5, 0};
+ int i = 0, j;
+ u32 remainder = 0, sf_cap;
char tmp[8];
const char *unit;
tmp[0] = '\0';
- i = 0;
- if (!size)
+
+ if (blk_size == 0)
+ size = 0;
+ if (size == 0)
goto out;
- while (blk_size >= divisor[units]) {
- remainder = do_div(blk_size, divisor[units]);
+ /* This is napier's algorithm. Reduce the original block size to
+ *
+ * coefficient * divisor[units]^i
+ *
+ * we do the reduction so both coefficients are just under 32 bits so
+ * that multiplying them together won't overflow 64 bits and we keep
+ * as much precision as possible in the numbers
+ */
+
+ while (blk_size >= UINT_MAX)
i++;
- }
- exp = divisor[units] / (u32)blk_size;
- /*
- * size must be strictly greater than exp here to ensure that remainder
- * is greater than divisor[units] coming out of the if below.
- */
- if (size > exp) {
- remainder = do_div(size, divisor[units]);
- remainder *= blk_size;
+ while (size >= UINT_MAX)
i++;
- } else {
- remainder *= size;
- }
+
+ /* now perform the actual multiplication keeping i as the sum of the
+ * two logarithms */
size *= blk_size;
- size += remainder / divisor[units];
- remainder %= divisor[units];
+ /* and logarithmically reduce it until it's just under the divisor */
while (size >= divisor[units]) {
remainder = do_div(size, divisor[units]);
i++;
@@ -84,9 +86,20 @@ void string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
for (j = 0; sf_cap*10 < 1000; j++)
sf_cap *= 10;
+ /* express the remainder as a decimal (it's currently the numerator of
+ * a fraction whose denominator is divisor[units]) */
+ remainder *= 1000;
+ remainder /= divisor[units];
+
+ /* add a 5 to the digit below what will be printed to ensure
+ * an arithmetical round up and carry it through to size */
+ remainder += rounding[j];
+ if (remainder >= 1000) {
+ remainder -= 1000;
+ size += 1;
+ }
+
if (j) {
- remainder *= 1000;
- remainder /= divisor[units];
snprintf(tmp, sizeof(tmp), ".%03u", remainder);
tmp[j+1] = '\0';
}
next prev parent reply other threads:[~2015-11-03 21:21 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-11-03 20:33 [PATCH] string_helpers: fix precision loss for some inputs James Bottomley
2015-11-03 21:21 ` James Bottomley [this message]
2015-11-03 22:13 ` [PATCH v2] " Rasmus Villemoes
2015-11-03 22:54 ` James Bottomley
2015-11-03 23:26 ` Rasmus Villemoes
2015-11-03 23:42 ` James Bottomley
2015-11-04 9:02 ` Rasmus Villemoes
2015-11-03 23:12 ` [PATCH v3] " James Bottomley
2015-11-07 0:50 ` [PATCH v4] " James Bottomley
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1446585708.6440.47.camel@HansenPartnership.com \
--to=james.bottomley@hansenpartnership.com \
--cc=akpm@linux-foundation.org \
--cc=andriy.shevchenko@linux.intel.com \
--cc=keescook@chromium.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=linux@rasmusvillemoes.dk \
--cc=ulf.hansson@linaro.org \
--cc=vkuznets@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox