Modulus Operator Usage

Started by
15 comments, last by lightxbulb 10 years, 11 months ago
Ok, I guess this could be a bit of a math question so if he mods want it there then I understand. Just posted it here as I'm trying to understand it.

Well I understand it. As in I know when it is used what it is doing. I understand its giving me the remainder of two numbers being divided. That's easy enough. My question is more along the lines of, how do you know when to use it? Yes I know when I need to know or get the remainder but how/when do I really need to do know this on calculations? How do I know I need to use it here? I see some examples of code and they say "a simple modulus operator will give us what we need. I guess when I sit and actually work it out like it was a math problem I see how it helps. Though if I was just trying to solve a problem on my own I would never think to use it really.

Maybe it is because Math isn't my best subject, though I'm not terrible at it, mostly a B student in it. I mean I've gotten myself through Calculus 1, Calculus 2, Probability, and Linear Algebra (know a little bit already) next semester. Though I just can't quite ever see "ok this problem needs to be solved by the modulus operator." I guess I could see a problem here or there that I know I'd use it (could see it helping when doing some unit conversions) but then again I know that because I have basically been told it's used here.

So how do I know or figure out really when to use it?

Thanks to all who help.
Advertisement
I'm not really sure how to answer it, as it's the same as any other operation -- how do you know when to use addition or division?

A lot of the time you need to know how to do an operation and the opposite of that operation. e.g. if an attack subtracts 5 from health, then to 'undo' that attack, you add 5 to health.
In these cases, then mod will come up whenever you're trying to do the opposite of integer division. Keep in mind though that this is only for integers -- with floats there's no remainder in division because they can store fractions as well as whole numbers.

One of the most common examples is calculating an index for a 2D array (when emulating a 2D array using a 1D array).
int* array2d = new int[width*height];

To calculate a 1D index from 2D x/y values, we can use:
int index = x + y*width;

If you want to then turn this index back into an x and y value, we need the opposite of this function.
Mathematically, we can rearrange the above formula to solve for x/y:
x = index - y*width
y = index/width - x/width

or working with real numbers, we can find y first then use it to find x:
y = RoundDown( index / width ) (this works because we know x < width, so x/width < 1)
x = index - y*width

or still working with real numbers, we can find x first then use it to find y:
x = Remainder( index / width ) (again, this works because we know x will be in the fractional part and y in the whole part)
y = index/width - x/width

or working with integers, we can take the two bold bits above that map easily over to integer code:
int y = index / width; (integer division rounds down)
int x = index % width;
It is best used when you want a number to go back to the beginning after it reaches a certain limit.

examples:

an analog clock. The hour after 12 is 1.

animation control: you want a sprite to change every x frames instead of every frame, so you would keep a counter that is incremented every frame. When the counter reaches x, you want to set it back to zero and change the sprite.

hash table: your hash is greater than the size of the table, so you reduce it using a modulo.

circular buffer/queue: same as hash table, you want the index value to be smaller than the size of the array.
You can also use it to bring a random int into a range:
int n = intrand(); //gives random value between 0 and a bajillion
n %= MY_ MAX_RAND; //brings the value into the range 0 to MY_MAX_RAND

The circular sequence behavior that ultramailman pointed out can be useful in many situations:
//pseudocode
int n = 0;
loop {
  n %= 5;
  print(n++);
}
0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0.....
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

You can also use it to bring a random int into a range:


int n = intrand(); //gives random value between 0 and a bajillion
n %= MY_ MAX_RAND; //brings the value into the range 0 to MY_MAX_RAND

The circular sequence behavior that ultramailman pointed out can be useful in many situations:

//pseudocode
int n = 0;
loop {
  n %= 5;
  print(n++);
}
0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0.....

Actually HUGE thank you for this. Just yesterday I was dealing with that and I ended up writing a simple if statement to reset it! I don't think I would have ever thought about using the modulus operator there. Thanks.

Hodgman: That's an interesting example also because I was working out a formula for that the other day. Interesting I ask about this and I immediately get like two examples that I've dealt or thought of.

On what you said about "how do you know when to use addition or division?" That is an interesting question to pose. I would say just because it was drilled into our minds so much in school that you learn to use it while using the modulus isn't really drilled into your mind to use in school. Only reason I can think of I guess...?

Thanks everyone!

I would say just because it was drilled into our minds so much in school that you learn to use it while using the modulus isn't really drilled into your mind to use in school. Only reason I can think of I guess...?

In that case, think of it as being basically the same operation as division. With integers, division has two results. You get the quotient with "/" and the remainder with "%".

With floats, division only has one result, you get the "quotient + remainder/divisor" with "/".

You can also use it to bring a random int into a range:

This is a very common usage, but if you require a completely random statistical distribution of random numbers, then this can screw you with a bias.

e.g. say that rand returns numbers from 0-15, but you want numbers 0-12. In this case, the numbers 13/14/15 are wrapped around to 0/1/2, which makes these first three numbers twice as likely to be picked than the rest (3-12) are!

Usually this isn't a problem because rand generates really big numbers, making the bias very small and not noticeable so I would still usually use modulo for this.

It's usually only a problem in very precise scientific simulators, where you'd want to cast the random number to a float and divide by RAND_MAX, to get a normalized range of 0-1, and then scale this value to the desired range.

Some practical everyday uses:

// I want to trigger something every 7 executions:

static int count = 0;

count = ++count % 7;

if( !count ) {do something}

// I want to cycle 0-6 repeatedly.

static int count = 0;

count = ++count % 7;

In the first case the 'if' relies on the fact that the only "true" condition is when something is non-zero. In the second case a value modded by any higher value results in the same value, it goes to zero when modded with itself. Basically a variable in both cases is going to step from 0 to mod value minus one, or 0-6 in this case and keep repeating.

I think I use the first case with the 'if' statement more often but I know I use the second case on occasion also.

In the first case the 'if' relies on the fact that the only "true" condition is when something is zero.

'true' is typically nonzero, isn't it? In c/c++ it would be "if(!count) {stuff}".
#include <iostream>

int main() {
  //print multiples of 5 up to 100
  for(int n = 1; n <= 100; ++n) {
    if(!(n % 5)) {std::cout << n << std::endl;}
  }
}
Is zero true in java or something?

Also, addressing the general topic, if you're wanting both the quotient and the remainder then check to see if there's an intrinsic that can give you both results without having to do the division twice. When the CPU does integer division it actually produces both results, regardless of whether you asked for division or modulus. It's not a huge issue, but division is the heaviest integer math operation for the cpu by a wide margin, so in anything where performance matters it can be good to know if there's an intrinsic. (Don't prematurely optimize, but don't prematurely pessimize either.)

Edit: Apparently there's one in std::div, which is convenient, but poking around for a minute I see that it's common for modern compilers to optimize this problem away in most cases.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

In the first case the 'if' relies on the fact that the only "true" condition is when something is zero.

'true' is typically nonzero, isn't it? In c/c++ it would be "if(!count) {stuff}".

#include <iostream>

int main() {
  //print multiples of 5 up to 100
  for(int n = 1; n <= 100; ++n) {
    if(!(n % 5)) {std::cout << n << std::endl;}
  }
}
Is zero true in java or something?

Blah, sorry.... Typo, "non-zero".. :)

Some practical everyday uses:

// I want to trigger something every 7 executions:

static int count = 0;

count = ++count % 7;

if( count ) {do something}

// I want to cycle 0-6 repeatedly.

static int count = 0;

count = ++count % 7;

In the first case the 'if' relies on the fact that the only "true" condition is when something is zero. In the second case a value modded by any higher value results in the same value, it goes to zero when modded with itself. Basically a variable in both cases is going to step from 0 to mod value minus one, or 0-6 in this case and keep repeating.

I think I use the first case with the 'if' statement more often but I know I use the second case on occasion also.

EDIT: Nvm, no reason for this post I guess. lol. Typo. :)

Like Khatharr said, in C/C++ it would actually execute every time except when count is 0.

As a quick test:


for(index = 0; index < 10; ++index)
{
     count = ++count % 7;
     if(count)
     {
          std::cout << count << " ";
     }

}

produced output:


1 2 3 4 5 6 1 2 3

This topic is closed to new replies.

Advertisement