Should = be avoided when using integers, such as in a For loop? [closed]

I have explained to my students that equal-to testing is not reliable for float variables, but is fine for integers. The textbook I am using said that it is easier to read > and < than >= and <=. I agree to some extent, but in a For loop? Isn’t it clearer to have the loop specify the starting and ending values?

Am I missing something that the textbook author is correct about?

Another example is in Range tests like:

if score > 89 grade = ‘A’
else if score > 79 grade = ‘B’ …

Why not just say: if score >= 90 ?

17

In curly-braced programming languages with zero-based arrays, it’s customary to write for loops like this:

for (int i = 0; i < array.Length; i++) { }

This traverses all of the elements in the array, and is by far the most common case. It avoids the use of <= or >=.

The only time this would ever have to change is when you need to skip the first or last element, or traverse it in the opposite direction, or traverse it from a different start point or to a different end point.

For collections, in languages that support iterators, it is more common to see this:

foreach (var item in list) { }

Which avoids the comparisons entirely.

If you’re looking for a hard and fast rule as to when to use <= vs <, there isn’t one; use what best expresses your intent. If your code needs to express the concept “Less than or equal to 55 miles per hour,” then it needs to say <=, not <.

To answer your question about the grade ranges, >= 90 makes more sense, because 90 is the actual boundary value, not 89.

11

It doesn’t matter.

But for the sake of the argument, let’s analyse the two options: a > b vs a >= b.

Hang on! Those are not equivalent!
OK, then a >= b -1 vs a > b or a > b vs a >= b +1.

Hm, a >b and a >= b both look better than a >= b - 1 and a >= b +1. What are all these 1s anyway? So I’d argue that any benefit from having > instead of >= or vice-versa is eliminated by having to add or subtract random 1s.

But what if it’s a number? Is it better to say a > 7 or a >= 6? Wait a second. Are we seriously arguing whether it’s better to use > vs >= and ignore the hard coded variables? So it really becomes a question of whether a > DAYS_OF_WEEK is better than a >= DAYS_OF_WEEK_MINUS_ONE… or is it a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONE vs a >= NUMBER_OF_LEGS_IN_INSECT? And we are back to adding/subtracting 1s, only this time in variable names. Or maybe debating if it’s best to use threshold, limit, maximum.

And it looks like there’s no general rule: it depends on what’s being compared

But really, there are far more important things to improve in one’s code and far more objective and reasonable guidelines (e.g. X-character limit per line) that still have exceptions.

6

Computationally there is no difference in cost when using < or > compared to <= or >=. It’s computed equally as fast.

However most for loops will be counting from 0 (because many languages use 0 indexing for their arrays). So the canonical for loop in those languages is

for(int i = 0; i < length; i++){
   array[i] = //...
   //...
}

doing this with a <= would require you to add a -1 somewhere to avoid the off-by one error

for(int i = 1; i <= length; i++){
   array[i-1] = //...
   //...
}

or

for(int i = 0; i <= length-1; i++){
   array[i] = //...
   //...
}

Of course if the language uses 1-based indexing then you would use <= as the limiting condition.

Key is that the values expressed in the condition are the ones from the problem description. It’s cleaner to read

if(x >= 10 && x < 20){

} else if(x >= 20 && x < 30){

}

for a half-open interval than

if(x >= 10 && x <= 19){

} else if(x >= 20 && x <= 29){

}

and have to do the math to know that there is no possible value between 19 and 20

3

I’d say that the point is not whether you should use > or >=. The point is to use whatever lets you write expressive code.

If you find that you need to add/subtract one, consider using the other operator. I find that good things happen when you start out with a good model of your domain. Then the logic writes itself.

bool IsSpeeding(int kilometersPerHour)
{
    const int speedLimit = 90;
    return kilometersPerHour > speedLimit;
}

This is way more expressive than

bool IsSpeeding(int kilometersPerHour)
{
    const int speedLimit = 90;
    return kilometersPerHour >= (speedLimit + 1);
}

In other cases, the other way is preferable:

bool CanAfford(decimal price, decimal balance)
{
    return balance >= price;
}

Much better than

bool CanAfford(decimal price, decimal balance)
{
    const decimal epsilon = 0e-10m;
    return balance > (price - epsilon);
}

Please excuse the “primitive obsession”. Obviously you’d want to use a Velocity- and Money-type here respectively, but I omitted them for brevity. The point is: Use the version that is more concise and that lets you focus on the business problem you want to solve.

2

As you pointed out in your question, testing for equality on float variables is not reliable.

The same holds true for <= and >=.

However, there is no such reliability issue for integer types. In my opinion, the author was expressing her opinion as to which is more readable.

Whether or not you agree with him is of course your opinion.

3

Each of the relations <, <=, >=, > and also == and != have their use-cases for comparing two floating-point values. Each has a specific meaning and the appropriate one should be chosen.

I’ll give examples for cases where you want exactly this operator for each of them. (Be aware of NaNs, though.)

  • You have an expensive pure function f that takes a floating-point value as input. In order to speed up your computations, you decide to add a cache of the most recently computed values, that is, a lookup table mapping x to f(x). You’ll really want to use == to compare the arguments.
  • You want to know whether you can meaningfully divide by some number x? You probably want to use x != 0.0.
  • You want to know whether a value x is in the unit interval? (x >= 0.0) && (x < 1.0) is the correct condition.
  • You have computed the determinant d of a matrix and want to tell whether it is positive definite? There is no reason to use anything else but d > 0.0.
  • You want to know whether Σn = 1, …, ∞ n−α diverges? I’d test for alpha <= 1.0.

Floating-point math (in general) is not exact. But that doesn’t mean that you should fear it, treat it as magic, and certainly not always treat two floating-point quantities equal if they are within 1.0E-10. Doing so will really break your math and cause all weird things to happen.

  • For example, if you use fuzzy comparison with the cache mentioned above, you’ll introduce hilarious errors. But even worse, the function will no longer be pure and the error depend on the previously computed results!
  • Yes, even if x != 0.0 and y is a finite floating-point value, y / x need not be finite. But it might be relevant to know whether y / x is not finite because of overflow or because the operation was not mathematically well-defined to begin with.
  • If you have a function that has as a precondition that an input parameter x has to be in the unit interval [0, 1), I would be really upset if it fired an assertion failure when called with x == 0.0 or x == 1.0 - 1.0E-14.
  • The determinant you have computed might not be accurate. But if you’re going to pretend that the matrix is not positive definite when the computed determinant is 1.0E-30, nothing is gained. All you did was increasing the likelihood of giving the wrong answer.
  • Yes, your argument alpha might be affected by rounding errors and therefore alpha <= 1.0 might be true even though the true mathematical value for the expression alpha was computed from might have been truly greater than 1. But there is nothing you could possibly do about it at this point.

As always in software engineering, handle errors at the appropriate level and handle them only once. If you add rounding errors on the order of 1.0E-10 (this seems to be the magic value most people use, I don’t know why) each time you’re comparing floating-point quantities, you’ll soon be at errors on the order of 1.0E+10

1

The type of conditional used in a loop may limit the kinds of optimizations a compiler can perform, for better or for worse. For example, given:

uint16_t n = ...;
for (uint16_t i=1; i<=n; i++)
  ...  [loop doesn't modify i]

a compiler could assume that the above condition should cause the loop to
exit after the nth pass loop unless n might 65535 and the loop might exit
in some fashion other than by i exceeding n. If those conditions apply,
the compiler must generate code which would cause the loop to run until something other than the above condition causes it to exit.

If the loop had instead been written as:

uint16_t n = ...;
for (uint16_t ctr=0; ctr<n; ctr++)
{
  uint16_t i = ctr+1;
  ... [loop doesn't modify ctr]
}

then a compiler could safely assume that the loop would never need to
execute more than n times and may thus be able to generate more efficient
code.

Note that any overflow with signed types can have nasty consequences.
Given:

int total=0;
int start,lim,mult; // Initialize values somehow...
for (int i=start; i<=lim; i++)
  total+=i*mult;

A compiler might rewrite that as:

int total=0;
int start,lim,mult; // Initialize values somehow...
int loop_top = lim*mult;
for (int i=start; i<=loop_top; i+=mult)
  total+=i;

Such a loop would behave identically to the original if no overflow
occur in the calculations, but could run forever even on hardware
platforms where integer overflow would normally have consistent wrapping
semantics.

5

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *