Join the discussion
Write your take first — we'll ask for email only when you're ready to publish.
- Hacker News
- You can count down to zero with while(i--)by bogdanoff_2
- One interesting feature of signed vs unsigned in languages where signed overflow is undefined is that signed numbers _behave_ like mathematical integers, whereas unsigned integers (because overflow is valid behavior) _do not_ behave like mathematical integers. For example: if you write a loop that sums numbers from 1 to N, for signed integers the compiler can assume the expression won’t overflow and emit the standard mathematical closed form, because unsigned can overflow it has to account for this and use a different closed form (if one exists for the unsigned case). IMO unsigned should only be used when you want to manipulate the bits of an integer and not the integer itself, but C and C++ fight you when you try to do this because of how ingrained size_t is.by variadix
- The entire reason for integer under/overflow to be undefined is to enable compiler optimisations. If you're going to be using unsigned anyway, we might as well drop the undefined behaviour from the standard and just say it's machine-defined. That should honestly be the correct choice. If a loop is hot enough to benefit from those optimisations, you can easily rewrite it in a form that makes the compiler assume overflow won't happen. Either using current syntax with unreachable(), or we can add a runtime_assume(expr) expression that signals to the compiler that it can assume expr is true. Though for full safety I would prefer using if(likely(expr)) {fast code} else {panic or return error}.
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.
by Asooka - > 0x7ffffffffffffffff. The typical argument is that such a value would be “pathological”. Not only is this argument incorrect, it’s even more dangerous which we will see later.
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.
by Dylan16807 - What if I’m making a 2d game and need to go leftby pikuseru
- Add -1.f, or pick another handedness.by tom_
- Yah, some of us use integer _math_ extensively. It's not just for arrays.by bahmboo
- > Where unsigned does benefit here is when these are used as indices into an array. The signed behavior will almost certainly produce invalid indices which leads to memory unsafety issues. The unsigned way will never do that, it’ll stay bounded, even if the index it produces is actually wrong. This is a much less-severe logic bug, but can still be used maliciously depending on context.
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!
by jgtrosh - > btw. Segmentation faults are your friends!
Indeed. I can't count how often I told people that segmentation faults are among the best kind of errors. One has the complete state and not a log message missing 90% of relevant info.
Of course you don't want processes to crash all the time and spend time on writing gigabytes of data onto disk. But for the rare failure it's a good thing to have.
- The article over-downplays the need for sentinel values. Surely Rust has Option that also spreads into C++ these days as std::optional. But for plain C using -1 or negative values to denote sentinels or error code is rather nice idiom.
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.
by fpoling - Not disagreeing about the need for sentinel values, but Rust also has NonZero. When combined with Option, you get a similar result to outcome to C's -1.
Similar because it's a compiler hack, as in the compiler treats std::num::NonZero specially. You can't create your own type with the same properties as you can in C.
by rstuart4133 - In 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'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
If they are unsigned, then we cannot do this. That is a barrier to refactoring code with arithmetic conditionals and just reasoning about it.a + b < c b < c - a // subtract a from both sides; "bring to other side"by kazinator - 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:
The safety conditions for signed 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 programmers that prefer unsigned arithmetic intuitively feel the greater simplicity compared to signed integers, but without any theorem proving, I agree that your assumption of small integers strongly supports signed integers.Ensure INT_MIN ≤ y+x and y+x ≤ INT_MAX. To avoid overflow or underflow, first compare x to 0. In the case x≤0, INT_MIN-x cannot underflow, and y+x cannot overflow. If y compares greater than INT_MIN-x, then y+x evaluates correctly. In the case 0≤x, then INT_MAX-x cannot overflow, and y+x cannot underflow. And if y compares less than INT_MAX-x, then y+x evaluates correctly. ∀x∀y((x≤0 ∧ INT_MIN-x≤y)∨(0≤x ∧ y≤INT_MAX-x) → ∃z(z = y-x)) Ensure INT_MIN ≤ y-x and y-x ≤ INT_MAX. To avoid overflow or underflow, first compare x to 0. In the case 0≤x, INT_MIN+x cannot underflow, and y-x cannot overflow. If y compares greater than INT_MIN+x, then y-x evaluates correctly. In the case x≤0, INT_MAX+x cannot overflow, and y-x cannot underflow. If y compares less than INT_MAX+x, then y-x evaluates correctly. ∀x∀y((0≤x ∧ INT_MIN-x≤y)∨(x≤0 ∧ y≤INT_MAX+x) → ∃z(z = y-x))by amavect - A long time ago, I also thought one should use unsigned mostly but I am now in the opposite camp. Unsigned integers in C have semantics for modulo arithmetic. They are suitable if you need this, so for crypto, hashes, or if you only care about bits, etc. IMHO they should not be used for anything else. The reason is that it is very easy to screen for signed overflow bugs exactly because they have undefined behavior, just by turning on a sanitizer. It is also possible to transform overflow to safe traps at run-time where this is important. In contrast, finding unsigned wraparound bugs is extremely hard and can not be done automatically and preventing consequences of such bugs is difficult. Also any kind of index computation may have intermediate results that may be negative, so signed arithmetic is also generally more useful and far easier for people to understand and get right.by uecker
- You can also just add -fwrapv.by Chu4eeno
- While I'm a fan of unsigned (size_t mostly) there have been a few times when the tax for converting them to float was shockingly high:
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)
by shaggie76 - That's a GCC skill issue. You can do it in five branchless instructions for unsigned by splitting the unsigned up in two 32-bit halves, converting those to floats simply by inserting their values as mantissa into constants 2^52 and 2^(52 + 32). This conversion is exact.
Then to finish the conversion you subtract 2^52 and 2^(52 + 32) respectively from the halves and add them together.
Here CONST1 = [0x43300000, 0x45300000, 0, 0] and CONST2 = [0, 0x43300000, 0, 0x45300000].vmovq xmm0, rdi vpunpckldq xmm0, xmm0, xmmword ptr [rip + .CONST1] vsubpd xmm0, xmm0, xmmword ptr [rip + .CONST2] vshufpd xmm1, xmm0, xmm0, 1 vaddsd xmm0, xmm1, xmm0by orlp - Stroustrup recommends int over unsigned. Dijkstra recommends int over unsigned. Google coding guidelines recommend int over unsigned.
Blogger recommends unsigned over int.
Tough choice.
by dataflown - signed overflow (or underflow) is frequently undefined behavior. (often because it's undefined in C)
unsigned is frequently defined. (often because it's defined in C)
tough choice.
(honestly I just lean towards "over/underflow should raise unless explicitly allowed", the ratio of unintended to intended-and-fully-checked overflow behavior is almost certainly FAR beyond 100:1)
by Groxx - Gosling went one step further unsigned isn't even available, although nowadays there are helper classes for doing unsigned arithmetic.by pjmlp
- ClickHouse code style recommends unsigned in every case when you don't need the sign: https://clickhouse.com/docs/development/style
- Commenter implies authority dictates strategy.
We should be engaging with the article's content.
- 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#t=42m40s
- 1:02:50-1:03:15 - https://www.youtube.com/watch?v=Puio5dly9N8#t=1h2m50s
- I 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 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.
- > I 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.
That’s why Swift completely removed that kind of for loop (https://github.com/swiftlang/swift-evolution/blob/main/propo...)
> This stuff compiles to broadly the same assembly.
At the cost of requiring a more complex compiler. Also, C/C++ won’t find “broadly the same” sufficient. They’ll want to see the same performance.
by Someone - 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 rule of thumb is to always use signed integers, except for bit twiddling and modulo-math.
Especially with 64-bit integers it's really no longer an issue to lose one bit for the sign (63 bits ought to be enough for anybody heh).
by flohofwoe