I almost never write a raw for-i loop any more, especially since 99% of the time I want to enumerate through the entire array or vector, and I can just use a ranged for-loop to do that. It allows me to redesign my code around the data, express things at a higher level, and my code looks far more SIMDable and reminiscent of array programming languages. And yet it is safer, I will never see under/overflow or any of these old-hat problems.
If you are using C, then too bad, you're stuck with a language that doesn't allow the programmer to more meaningfully and more clearly express intent at a higher level of abstraction without paying additional runtime costs.
This stuff compiles to broadly the same assembly.
Blogger recommends unsigned over int.
Tough choice.
https://godbolt.org/z/96T4jTshc
1-2 instructions for signed vs 11 including a branch for unsigned.
(in times like these I found casting to signed first preferable)
Whether unsigned or signed is better is a matter that is a combination of personal opinion and the quirks of a given systems programming fixed integer language.
The trade-off reasoning would be different, for instance, in a language that requires implementations to provide two's complement signed integers, with wraparound semantics. Or, say, no wraparound semantics but a robust overflow detection system coupled to exception handling.
There are other matters beside overflow, like conversions. In C, mixtures of signed and unsigned bring in some implementation-defined conversion rules, which nudges the argument toward "all unsigned" or "all signed" for the sake of avoiding mixtures.
I like to trot out the following argument.
Suppose a, b and c are small integers close enough to zero that any additive/subtractive combination of them is free of overflow.
If they are signed, then we can make inequality derivations like
a + b < c
b < c - a // subtract a from both sides; "bring to other side"
If they are unsigned, then we cannot do this. That is a barrier to refactoring code with arithmetic conditionals and just reasoning about it.The argument will be more valid if array indexes will be 1-based like in Fortran/Matlab/Julia as then 0 becomes extremely nice sentinel values. But C is C and needs -1.
The argument for signed often goes that it's easier to detect an invalid operation on an index by checking for bounds, or the higher probability of segmentation faults, than if your index is always “valid”. Granted, even with signed your invalid operation might still give a valid index. Simply put, seeing a negative index in your debugger is an obvious red flag you lose with unsigned.
In general I want to complain about the idea that a subtle bug is less severe than an obvious bug. Obvious bugs can be caught automatically or manually and are therefore less severe than subtle ones. This is a mistakes all students do btw. Segmentation faults are your friends!
The only later thing I see that's somewhat relevant to that appears to be dealing with 32 bit overflow? That doesn't prove the argument incorrect.
No current OS I'm aware of lets you have a size_t that goes over 2^63. I doubt any OS will ever allow it. It makes things easier if virtual memory is capped to 2^62 or so, and if anyone really ends up with a use case for more I expect them to switch to 128 bit numbers.
using int64 = int64_t;
using nat64 = uint64_t;
There are no unsigned integers. Naturals, folks, naturals have no sign.Or, if you must, the positive integers:
using pos64 = uint64_t;As for why signed is default, this may have to do with the error handling in C. By convention, if a called function hits a failure mode, it returns a negative integer. If a function succeeds but with a caveat or warning, it returns a positive integer. Unqualified success returns zero. Hence why idiomatic C functions typically return int, not unsigned int, and we've been stuck on this convention ever since.
It’s pretty sad that after all these years, dealing with fixed size integers is still so complicated. Yes, many of the problems are specific to low level languages with undefined behaviour and numeric for loops. But the issue of subtracting two numbers and possibly having an underflow is both common and a bit absurd.
The code in the article for “safely” calculating the difference of two unsigned numbers, which is simpler than the equivalent for signed integers, is this little ritual:
> delta = max(x, y) - min(x, y);
Seriously??? Two function calls just for the difference of two numbers??
Why can’t such “safe” operations have some of the sweet syntactic sugar, and the underflow-rampant “ordinary” operations have the bitter medicine of ritual?
Erm... just because you can, doesn't mean you should.
Also, what if you want to go down to something other than 0?
Not only, most people don't know the rules even.
> For me as a language designer, which I don't really count myself as these days, what "simple" really ended up meaning was could I expect J. Random Developer to hold the spec in his head. That definition says that, for instance, Java isn't -- and in fact a lot of these languages end up with a lot of corner cases, things that nobody really understands. Quiz any C developer about unsigned, and pretty soon you discover that almost no C developers actually understand what goes on with unsigned, what unsigned arithmetic is. Things like that made C complex. The language part of Java is, I think, pretty simple. The libraries you have to look up.
Taken from http://www.gotw.ca/publications/c_family_interview.htm
As an aside, unsigned does not save you from undefined behaviour. When sizeof(short)==2 and sizeof(int)==4 (e.g. x86, x64, arm32, arm64), then multiplying two unsigned short values happens by upcasting them to ints (see integer promotion rules), which can overflow the int.
My personal opinion is that along with making signed overflow defined, unsigned integers should be entirely removed as a type and there should instead be separate signed vs unsigned operators, because at the processor level there is no difference between the two, and there hasn't been a good case to separate them at the hardware level for the last ~half century. Basically, do what Java does with some syntax like unsigned{expr} which forces all integers inside expression to be treated as unsigned. Unsigned literals can stay, but they will be bitcast to signed equivalents if used outside unsigned context.
> for instance C and C++ leave signed integer wrap undefined
I'm pretty sure the way to write what was meant here is "C and C++ leave signed integer overflow undefined". Wrapping would be a definite choice. Overflow is the situation we're considering, not a particular outcome to choose so that is what's undefined.
The confusion gets worse later when it insists that just like in C or C++ these three Rust expressions will produce invalid results because of LLVM:
x / 0
INT_MIN / -1
INT_MAX % -1
INT_MAX - INT_MIN
Assuming we defined INT_MAX and INT_MIN as say i32::MAX and i32::MIN (or whichever signed type you prefer) of course what these actually do in Rust is just panic. If you write this in a context where it'll be evaluated at compile time, your compilation fails. That's not "invalid" in any sense I understand.It mentions Odin too, I know less about Odin but I believe it too will reject this nonsense, on Godbolt it seems to either SIGILL (for zero) or SIGFPE (for other impossible operations)
Edited: Apparently I copy-pasted wrong? Some of those invalid expressions were not as written on the blog post but are now hopefully fixed. They are, of course, still not invalid in Rust, some panic because they aren't valid questions (like dividing by zero), others are fine - neither case is a problem.