
Counting up vs. counting down - ingve
http://www.tedunangst.com/flak/post/counting-up-vs-counting-down
======
Double_Cast
One solution is to make both bounds explicit, like set-builder notation would.

    
    
      for (size_t i = numBurritos - 1; 0 <= i && i < numBurritos; i--)
    

But this loops infinitely if sizeof(numBurritos) == sizeof(size_t) where
size_t is unsigned, because the entire space of possible values returns True.
This failure mode exists whether we increment _or_ decrement over unsigned
values.

The root problem here isn't decrementing. The root problem is the unsigned
value. So maybe it's best practice to use signed values whenever possible.
This way, the existence of negative values guarantees the existence of a
sentinel.

------
vog
There is a mistake in the code

    
    
        for (size_t i = numBurritos - 1; i >= 0; i++)
            if (wantBurrito(i))
                break;
    

Apart from the didactic bug that _i >= 0_ is always true for _size_t_ , this
is supposed to count down, so i++ should be replaced with i--:

    
    
        for (size_t i = numBurritos - 1; i >= 0; i--)
            if (wantBurrito(i))
                break;

------
scj
There's a more concise way of iterating in reverse.

    
    
      for (size_t i = numBurritos; i-- > 0;)
        if (wantBurrito(i))
          break;
    

Where an advantage is that many assembly languages have a "jump if zero" or
"jump if not zero" instruction.

EDIT: It appears the article has been updated to reflect this method.

~~~
efaref
I've seen this referred to as the "goes-to" operator:

    
    
        size_t i = num_burritos;
            while (i --> 0)           // Read: while 'i' goes to zero
                if (want_burrito(i))
                    break;
    

While it sounds nice, I don't think it's a good idea.

Clearly the correct solution is to use a better language, e.g.:

    
    
        return next((burrito for burrito in reversed(burritos) if burrito.wanted()), None)

------
notacoward
While counting up instead of down does allow the use of "i >= arraylength"
construct to check whether an item was found, it's still not as clear as using
an explicit "found" boolean. Once you do that, it doesn't matter whether you
count up or down. Also, Python's for...else construct is nice for this sort of
thing.

Fun fact: I usually count down unless there's a code-specific reason to count
up. It's one of the biggest "tells" that I wrote a piece of code, and it
exists because I'm old enough to have worked with compilers stupid enough that
they really did produce more efficient code that way. Nowadays I wouldn't
generally recommend it, because AFAIK nobody has made a production-level
compiler that dumb for quite a while.

~~~
pmikesell
The common counting down problem he's referring to is where an unsigned
iterator can never be <0, so it introduces an infinite loop. Setting a found
boolean does nothing to alleviate that situation.

~~~
notacoward
True, that part's still a potential problem. I often avoid it with something
like this (which I believe is more clear anyway).

    
    
       i = arraylength;
       while (i-- > 0)
    

In other cases it really does make more sense to count up. Another
unconsidered possibility is that the "found" case might actually _return_ from
an iterator function, so you can put the "not found" case after the loop
regardless of which way you're counting.

The "counting down can cause an infinite loop" problem does exist, but I've
also seen cases where counting up can fail too (e.g. index incremented twice
within a loop either accidentally or deliberately), or where confusion of
whether to use "i" or "i-1" caused bugs. It's probably even easier for a
static analyzer to catch a counting-down bug than a counting-up bug, so I'm
not really sure that a preference for counting up is all that helpful.

------
shogun21
Code readability trumps cleverness.

------
iamsohungry
The infinite loop on unsigned integers exemplifies a failure in C's type
system: integer wraparound is so rarely desirable behavior that a range error
on wraparound would prevent many such errors from failing silently.

There's an argument to be made that this check would hurt performance (and I
buy that argument--given C's goals and the time when it was made, the type
system compromises it made make sense). But it would be nice if processor
manufacturers made add/sub instructions that detected wraparound, which would
allow languages to implement such checks performantly.

~~~
scj
The overflow flag is what you're looking for.

~~~
iamsohungry
But checking this flag takes another instruction, no? I'm thinking more along
the lines of some sort of interrupt being thrown automatically if the
operation overflows.

------
cperciva

        for (size_t i = numBurritos - 1; i != -1; i--)
    

This will fail on a platform where sizeof(size_t) < sizeof(int). I admit that
I don't know of any such platform, but I can't see any reason why it wouldn't
be possible.

A better way to write this is

    
    
        for (size_t i = numBurritos - 1; i < numBurritos; i--)

~~~
phkahler
I seriously wonder what's wrong with using an int and the original count down
construct. You have to think about it on 16 or 32 bit platforms, but this
statement from the link:

>> Especially consider the case of an index into a string (char *) on a 64 bit
platform. size_t is the appropriate type for an index because the limited
range of int is not nearly enough.

That's just bull in most applications. Even if you could run 1 trillion
iterations per second, the loop would take 2^23 seconds to complete or about
97 days. In general if you're not doing "big data" and int will index an array
just fine thank you. And that's indexing bytes, it should index any other type
just fine in any case - assuming of course that a pointer and int are the same
size.

~~~
cperciva
Many 64-bit platforms have 32-bit int.

------
SamReidHughes
I've had great success with this solution:

    
    
        for (size_t i = n; i > 0; ) {
            i--;
            ...
        }
    

I'd have gone with "i-- > 0" if I wasn't feeling out the consequences of
trying to avoid all integer overflow, though.

------
alblue
The terminal operation on the clever code compares the size_t to -1. But if
it's unsigned this will never be true either, and so the test may be optimised
away.

This probably demonstrates the point where clever code is more likely to have
bugs ...

~~~
gpvos
Are you really sure? I _think_ the -1 will be coerced to unsigned int, instead
of the other way around. That would make it work.

~~~
knodi123
what does it mean for -1 to be coerced into something unsigned? Wouldn't that
be equivalent to 1?

I, too, fail to understand how this is clever. Can someone enlighten me?

~~~
zhang3r
-1 in 2's complement is actually MAX_INT in unsigned. Since numBurritos is also an unsigned int it would never exceed MAX_INT making this approach work.

~~~
masklinn
Wouldn't it be SIZE_MAX since i is a size_t?

------
tbirdz
Or, just use iterators.

~~~
vog
That comment doesn't make any sense to me. Did I missing something?

There are iterators in C++, but not in C.

~~~
tbirdz
Iterators are a technique and are not bound to any particular programming
language. Iterators abstract the idea of iterating through data. Instead of
keeping track of the array index and thinking of it in terms of counting up or
down, you instead create an iterator that will abstract this for you. You can
create iterators to traverse the array in forward, backwards, every odd
number, whatever way you want. Then you write your client code to operate on
an iterator, and so completely separate the traversal of the sequence from the
action to be performed on each element. Another useful abstraction in this
context are list comprehensions (map, filter, reduce, etc.).

You can create iterators in any language, and I was referring to the abstract
concept of iterators, not the particular form of iterators found in C++'s
standard library.

~~~
TorKlingberg
Sure, you can write iterators in C, but nobody wants to drag all that
complexity just for a reverse loop. In a language like Python of course,
iterators are native and idiomatic.

~~~
tbirdz
The complexity is already there, iterators just help you manage it better.
Even "just a reverse loop" is more complex than you might think, as evidenced
by the article above.

~~~
SamReidHughes
How do they help you manage it better in C?

------
rasz_pl
I wonder if counting down messes up with efficient caching.

