Getting a reliable integer percentage from two (64-bit) integers

On my platform, unsigned long long is 64 bits (8 bytes). Suppose I have two such variables:

 unsigned long long partialSize; unsigned long long totalSize; //somehow determine partialSize and totalSize 

How can I reliably determine how many percent (rounded to the nearest integer) partialSize has a value of totalSize ? (If possible, it would be nice if I did not assume that the first is less than the last, but if I really need to make this assumption, thatโ€™s fine. But we can certainly assume that both of them are non-negative.)

For example, is the following code completely bulletproof? My fear is that it contains some kind of rounding, casting, or conversion errors that could cause the coefficient to go out of reach under some conditions.

 unsigned long long ratioPercentage = (unsigned long long)( ((double)partialSize)/((double)totalSize) * 100.0 ); 
+4
source share
3 answers

It is not completely bulletproof. double mantissae is only 53 bits (52 + 1 implicit), so if your numbers are greater than 2^53 , converting to double will generally result in rounding errors. However, rounding errors are very small with respect to the numbers themselves, so a percentage calculation that leads to an integer value will lead to more inaccuracy than the conversion.

Perhaps a more serious problem is that it will always round down, for example. for totalSize = 1000 and partialSize = 99 it will return 9 , not a closer value of 10 . You can get better rounding by adding 0.5 before you start unsigned long long .

You can get accurate results using only integer arithmetic (if the end result is not overflowing), quite easily if partialSize not too large:

 if (partialSize <= ULLONG_MAX / 100) { unsigned long long a = partialSize * 100ULL; unsigned long long q = a / totalSize, r = a % totalSize; if (r == 0) return q; unsigned long long b = totalSize / r; switch(b) { case 1: return q+1; case 2: return totalSize % r ? q : q+1; // round half up default: return q; } } 

Easy modifications if you want a floor, a ceiling or a round half to parity.

Well, if totalSize >= 100 and ULLONG_MAX / 100 >= partialSize % totalSize ,

 unsigned long long q0 = partialSize / totalSize; unsigned long long r = partialSize % totalSize; return 100*q0 + theAbove(r); 

In other cases, it gets weirder, I'm not interested in this, but you can convince me if you need it.

+4
source

Please note that your formula is incorrect, as it skips the +0.5 required to round to the nearest.

So, I will proceed from this corrected formula:

 (unsigned long long)( ((double)partialSize)/((double)totalSize) * 100.0 + 0.5); 

As I mentioned in the comments, the direct method, although simple, does not guarantee proper rounding of the results. So your intuition is right in the sense that it is not bulletproof.

In the vast majority of cases, it will still be correct, but there will be a small set of borderline cases when it will be incorrectly rounded. Regardless of whether it is up to you. But the direct method is usually sufficient for most purposes.

Why this may be unsuccessful:

There are 4 rounding levels. (fixed from 2, which I mentioned in the comments)

  • Throws 64-bit โ†’ 53-bit
  • Separation
  • Multiply by 100.
  • The final.

Whenever you have multiple sources of rounding, you suffer from the usual sources of floating point errors.

Examples of counters:

Although this is rare, I will give a few examples where a straightforward formula will give an incorrectly rounded result:

  850536266682995018 / 3335436339933313800 // Correct: 25% Formula: 26% 3552239702028979196 / 10006309019799941400 // Correct: 35% Formula: 36% 1680850982666015624 / 2384185791015625000 // Correct: 70% Formula: 71% 

Decision:

I canโ€™t think of a pure 100% bulletproof solution other than using arbitrary precision arithmetic .

But in the end, do you really need it to always be perfectly rounded?


EDIT:

For smaller numbers, here is a very simple solution, which is rounded by 0.5 :

 return (x * 100 + y/2) / y; 

This will work until x * 100 + y/2 overflows.

@ Daniel Fisher's answer has a more complete solution for other rounding actions. Although it should not be too difficult to change it to get along smoothly.

+3
source

A single formula will always overflow, break, or give big errors for some values.
This combination works well almost always:

 if (totalSize > 1000000) { pct = partialSize / (totalSize / 100); } else { pct = (partialSize*100) / totalSize; } 

This will happen only when partialSize is greater than MAX_U_LONG_LONG / 100 and totalSize is below 1,000,000. In this case, the correct percentage is much more than 100%, so this is not very interesting.

+2
source

Source: https://habr.com/ru/post/1388752/


All Articles