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;
Almost Always Unsigned
81–90 of 103 posts
Re: Almost Always Unsigned
#82In a language that has arbitrary precision integers, you'd pretty much never want them unsigned, or even to have signed and unsigned flavors. 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'…
I believe the main issue lies in most programming languages lacking theorem proving capabilities to prove the safety of integer operations. The safety conditions for unsigned arithmetic: Ensure y+x ≤ INT_MAX. If x ≤ UINT_MAX-y, then x+y evaluates correctly: ∀x∀y(x ≤ UINT_MAX-y → ∃z(z = y+x)) Ensure y-x ≤ INT_MAX. If x≤y, then y-x evaluates correctly: ∀x∀y(x≤y → ∃z(z = y-x)) The safety conditions for signed arithmetic…
Ensure y+x ≤ INT_MAX.
Is this supposed to be a precondition? Why would I want this precondition when using unsigned arithmetic?Re: Almost Always Unsigned
#83I have a better solution: address the root cause of unsafe semantics by not using raw indexed for-loops, unless one absolutely needs an index, in which case one should generate it with std::views::enumerate. To reverse it, use std::views::enumerate | std::views::reverse. Ditto for languages with similar semantics. I almost never write a raw for-i loop any more, especially since 99% of the time I want to enumerate thr…
Loop counters really are not the problem. The problem is that even when using unsigned integers you often want to do 'signed math' on them (e.g. adding a negative amount, or you could have an expression made entirely of unsigned integers (like ((x - y) + z) where an intermediate result may become negative even when the end result is positive - and in languages with overflow check that may result in a panic). A better…
[0] https://www.boost.org/doc/libs/develop/libs/safe_numerics/do...
[1] https://github.com/duneroadrunner/SaferCPlusPlus/blob/master...
Re: Almost Always Unsigned
#84> for (size_t i = size - 1; i Erm... just because you can, doesn't mean you should. Also, what if you want to go down to something other than 0?
(Of course the best thing is real range types like in Rust.)
Re: Almost Always Unsigned
#85Re: Almost Always Unsigned
#86There was a golang proposal to change Go's default int type to arbitrary precision big int. It would avoid overflow bugs, but the proposal was closed (after eight years) due to concerns about compatibility reading serialized data and performance. https://github.com/golang/go/issues/19623
But I'm not sure it makes sense in a language like Go where they actually care about performance.
Re: Almost Always Unsigned
#87Earlier quoted context omitted.
I liked how the discussion of 'delta = x - y' moved right on to how really you usually want delta = abs(x - y), so let's talk about that instead... Even beyond Stroustrup, Dijkstra, and Google, this whole panel of C++ luminaries agrees to prefer signed types and explains pretty clearly why: - 12:12-13:08 - https://www.youtube.com/watch?v=Puio5dly9N8#t=12m12s - 42:40-45:26 - https://www.youtube.com/watch?v=Puio5dly9N8…
Thanks for the excerpts! I was trying to understand the reasoning, which seem to just be in the 2nd excerpt: - The rules of signed/unsigned are complicated and there is too much auto-conversion - does that mean languages that make this more explicit means this is fine? It just seems ideal to have stronger typing. - It is mentioned that you can initialized an unsigned int to "-2" - but that presumably could also be fi…
To achieve high performance, any language would need to implement integer addition with a single machine instruction like 'ADD'. Languages can achieve more intuitive behavior by adding an operand check before the 'ADD', or by using an 'ADC' instruction and checking the carry bit afterwards. But adding branch statements to every add operation would slow computation significantly. Clever languages/compilers might deduce certain invariants and variable ranges that enable it to remove some of these branches to help in certain special cases.
Evidently Rust has an optional "safe add" that adds the branches to check for overflow. So newer languages offer more explicit options. But the core issue is more fundamental than language-specific.
Re: Almost Always Unsigned
#88Earlier quoted context omitted.
Loop counters really are not the problem. The problem is that even when using unsigned integers you often want to do 'signed math' on them (e.g. adding a negative amount, or you could have an expression made entirely of unsigned integers (like ((x - y) + z) where an intermediate result may become negative even when the end result is positive - and in languages with overflow check that may result in a panic). A better…
But are we necessarily limited to native integer types? At least with C++, the type system is powerful enough to support integer replacement types (eg. [0][1]) that don't inherit these issues. (Where, for example, the subtraction of an unsigned from another unsigned returns a value of signed integer type.) Another advantage being the ability to customize the overflow/underflow handling policy per declaration (rather…
Beyond that, the decision whether a number is signed or unsigned is only needed for string formatting (e.g. it's like Schroedingers integers: whether a number is signed or unsigned only matters when you actually look at the number).
Re: Almost Always Unsigned
#89Re: Almost Always Unsigned
#90Earlier quoted context omitted.
Post-increment inverts to pre-decrement, but for-loops don't support proper syntax sugar for pre-decrement. for(size_t i = 0; i 0;){ i--; // loop body }
Put the "--" in the condition. It's less ugly.
size_t i = size;
while(i-- > 0){
// loop body, possibly break
}
use(i); // i wraps below 0
size_t i = size;
while(i > 0){
i--;
// loop body, possibly break
}
use(i); // i doesn't wrap
Practically, i leaves the for-loop scope, so most never encounter this problem.