diff options
Diffstat (limited to 'lib/string_helpers.c')
| -rw-r--r-- | lib/string_helpers.c | 63 | 
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,  		[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 }; +	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. +	 * +	 * Note: it's safe to throw away the remainders here because all the +	 * precision is in the coefficients. +	 */ +	while (blk_size >> 32) { +		do_div(blk_size, divisor[units]);  		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 >> 32) { +		do_div(size, divisor[units]);  		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++;  	} +	/* work out in j how many digits of precision we need from the +	 * remainder */  	sf_cap = size;  	for (j = 0; sf_cap*10 < 1000; j++)  		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 /= 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);  		tmp[j+1] = '\0';  	} | 
