> What I mean by "bend the rules of arithmetic" is that if we decrement from zero, we suddenly get a large value.
Yes completely consistent with rules of modular arithmetic. A programmer ought to be able to extend math horizons beyond preschool. Which is ironic because I can explain this concept to my 6 year old on a clock face and it’s easy for them to grasp.
> Unsigned tricks with circular buffer indices will not do the right thing unless the circular buffer is power-of-two sized.
How will they “not do the right thing?”. With power of 2 you avoid expensive moduli operations, but nothing breaks if you choose to use a non power of 2.
> two's complement
Two’s complement is not even mandated in C. You are invoking implementation defined behavior here. Meanwhile I can just increment or decrement the unsigned value without even masking the retained value and know the result is well defined.
Like I get 2s complement is the overwhelming case, but why be difficult, why not just use the well defined existing mechanism?
And there’s no tricks here, literally just using the fucking type as it was designed and specified, why clutter things with extra masking.
In the N3096 working draft it is written: "The sign representation defined in this document is called two’s complement. Previous revisions of this document
additionally allowed other sign representations."
Non-two's complement machines are museum relics, and are no longer going to be supported by ISO C.
> why clutter things with extra masking.
Because even if the circular buffer is a power of two, its size doesn't necessarily line up with the range of a given unsigned type.
If the buffer doesn't have a width of 256, 65536, or 4294967296, then you're out of luck; you can't just uint8_t, uint16_t or uint32_t as the circular buffer index without masking to the actual power-of-two size.
(Note that uint16_t and uint8_t promote to int (on the overwhelming majority of platforms where their range fits into that type), so you don't get away from reasoning about signed arithmetic for those.)
> If the buffer doesn't have a width of 256, 65536, or 4294967296, then you're out of luck
Why so much hyperbole?
You’re not out of luck. You can atomic increment/add the unsigned no matter the buffer size. You don’t worry about overflow like you would with a signed type. You can mask after.
And you continue to avoid answering the simple question: what is the advantage of the signed type. I’ve already outlined the one with unsigned, especially with atomics.
The main advantage is not foisting unsigned on the user of the API.
(You can do that while using unsigned internally, but then you have to convert back and forth.)
The most important decision is what is the index type at the API level of the circular buffer, not what is inside it. But it's nicer if you can just use the API one inside.
The sizeof operator yielding the type size_t which is unsigned has done a lot of harm. Particularly the way it spread throughout the C library. Why do we have size_t being unsigned? Because on small systems, where we have 16 bit sizes, signed means limiting to 32767 bytes, which is a problem. In all other ways, it's a downer. Whenever you mention sizeof, you have unsigned arithmetic creeping into the calculation.
The author of the above blog article has the right idea to want a sizeof operator that yields ptrdiff_t instead of size_t. (Unfortunately, the execution is bungled; he redefined a language keyword as a macro, and on top of that didn't wrap the macro expansion in parentheses, even.)
> Yes completely consistent with rules of modular arithmetic.
In modular arithmetic, there is no such thing as <. (To put it precisely, ℤ_𝑛 is not an ordered ring.) Or are you teaching your 6-year old that 9:00 today is later than 7:00 tomorrow?
Unsigned arithmetic is useful for wrapping clocks, like interrupt tick counters and whatnot. There is always some current value, "now". There is a range of it defined as the future. Everything outside of that range is considered past. Timers are never set farther into the future beyond the range, and are expired in a timely way so that unexpired timers never recede sufficiently far into the past that they appear to flip to the future. One way of doing it is to just cut the range in half: take the difference between two times t1 - t0 and cast it to the same-sized signed type. If the difference is positive, then t1 is in the future relative to t0. If negative, t1 is in the past relative to t0.
This is one of those niche uses of unsigned.
You probably want to hide it behind an API, where the domain is opaque and abstract and you have function such as a time_before(t1, t0) predicate.
Yes completely consistent with rules of modular arithmetic. A programmer ought to be able to extend math horizons beyond preschool. Which is ironic because I can explain this concept to my 6 year old on a clock face and it’s easy for them to grasp.
> Unsigned tricks with circular buffer indices will not do the right thing unless the circular buffer is power-of-two sized.
How will they “not do the right thing?”. With power of 2 you avoid expensive moduli operations, but nothing breaks if you choose to use a non power of 2.
> two's complement
Two’s complement is not even mandated in C. You are invoking implementation defined behavior here. Meanwhile I can just increment or decrement the unsigned value without even masking the retained value and know the result is well defined.
Like I get 2s complement is the overwhelming case, but why be difficult, why not just use the well defined existing mechanism?
And there’s no tricks here, literally just using the fucking type as it was designed and specified, why clutter things with extra masking.
There’s also the pragmatic atomicity benefit.