Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
This is a bit of a well-known problem when converting from an up-counting to a down-counting loop and using an unsigned loop iterator. Since unsigned numbers are always positive and have well-defi...
Answer
#2: Post edited
- This is a bit of a well-known problem when converting from an up-counting to a down-counting loop and using an unsigned loop iterator.
- Since unsigned numbers are always positive and have well-defined wrap-around when going below zero, `i >= 0` will always be true for an unsigned loop iterator. Compilers often warn against this.
- It has nothing to do with `for` vs `while`, the comparison expressions used are simply not equivalent. `while(i--)` is the same as `while(i-- != 0)` so it performs no range check of the value with relative operators, only an equivalence check. The equivalent for loop would be
- ```c
- for(int i=16; i--; )
- ```
- Which is strange and bad practice but valid C.
- As for how to fix it, one solution I often recommend is _don't_ write a down-counting loop. The best and most readable loop we can write is the idiomatic:
- ```c
- for(int i=0; i<n; i++)
- ```
- The further we divert from that form, the harder the loop is to read and bugs become more probable. We can actually write your example with an up-counting loop:
- for(int i=0; i<n; i++)
- {
- printf("%"PRIu64" ", n-i-1);
- }
By subtracting the loop iterator from the upper limit and then an additional `-1`, we can achieve the same backwards counting. The loop body looks a bit stranger but now it actually doesn't matter if we use a signed or unsigned loop iterator. So it has the pretty nice advantage that it actually works :)- Alternatively, if we insist on using down-counting with an unsigned iterator, there's not really a pretty way to do it either:
- - We could increase the starting value by 1 and change the condition `i>0`. This might mean we have to adjust the loop body too, since indices end up off by 1.
- - We could leave out the for loop condition and add a `if(i==0) break;` at the end of the loop.
- - We could let the iterator deliberately wrap-around and write a strange loop condition like
- `i != UINT64_MAX` or `i != (uint64_t)-1`, which looks plain weird.
- - We could cast the iterator `(int64_t)i >= 0` which is somewhat more readable but introduces implementation-defined behavior to the code.
- So the best solution here probably is to drop one of the requirements: either don't write down-counting loop or don't use an unsigned loop iterator.
- ---
- Side note:
- Note that down-counting loops are _not_ faster. It used to be like that back in the days when compilers were horrible at optimization. Namely because an assembler instruction "branch if zero" is usually a few ticks faster than "branch if lower than n" or similar checks that uses a comparison against a number.
- However, all modern compilers are well-aware of this and can turn a loop down-counting in the generated machine code if that would make the code faster. So writing down-counting loops on purpose would be a school book example of "premature optimization" these days.
- This is a bit of a well-known problem when converting from an up-counting to a down-counting loop and using an unsigned loop iterator.
- Since unsigned numbers are always positive and have well-defined wrap-around when going below zero, `i >= 0` will always be true for an unsigned loop iterator. Compilers often warn against this.
- It has nothing to do with `for` vs `while`, the comparison expressions used are simply not equivalent. `while(i--)` is the same as `while(i-- != 0)` so it performs no range check of the value with relative operators, only an equivalence check. The equivalent for loop would be
- ```c
- for(int i=16; i--; )
- ```
- Which is strange and bad practice but valid C.
- As for how to fix it, one solution I often recommend is _don't_ write a down-counting loop. The best and most readable loop we can write is the idiomatic:
- ```c
- for(int i=0; i<n; i++)
- ```
- The further we divert from that form, the harder the loop is to read and bugs become more probable. We can actually write your example with an up-counting loop:
- for(int i=0; i<n; i++)
- {
- printf("%"PRIu64" ", n-i-1);
- }
- By subtracting the loop iterator from the upper limit and then an additional `-1`, we can achieve the same backwards counting. The loop body looks a bit stranger but now it actually doesn't matter if we use a signed or unsigned loop iterator. And it has the pretty nice advantage that it actually works :)
- Alternatively, if we insist on using down-counting with an unsigned iterator, there's not really a pretty way to do it either:
- - We could increase the starting value by 1 and change the condition `i>0`. This might mean we have to adjust the loop body too, since indices end up off by 1.
- - We could leave out the for loop condition and add a `if(i==0) break;` at the end of the loop.
- - We could let the iterator deliberately wrap-around and write a strange loop condition like
- `i != UINT64_MAX` or `i != (uint64_t)-1`, which looks plain weird.
- - We could cast the iterator `(int64_t)i >= 0` which is somewhat more readable but introduces implementation-defined behavior to the code.
- So the best solution here probably is to drop one of the requirements: either don't write down-counting loop or don't use an unsigned loop iterator.
- ---
- Side note:
- Note that down-counting loops are _not_ faster. It used to be like that back in the days when compilers were horrible at optimization. Namely because an assembler instruction "branch if zero" is usually a few ticks faster than "branch if lower than n" or similar checks that uses a comparison against a number.
- However, all modern compilers are well-aware of this and can turn a loop down-counting in the generated machine code if that would make the code faster. So writing down-counting loops on purpose would be a school book example of "premature optimization" these days.
#1: Initial revision
This is a bit of a well-known problem when converting from an up-counting to a down-counting loop and using an unsigned loop iterator. Since unsigned numbers are always positive and have well-defined wrap-around when going below zero, `i >= 0` will always be true for an unsigned loop iterator. Compilers often warn against this. It has nothing to do with `for` vs `while`, the comparison expressions used are simply not equivalent. `while(i--)` is the same as `while(i-- != 0)` so it performs no range check of the value with relative operators, only an equivalence check. The equivalent for loop would be ```c for(int i=16; i--; ) ``` Which is strange and bad practice but valid C. As for how to fix it, one solution I often recommend is _don't_ write a down-counting loop. The best and most readable loop we can write is the idiomatic: ```c for(int i=0; i<n; i++) ``` The further we divert from that form, the harder the loop is to read and bugs become more probable. We can actually write your example with an up-counting loop: for(int i=0; i<n; i++) { printf("%"PRIu64" ", n-i-1); } By subtracting the loop iterator from the upper limit and then an additional `-1`, we can achieve the same backwards counting. The loop body looks a bit stranger but now it actually doesn't matter if we use a signed or unsigned loop iterator. So it has the pretty nice advantage that it actually works :) Alternatively, if we insist on using down-counting with an unsigned iterator, there's not really a pretty way to do it either: - We could increase the starting value by 1 and change the condition `i>0`. This might mean we have to adjust the loop body too, since indices end up off by 1. - We could leave out the for loop condition and add a `if(i==0) break;` at the end of the loop. - We could let the iterator deliberately wrap-around and write a strange loop condition like `i != UINT64_MAX` or `i != (uint64_t)-1`, which looks plain weird. - We could cast the iterator `(int64_t)i >= 0` which is somewhat more readable but introduces implementation-defined behavior to the code. So the best solution here probably is to drop one of the requirements: either don't write down-counting loop or don't use an unsigned loop iterator. --- Side note: Note that down-counting loops are _not_ faster. It used to be like that back in the days when compilers were horrible at optimization. Namely because an assembler instruction "branch if zero" is usually a few ticks faster than "branch if lower than n" or similar checks that uses a comparison against a number. However, all modern compilers are well-aware of this and can turn a loop down-counting in the generated machine code if that would make the code faster. So writing down-counting loops on purpose would be a school book example of "premature optimization" these days.