aboutsummaryrefslogtreecommitdiffstats
path: root/lib/string_helpers.c
diff options
context:
space:
mode:
authorJames Bottomley <JBottomley@Odin.com>2016-01-20 17:58:29 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2016-01-20 20:09:18 -0500
commit564b026fbd0d28e9f70fb3831293d2922bb7855b (patch)
treeab7a5f6751cb7ef5764031e6f9bfee8a051aa2a7 /lib/string_helpers.c
parenta4cc3c3c7356ded3eba5905f94279382a05d9c96 (diff)
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: b9f28d863594c429e1df35a0474d2663ca28b307 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>
Diffstat (limited to 'lib/string_helpers.c')
-rw-r--r--lib/string_helpers.c63
1 files changed, 43 insertions, 20 deletions
diff --git a/lib/string_helpers.c b/lib/string_helpers.c
index 5939f63d90cd..5c88204b6f1f 100644
--- a/lib/string_helpers.c
+++ b/lib/string_helpers.c
@@ -43,50 +43,73 @@ void string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
43 [STRING_UNITS_10] = 1000, 43 [STRING_UNITS_10] = 1000,
44 [STRING_UNITS_2] = 1024, 44 [STRING_UNITS_2] = 1024,
45 }; 45 };
46 int i, j; 46 static const unsigned int rounding[] = { 500, 50, 5 };
47 u32 remainder = 0, sf_cap, exp; 47 int i = 0, j;
48 u32 remainder = 0, sf_cap;
48 char tmp[8]; 49 char tmp[8];
49 const char *unit; 50 const char *unit;
50 51
51 tmp[0] = '\0'; 52 tmp[0] = '\0';
52 i = 0; 53
53 if (!size) 54 if (blk_size == 0)
55 size = 0;
56 if (size == 0)
54 goto out; 57 goto out;
55 58
56 while (blk_size >= divisor[units]) { 59 /* This is Napier's algorithm. Reduce the original block size to
57 remainder = do_div(blk_size, divisor[units]); 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]);
58 i++; 72 i++;
59 } 73 }
60 74
61 exp = divisor[units] / (u32)blk_size; 75 while (size >> 32) {
62 /* 76 do_div(size, divisor[units]);
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;
69 i++; 77 i++;
70 } else {
71 remainder *= size;
72 } 78 }
73 79
80 /* now perform the actual multiplication keeping i as the sum of the
81 * two logarithms */
74 size *= blk_size; 82 size *= blk_size;
75 size += remainder / divisor[units];
76 remainder %= divisor[units];
77 83
84 /* and logarithmically reduce it until it's just under the divisor */
78 while (size >= divisor[units]) { 85 while (size >= divisor[units]) {
79 remainder = do_div(size, divisor[units]); 86 remainder = do_div(size, divisor[units]);
80 i++; 87 i++;
81 } 88 }
82 89
90 /* work out in j how many digits of precision we need from the
91 * remainder */
83 sf_cap = size; 92 sf_cap = size;
84 for (j = 0; sf_cap*10 < 1000; j++) 93 for (j = 0; sf_cap*10 < 1000; j++)
85 sf_cap *= 10; 94 sf_cap *= 10;
86 95
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 */
88 remainder *= 1000; 100 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) {
90 snprintf(tmp, sizeof(tmp), ".%03u", remainder); 113 snprintf(tmp, sizeof(tmp), ".%03u", remainder);
91 tmp[j+1] = '\0'; 114 tmp[j+1] = '\0';
92 } 115 }