15

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 ?

  • 11
    Unfortunately, since there is no objective difference in behavior between these options, this amounts to an opinion poll on what people consider more intuitive, and polls are not suitable for StackExchange sites like this one. – Ixrec Mar 18 '16 at 15:10
  • 1
    It doesn't matter. Really. – Auberon Mar 18 '16 at 15:10
  • 4
    Actually, this is objectively answerable. Stand by... – Robert Harvey Mar 18 '16 at 15:11
  • 9
    @Ixrec I always find it interesting that "best practice" is not considered a suitable topic. Who doesn't want to improve, or make their code more readable? If people disagree, we all learn more sides of the issue, and might... even... change-our-minds! Ugh, that was so hard to say. Robert Harvey says he can answer it objectively. This will be interesting. –  Mar 18 '16 at 15:16
  • @nocomprende: You can read more about why that happens, here. – Robert Harvey Mar 18 '16 at 15:17
  • 1
    @nocomprende Mostly because "best practice" is an extremely vague and overused term which can refer useful advice based on objective facts about the language, but just as often refers to the "most popular opinion" (or the opinion of whoever's using the term) when in reality all options are equally valid and invalid. In this case, you can only make an objective argument by restricting the answer to certain types of loops as Robert did, and as you pointed out yourself in a comment that does not completely answer the question. – Ixrec Mar 18 '16 at 15:26
  • 1
    @Ixrec so I think everyone is saying that there is no way to write a "best practice" Answer because there is no way to write a coherent Question. I am still left with the textbook author's advice, and I don't know where it came from. Is it commonplace? I found it really hard to google anything that included "<=" or ">=" for some reason. Couldn't get my research off the ground. I am sure this coding issue is an old grey-haired argument, I just want to read about it... –  Mar 18 '16 at 15:29
  • @nocomprende In my experience, it's certainly common to write array-traversing for loops with < instead of <=, and in my experience the overwhelming majority of for loops are something-traversing. I'm sure other programmers' experiences are very different, so that's all I can offer. – Ixrec Mar 18 '16 at 15:33
  • @Ixrec Arrays is the next chapter of the book. The students are not there yet. Recent exercise was to write shapes on the screen: rectangles, diamonds etc. "Even Dwarfs Started Small." –  Mar 18 '16 at 15:36
  • 1
    I like this question, I have thought about asking this many times before. I would say no, there are some times where I need to access the 0th element of an array in a for loop so I say ">=". The other thing you might pull this trick... (val > -1) so you don't have to say = but it doesn't matter. – Snoop Mar 18 '16 at 15:49
  • Don't get me started on: "should I write my IF statement x <= 55 or x > 55"... –  Mar 18 '16 at 16:02
  • 1
    @nocomprende Those two if statements express different things. Now you're getting into the realm of "should my logic be in the then or the else clause?" The answer to which is usually, once again, "do that which most clearly expresses your intent." if (x > 55) { write ticket } – Robert Harvey Mar 18 '16 at 16:23
  • 1
    @RobertHarvey For those of us who read left to right, it might always be better to do "x > 55". – Snoop Mar 18 '16 at 16:32
  • 1
    Also, in some particular cases a <= b does not equal a - 1 < b. Javascript: var a = b = Infinity, (a <= b) !== (a - 1 < b). – mucaho Mar 18 '16 at 16:58
  • 1
    Please be very careful when teaching about floating point values. You don't want to propagate the impression that they're unstable and mysterious. – Lars Viklund Mar 18 '16 at 19:50
  • 1
    @mucaho: Infinity is a floating point value, not an integral value. – JacquesB Mar 18 '16 at 19:51
  • Just to be clear > 89 !== >=90. 89 < 89.1 <= 90 – Chris G Jul 25 '16 at 22:22

7 Answers7

37

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.

Robert Harvey
  • 199,517
  • 9
    It doesn't avoid the comparisons entirely, it just sweeps them under the rug by moving them into the enumerator implementation. :P – Mason Wheeler Mar 18 '16 at 15:16
  • OK on the zero-based addressing and length in that case, but what about situations where you are really just comparing two numbers? If the speed limit is 55, shouldn't I say: if speed <= 55 ? What do I gain by saying < 55 + 1? –  Mar 18 '16 at 15:23
  • 2
    Of course, but that's no longer traversing an array, is it? – Robert Harvey Mar 18 '16 at 15:24
  • 2
    It is traversing a roadway. My original question actually didn't say anything about arrays at all... Wondered why 2 people wrote their answers based on array lengths. Where did that come in? For loops: they aren't just for breakfast anymore. –  Mar 18 '16 at 15:32
  • 4
    Because arrays are the most common use case for for loops like this. The form of for loop I provided here will be instantly recognizable to any developer with a modicum of experience. If you want a more specific answer based on a more specific scenario, you need to include that in your question. – Robert Harvey Mar 18 '16 at 15:41
  • 1
    OK. I asked a general question but got a specific answer. I will be more specific in the future that I want general answers. Now I see why best practice is such a pretzel: there are so many trees! –  Mar 18 '16 at 15:47
  • See my update about the grade ranges. – Robert Harvey Mar 18 '16 at 15:54
  • 1
    Not sure who downvoted you or why. I added stuff to my question to improve it. I hope that is not why. This place is like a Zoo! –  Mar 18 '16 at 15:54
  • 3
    "use what best expresses your intent" <- This! – Jasper N. Brouwer Mar 18 '16 at 19:58
  • 3
    @JasperN.Brouwer It's like a zeroth law of programming. All style rules and coding conventions collapse in deepest shame when their mandates conflict with the code's clarity. – Iwillnotexist Idonotexist Mar 19 '16 at 06:17
  • Did you mean to put a semi-colon rather than a comma in the first for loop? – User Jan 20 '21 at 04:36
17

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.

  • 1
    OK, there is no general rule. (Wondering why the textbook seemed to state a general rule, but I can let it go.) There is no emerging consensus that it was a memo I missed. –  Mar 18 '16 at 15:58
  • 2
    @nocomprende because coding and formatting standards are both purely subjective and highly contentious. Most of the time none of them matter, but programmers still wage holy wars over them. (they only matter when sloppy code is likely to result in bugs) –  Mar 18 '16 at 16:21
  • 1
    I've seen a lot of crazy discussions about coding standards but this one might just take the cake. Really silly. – JimmyJames Mar 18 '16 at 16:31
  • @JimmyJames is this about the discussion of > vs >= or the discussion about whether the discussion of > vs >= is meaningful? although it's probably best to avoid discussing this :p – Thanos Tintinidis Mar 18 '16 at 17:59
  • 2
    “Programs are meant to be read by humans and only incidentally for computers to execute” - Donald Knuth. Use the style which makes it easiest to read, understand, and maintain. – simpleuser Mar 18 '16 at 18:43
  • @ThanosTintinidis Worrying about < vs. <= is ludicrous. There are real issues about code style that matter. This isn't one of them. – JimmyJames Mar 18 '16 at 20:05
7

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

ratchet freak
  • 25,876
  • I see the "length" idea, but what if you are just using values that came from somewhere and are meant to iterate from a starting value to an ending value. A class example was Markup percentage from 5 to 10. Isn't it clearer to say: for ( markup = 5 ; markup <= 10 ; markup++ ) ... ? Why would I change to it test: markup < 11 ? –  Mar 18 '16 at 15:20
  • 6
    @nocomprende you wouldn't, if the boundary values from your problem description are 5 and 10 then it's better to have them explicitly in the code rather than a slightly changed value. – ratchet freak Mar 18 '16 at 15:47
  • @nocomprende The right format is more obvious when the upper bound is a parameter: for(markup = 5; markup <= MAX_MARKUP; ++markup). Anything else would be overcomplicating it. – Navin Mar 19 '16 at 04:48
1

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.

Matthew Flynn
  • 13,385
  • 2
  • 38
  • 57
sara
  • 2,559
  • 2
    Speed limit example is a poor example. Going 90kph in a 90kph zone is not considered speeding. A speed limit is inclusive. – eidsonator Mar 18 '16 at 19:29
  • you are absolutely correct, brain fart on my part. feel free to edit it to use a better example, hopefully the point isn't completely lost. – sara Mar 18 '16 at 19:37
0

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.

Dan Pichelman
  • 13,813
  • Is this a generally held view, by other authors and grey-hairs in the field (I have grey hair, actually)? If it is just "One Reporter's Opinion" I can tell the students that. I have, actually. Never heard of this idea before. –  Mar 18 '16 at 15:38
  • Author's gender is irrelevant, but I updated my answer anyway. I prefer to select < or <= based on what's most natural for the particular problem I'm solving. As others have pointed out, in a FOR loop < makes more sense in C-type languages. There are other use cases that favor <=. Use all the tools at your disposal, when and where appropriate. – Dan Pichelman Mar 18 '16 at 15:44
  • Disagree. There can be reliability issues with integer types: in C, consider: for (unsigned int i = n; i >= 0; i--) or for (unsigned int i = x; i <= y; i++) if y happens to be UINT_MAX. Oops, those loop forever. – jamesdlin Mar 19 '16 at 06:30
0

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

5gon12eder
  • 6,986
  • 2
  • 24
  • 29
-2

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.

supercat
  • 8,445
  • 23
  • 28
  • Yeah, I think that is a little farther along in the textbook. Not a consideration yet. Compiler Optimizations are useful to understand, and overflow has to be avoided, but it was not really the motivation for my question, which was more in terms of how people understand the code. Is it easier to read x > 5 or x >= 6? Well, it depends... –  Mar 18 '16 at 19:43
  • Unless you're writing for an Arduino the max value for int is going to be a bit higher than 65535. – Corey Mar 19 '16 at 08:59
  • 1
    @Corey: The max value for uint16_t is going to be 65535 on any platform where the type exists; I used uint16_t in the first example because I would expect more people to know the exact maximum value of a uint16_t than that of a uint32_t. The same point would apply in either case. The second example is agnostic with regard to the type of "int", and represents a critical behavioral point many people fail to recognize about modern compilers. – supercat Mar 19 '16 at 13:58
  • On a PowerPC processor, code will run faster if the compiler can determine how often the loop iterates (by using the count register, and a loop branch that is not predicted because the processor knows how often it is iterated) – gnasher729 Jan 22 '21 at 21:15
  • @gnasher729: I've used a DSP that had a similar feature, and the compiler for it would optimize simple for loops to use it. While the design of C's for loop may have been convenient for simple compilers, it creates corner cases that compilers wouldn't have to worry about when processing something like a FORTRAN DO loop. – supercat Jan 22 '21 at 21:25