mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
string_helpers: fix precision loss for some inputs
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.
Fixes: b9f28d8635
Signed-off-by: James Bottomley <JBottomley@Odin.com>
Reported-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Cc: <stable@vger.kernel.org> [delay until after 4.4 release]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
a4cc3c3c73
commit
564b026fbd
@ -43,50 +43,73 @@ void string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
|
|||||||
[STRING_UNITS_10] = 1000,
|
[STRING_UNITS_10] = 1000,
|
||||||
[STRING_UNITS_2] = 1024,
|
[STRING_UNITS_2] = 1024,
|
||||||
};
|
};
|
||||||
int i, j;
|
static const unsigned int rounding[] = { 500, 50, 5 };
|
||||||
u32 remainder = 0, sf_cap, exp;
|
int i = 0, j;
|
||||||
|
u32 remainder = 0, sf_cap;
|
||||||
char tmp[8];
|
char tmp[8];
|
||||||
const char *unit;
|
const char *unit;
|
||||||
|
|
||||||
tmp[0] = '\0';
|
tmp[0] = '\0';
|
||||||
i = 0;
|
|
||||||
if (!size)
|
if (blk_size == 0)
|
||||||
|
size = 0;
|
||||||
|
if (size == 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
while (blk_size >= divisor[units]) {
|
/* This is Napier's algorithm. Reduce the original block size to
|
||||||
remainder = do_div(blk_size, divisor[units]);
|
*
|
||||||
i++;
|
* coefficient * divisor[units]^i
|
||||||
}
|
*
|
||||||
|
* we do the reduction so both coefficients are just under 32 bits so
|
||||||
exp = divisor[units] / (u32)blk_size;
|
* that multiplying them together won't overflow 64 bits and we keep
|
||||||
/*
|
* as much precision as possible in the numbers.
|
||||||
* size must be strictly greater than exp here to ensure that remainder
|
*
|
||||||
* is greater than divisor[units] coming out of the if below.
|
* Note: it's safe to throw away the remainders here because all the
|
||||||
|
* precision is in the coefficients.
|
||||||
*/
|
*/
|
||||||
if (size > exp) {
|
while (blk_size >> 32) {
|
||||||
remainder = do_div(size, divisor[units]);
|
do_div(blk_size, divisor[units]);
|
||||||
remainder *= blk_size;
|
|
||||||
i++;
|
i++;
|
||||||
} else {
|
|
||||||
remainder *= size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size *= blk_size;
|
while (size >> 32) {
|
||||||
size += remainder / divisor[units];
|
do_div(size, divisor[units]);
|
||||||
remainder %= divisor[units];
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* now perform the actual multiplication keeping i as the sum of the
|
||||||
|
* two logarithms */
|
||||||
|
size *= blk_size;
|
||||||
|
|
||||||
|
/* and logarithmically reduce it until it's just under the divisor */
|
||||||
while (size >= divisor[units]) {
|
while (size >= divisor[units]) {
|
||||||
remainder = do_div(size, divisor[units]);
|
remainder = do_div(size, divisor[units]);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* work out in j how many digits of precision we need from the
|
||||||
|
* remainder */
|
||||||
sf_cap = size;
|
sf_cap = size;
|
||||||
for (j = 0; sf_cap*10 < 1000; j++)
|
for (j = 0; sf_cap*10 < 1000; j++)
|
||||||
sf_cap *= 10;
|
sf_cap *= 10;
|
||||||
|
|
||||||
if (j) {
|
if (units == STRING_UNITS_2) {
|
||||||
|
/* express the remainder as a decimal. It's currently the
|
||||||
|
* numerator of a fraction whose denominator is
|
||||||
|
* divisor[units], which is 1 << 10 for STRING_UNITS_2 */
|
||||||
remainder *= 1000;
|
remainder *= 1000;
|
||||||
remainder /= divisor[units];
|
remainder >>= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
snprintf(tmp, sizeof(tmp), ".%03u", remainder);
|
snprintf(tmp, sizeof(tmp), ".%03u", remainder);
|
||||||
tmp[j+1] = '\0';
|
tmp[j+1] = '\0';
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user