Skip to content

Commit c81f4f0

Browse files
James Bottomleysashalevin
authored andcommitted
string_helpers: fix precision loss for some inputs
[ Upstream commit 564b026 ] 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: b9f28d8 Signed-off-by: James Bottomley <[email protected]> Reported-by: Vitaly Kuznetsov <[email protected]> Cc: <[email protected]> [delay until after 4.4 release] Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]> Signed-off-by: Sasha Levin <[email protected]>
1 parent 4aba582 commit c81f4f0

File tree

1 file changed

+43
-20
lines changed

1 file changed

+43
-20
lines changed

lib/string_helpers.c

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,50 +43,73 @@ void string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
4343
[STRING_UNITS_10] = 1000,
4444
[STRING_UNITS_2] = 1024,
4545
};
46-
int i, j;
47-
u32 remainder = 0, sf_cap, exp;
46+
static const unsigned int rounding[] = { 500, 50, 5 };
47+
int i = 0, j;
48+
u32 remainder = 0, sf_cap;
4849
char tmp[8];
4950
const char *unit;
5051

5152
tmp[0] = '\0';
52-
i = 0;
53-
if (!size)
53+
54+
if (blk_size == 0)
55+
size = 0;
56+
if (size == 0)
5457
goto out;
5558

56-
while (blk_size >= divisor[units]) {
57-
remainder = do_div(blk_size, divisor[units]);
59+
/* This is Napier's algorithm. Reduce the original block size to
60+
*
61+
* coefficient * divisor[units]^i
62+
*
63+
* we do the reduction so both coefficients are just under 32 bits so
64+
* that multiplying them together won't overflow 64 bits and we keep
65+
* as much precision as possible in the numbers.
66+
*
67+
* Note: it's safe to throw away the remainders here because all the
68+
* precision is in the coefficients.
69+
*/
70+
while (blk_size >> 32) {
71+
do_div(blk_size, divisor[units]);
5872
i++;
5973
}
6074

61-
exp = divisor[units] / (u32)blk_size;
62-
/*
63-
* size must be strictly greater than exp here to ensure that remainder
64-
* is greater than divisor[units] coming out of the if below.
65-
*/
66-
if (size > exp) {
67-
remainder = do_div(size, divisor[units]);
68-
remainder *= blk_size;
75+
while (size >> 32) {
76+
do_div(size, divisor[units]);
6977
i++;
70-
} else {
71-
remainder *= size;
7278
}
7379

80+
/* now perform the actual multiplication keeping i as the sum of the
81+
* two logarithms */
7482
size *= blk_size;
75-
size += remainder / divisor[units];
76-
remainder %= divisor[units];
7783

84+
/* and logarithmically reduce it until it's just under the divisor */
7885
while (size >= divisor[units]) {
7986
remainder = do_div(size, divisor[units]);
8087
i++;
8188
}
8289

90+
/* work out in j how many digits of precision we need from the
91+
* remainder */
8392
sf_cap = size;
8493
for (j = 0; sf_cap*10 < 1000; j++)
8594
sf_cap *= 10;
8695

87-
if (j) {
96+
if (units == STRING_UNITS_2) {
97+
/* express the remainder as a decimal. It's currently the
98+
* numerator of a fraction whose denominator is
99+
* divisor[units], which is 1 << 10 for STRING_UNITS_2 */
88100
remainder *= 1000;
89-
remainder /= divisor[units];
101+
remainder >>= 10;
102+
}
103+
104+
/* add a 5 to the digit below what will be printed to ensure
105+
* an arithmetical round up and carry it through to size */
106+
remainder += rounding[j];
107+
if (remainder >= 1000) {
108+
remainder -= 1000;
109+
size += 1;
110+
}
111+
112+
if (j) {
90113
snprintf(tmp, sizeof(tmp), ".%03u", remainder);
91114
tmp[j+1] = '\0';
92115
}

0 commit comments

Comments
 (0)