1
\$\begingroup\$

The following code

if (i >= N) 
    i = 0;

has been corrected to:

i %= N;

for performance reasons. Does it makes sense?

More context: i is guaranteed to be non negative. The code is used to fill a circular buffer of length N, i is first incremented and then the check is performed. N depends on user input. The code is an interview exercise so there is no clear context.

\$\endgroup\$
5
  • \$\begingroup\$ I think we need more information about the potential values of i and N as well as the platform it should run on if we are to answer this. Also, it seems like this question would be a better fit for Stack Overflow. \$\endgroup\$
    – hoffmale
    Nov 14, 2017 at 11:37
  • 2
    \$\begingroup\$ i does not always have the same value in both cases. \$\endgroup\$ Nov 14, 2017 at 12:07
  • \$\begingroup\$ @BillalBEGUERADJ: It does have the same value if you assume that i is iteratively incremented (i++ in a loop). This is almost always the case when a modulo operator is used. The OP didn't explicitly confirm that (and he should have), but it's a reasonable assumption to make. \$\endgroup\$
    – Flater
    Nov 14, 2017 at 12:18
  • 2
    \$\begingroup\$ This really depends on the context, for example if N is an arbitrary variable then i %= N is a pessimization, but if N is a constant power of two it's a completely different matter. So, is there more context to what N is, or was it supposed to be super general? \$\endgroup\$
    – harold
    Nov 14, 2017 at 13:41
  • \$\begingroup\$ The way to test this is to run code with both versions many times and see what happens. Note that the answer may vary from system to system, as different hardware may optimized differently. \$\endgroup\$
    – mdfst13
    Nov 16, 2017 at 2:46

1 Answer 1

1
\$\begingroup\$

EDIT
I missed the fact that this question was about C instead of C#. This likely invalidates my performance argument, so I removed that part of the answer. However, I do think that the readability argument (which is 90% of my answer) remains to be a relevant consideration.


You didn't really explain what the code does, and you really should have. We need the surrounding context to make sense of why each snippet is relevant.


However, if I can rely on the fact that whoever corrected your code is sure that the alternative works the same way, then I think I understand the scenario:

  • You are incrementing i, e.g. i++;
  • You're trying to have i increment from 0 up to N, multiple times. E.g. if N = 5, you want 0,1,2,3,4,0,1,2,3,4,... For the rest of the answer, I will refer to this behavior as "cyclical values".

The rest of the answer works under this assumption.


I agree with the corrected version.

The modulo operator is a fairly niche operator, compared to other C# operations. By that, I mean that when you encounter the modulo operator, you can almost immediately assume that you're dealing with cyclical values.

This cyclical behavior is exactly the case for you, so I do advocate the use of the modulo operator. It makes the cyclical nature of the algorithm easily discernible.


Distinguishing between the logical and the mathematical.

There are other cases where you can choose between a logical approach and a mathematical one. A simple example is for (mathematical) and foreach (logical):

for(int i = 0 ; i < myArray.Length; i++)
{
    var item = myArray[i];

    // ...
}

foreach(var item in myArray)
{
    //...
}

These two are functionally equivalent. However, more importantly, observe the intention of a for/foreach: it is used to direct the logical flow, it's not used (by itself) for calculating and storing values.
In the majority of cases, I would advocate the use of a foreach instead of a for, because it is easier to read a logical approach when the intention of the code is to direct the logical flow.

Note that for loops have their uses, e.g. when your iterations need a counter value to keep track of which iteration you're in. This is where the mathematical side of things becomes more important. But for the majority of use cases, you'll simply be iterating over an entire list and will not need a counter.

If you boil this down, you get to the following rule of thumb:

In cases where the mathematical and logical approaches are sufficiently equivalent (!) you should use the logical approach when you're directing the algorithm's logic, and you should use the mathematical approach when making calculations.

This closes the gap between the code and the intention of the code. The smaller the gap, the more readable the code will be.

The general idea here is that when you use a mathematical approach for a logical flow, then the developer needs to understand the mathematics to understand how the code will flow.
A sufficiently experienced developer will of course not get hindered by something he's familiar with, e.g. the simple for that I used. But as the logic gets more and more complex, the mathematical approach will compromise more and more of the code readability, faster than the logical approach will.


Let's go back to comparing your two options:

if (i >= N) 
    i = 0;

i %= N;

Observe the intention of the code: You are trying to find the next value of i. That is inherently a mathematical operation, and therefore warrants the use of a mathematical approach over a logical one.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ The question is about performance. Even with "the readability argument (which is 90% of my answer)", the remaining 10% does not clearly deal with the performance issue either - I found none here. Suggest to lead with the performance issue. Then delve into the other issues if still warranted, else the answer has no relevancy here. \$\endgroup\$ Nov 14, 2017 at 18:48
  • \$\begingroup\$ @chux: "This likely invalidates my performance argument, so I removed that part of the answer." Check the edit history. The answer you read now is the remaining 90%. And yes, this question is about performance (pedantically, it's about the validity of a refactoring in the interest of performance), but performance at all costs is not a reasonable argument, hence why I consider the difference in readability. You always need to weigh the impact on other aspects of the code. I wouldn't implement a 0.1% performance gain if the fix made the code thrice as hard to read, for example. \$\endgroup\$
    – Flater
    Nov 15, 2017 at 13:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.