Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does C perform the % operation interally

Tags:

c

modulo

I am curious to understand the logic behind the mod operation since I understand that bit-shifting operations can be performed to do different things such as bit shifting to multiply.

One way I can see it being done is by a recursive algorithm that keeps dividing until you cannot divide anymore, but this does not seem efficient.

Any ideas will be helpful. Thanks in advance!

like image 745
StevenTsooo Avatar asked Jun 26 '13 18:06

StevenTsooo


1 Answers

The quick version is: Depends on hardware, the optimizer, if it's division by a constant or not (pdf), if there's exceptions to be checked for (e.g. modulo by 0), if and how negative numbers are handled (this is a scary question for C++), etc...

R gave a nice, concise answer for unsigned integers, but it's difficult to understand unless you're well versed with C.

The crux of the technique illuminated by R is to strip away multiples of q until there's no more multiples of q left. We could naively do this with a simple loop:

while (p >= q) p -= q; // One liner, woohoo!

The code may be short, but for large values of p and small values of q this might take a very long time.

Better than stripping away one q at a time would be to strip away many q's at a time. Note that we actually want to strip away as many q's as possible -- that is, floor(p/q) many q's... And indeed, that's a valid technique. For unsigned integers, one would expect that p % q == p - (p / q) * q. (Note that unsigned integer division rounds down.)

But this almost feels like cheating because division and remainder operations are so intimately related. (In fact, often if hardware natively supports division, it supports a divide-and-compute-remainder operation because they're so strongly related.)

Assuming we've no access to division, how shall we find a multiple of q greater than 1 to strip away? In hardware, fixed shift operations are cheap (if not practically free) and conceptually represent multiplication by a non-negative power of two. For example, shifting a bit string left by 3 is equivalent to multiplying by 8 (that is, 2^3), e.g. 5 decimal is equivalent to '101' binary. Shift '101' in binary by adding three zeroes on the right (giving '101000') and the result is 50 in decimal -- five times eight.

Likewise, shift operations are very cheap as software operations and you'll struggle to find a controller that doesn't support them and quickly. (Some architectures such as ARM can even combine shifts with other instructions to make them 'free' a good deal of the time.)

ARMed (couldn't resist) with these shift operations, we can proceed as follows:

  1. Find out the largest power of two we can multiply q by and still be less than p.
  2. Working from the largest power of two to the smallest, multiply q by each power of two and if it's less than what's left of p subtract it from what's left of p.
  3. Whatever you've got left is the remainder.

Why does this work? Because in the end you'll find that all the subtracted powers of two actually sum to floor(p / q)! Don't take my word for it, similar knowledge has been known for a very long time.

Breaking apart R's answer:

#define HI (-1U-(-1U/2))

This effectively gives you an unsigned integer with only the highest value bit set.

unsigned i;
for (i=0; !(HI & (q<<i)); i++);

This line actually finds the highest power of two q can be multiplied before overflowing an unsigned integer. This isn't strictly necessary, but it doesn't change the results other than increasing the amount of execution time required.

In case you're not familiar with the C-isms in this line:

  1. (q<<i) is a left bit shift by i. Recall this is equivalent to multiplying by 2^i.
  2. HI & (q<<i) performs a bitwise-AND. Since HI only has its top bit populated this will only result in a non-zero value when (q<<i) is large enough to cause the top bit to be non-zero. One more shift over to the left and there'd be an integer overflow.
  3. !(HI & (q<<i)) is 'true' when (HI & (q<<i)) is zero and 'false' otherwise.

do { if (p >= (q<<i)) p -= (q<<i); } while (i--);

This is a simple decreasing loop do { .... } while (i--);. Note that post-decrementing is used on i so the loop executes, then it checks to see if i is not zero, then it subtracts one from i, and then if its earlier check resulted in true it continues. This has the property that the loop executes its last time when i is 0. This is important because we may need to strip away an unmultiplied copy of q.

if (p >= (q<<i)) checks if the 2^i * q is less than or equal to p. If it is, p -= (q<<i) strips it away.

The remainder is left.

like image 169
Kaganar Avatar answered Sep 28 '22 06:09

Kaganar