[HN Gopher] Almost Always Unsigned
       ___________________________________________________________________
        
       Almost Always Unsigned
        
       Author : todsacerdoti
       Score  : 145 points
       Date   : 2022-01-02 07:35 UTC (15 hours ago)
        
 (HTM) web link (graphitemaster.github.io)
 (TXT) w3m dump (graphitemaster.github.io)
        
       | happyweasel wrote:
       | Might be of interest:
       | https://www.youtube.com/watch?v=Puio5dly9N8&t=587s IMHO bjarne's
       | comment sums it up nicely ...
       | 
       | "Use ints until you have a reason not to. Don't use unsigned
       | unless you are fiddling with bit patterns, and never mix signed
       | and unsigned.."
        
         | garaetjjte wrote:
         | Eh, but C++ std likes to use unsigned size_t for sizes...
        
       | Slartie wrote:
       | Tangentially related:
       | 
       | https://docs.microsoft.com/en-us/answers/questions/680467/ex...
       | 
       | Had Microsoft used an unsigned int to store the date - which
       | can't ever be negative in this notation - Exchange wouldn't have
       | stopped working worldwide on 1/1/2022. They would have been safe
       | for another two decades, in which they would hopefully had either
       | gotten rid of that weird code or switched it over to 64 bits.
        
         | masklinn wrote:
         | > They would have been safe for another two decades, in which
         | they would hopefully had either gotten rid of that weird code
         | or switched it over to 64 bits.
         | 
         | Why? The code would have kept working all along, there would
         | have been no reason to explore it, and lots of other issues to
         | chase.
        
         | sparkie wrote:
         | Had Microsoft used common sense, they would not store the
         | string representation of a date as an integer, period.
        
           | jeroenhd wrote:
           | It depends on the use case. If this is an index that's
           | commonly sorted by and enumerated, the performance impact of
           | a string compare versus a simple 64 bit integer compare would
           | be enormous. A single compare instruction versus a series of
           | unicode character comparison operations with subsequent jump
           | instructions would not only be more code, it would also be
           | more likely to wreck the branch predictor of the CPU with all
           | the added conditionals.
           | 
           | I think using 64 bit integers for their natural hardware-
           | accelerated sortability is quite a neat trick. You just have
           | to be sure to use actual 64 bit numbers, and you have to
           | think ahead to check if your data type is wide enough to
           | actually contain your data.
        
       | beeforpork wrote:
       | I agree that signed is generally inferior and less often what you
       | really need. For me, in C and C++, the main arguments for
       | 'unsigned by default' are:
       | 
       | (1) integer overflow/underflow is undefined behaviour. You don't
       | want that, it's evil. Unsigned does not have that problem (often
       | it is still a bug if you over- or underflow -- but at least you
       | get sane code from the compiler).
       | 
       | (2) size_t is unsigned and is returned by sizeof() and is the
       | argument to memcpy(), strcpy(), etc., and with -Wconversion, you
       | do not want to handle all the warnings when mixing this with
       | signed loop counters or size variables.
       | 
       | Unfortunately, one problem with using unsigned by default in C is
       | that ptr1-ptr2 yields ptrdiff_t, which is signed. Which is bad,
       | in my opinion, because I usually want to find an array size or
       | index when I subtract two pointers, so I want size_t, and in
       | these cases, I always know which pointer is larger.
       | 
       | Oh well, it's C.
        
       | MaxBarraclough wrote:
       | > almost all code that uses signed integers to represent values
       | that will never be negative, tends to have a cacophony of range
       | assertions and other tests which are just as error-prone as
       | bounds checking to remembering to write, but also maintain when
       | refactoring. It's truly underappreciated how much those tests can
       | be eliminated if your integer can never actually become negative
       | due to the type system itself.
       | 
       | This is something the Ada language gets right: you define your
       | integer type with a range, and the compiler automagically inserts
       | the runtime range checks. (Unless you switch them off with
       | compiler directives.) It makes far more sense to bake the range-
       | checks into the type than to do it the manual way and just hope
       | you don't miss anything.
       | 
       | You can do this in C++ using a library (thanks to templates) [0]
       | but this is very rarely done.
       | 
       | [0]
       | https://www.boost.org/doc/libs/1_78_0/libs/safe_numerics/doc...
        
         | lifthrasiir wrote:
         | Range types are not a panacea, especially combined with other
         | arithmetic operators. There are essentially two options: [lo1,
         | hi1) + [lo2, hi2) can result in a new type [lo1 + lo2, hi1 +
         | hi2) and so on, or any operation reverts back to the base type.
         | Only the former provides a static type guarantee but can be
         | extremely annoying to use and you eventually have to constrain
         | the type back with runtime checks. (Interval arithmetic also
         | shares this problem.) Probably for this reason, to my
         | knowledge, both Pascal and Ada implements the latter, which is
         | more usable but also arguably useless: if your intermediate
         | type is ranged you have to manually calculate correct bounds (a
         | failure to do so will cause a runtime error), and if it's
         | unranged there is zero guarantee even in the run time.
        
           | MaxBarraclough wrote:
           | Somewhat related: there's apparently a defect in the Ada
           | language spec which permits reordering of arithmetic
           | operations such that out-of-range behaviour may hinge on the
           | compiler's whim: [0]
           | 
           | > non-parenthesized arithmetic operations could be re-ordered
           | by the compiler, which may result in a failing computation
           | (due to overflow checking) becoming a successful one, and
           | vice-versa. By default, GNATprove evaluates all expressions
           | left-to-right, like GNAT.
           | 
           | Presumably one solution would be to use a three-address-code
           | style [2] to completely tie the compiler's hands regarding
           | ordering, but this seems painfully restrictive even by the
           | standards of SPARK.
           | 
           | Also, an Ada compiler's internal choice of base type (with
           | which to implement a range-based integer type) can impact
           | overflow behaviour: [1]
           | 
           | > The choice of base types influences in which cases
           | intermediate overflows may be raised during computation. The
           | choice made in GNATprove is the strictest one among existing
           | compilers, as far as we know, which ensures that GNATprove's
           | analysis detects a superset of the overflows that may occur
           | at run time.
           | 
           | I don't know if there's a fully portable robust answer to
           | this second problem. (I believe you can generally use hints
           | to force the Ada compiler to use a particular sized type,
           | along the lines of C's uint32_t, but that this isn't
           | portable.)
           | 
           |  _edit_ On second thought, I imagine using a three-address-
           | code style would solve this too. Either the result falls
           | within the permitted range of the destination variable, or it
           | doesn 't.
           | 
           | [0] https://docs.adacore.com/spark2014-docs/html/ug/en/append
           | ix/...
           | 
           | [1] https://docs.adacore.com/spark2014-docs/html/ug/en/append
           | ix/...
           | 
           | [2] https://en.wikipedia.org/wiki/Three-address_code
        
         | pyjarrett wrote:
         | Ada provides integers and floats with range checks, and you can
         | specify the underlying size, but intermediate values don't get
         | checked. It also has a separate form of unsigned integers
         | called "modular types" which expressly indicate wrapping, if
         | that's your intent.                   -- Range checked
         | equivalent of uint64_t;         type Nibble is range 0 .. 15
         | with Size => 64;              -- Every assignment is as if
         | Value := (Value % 16);         type Wrapped_Nibble is mod 16;
         | type Small_Float is digits 4 range 0.0 .. 20.0 with Size => 32;
         | 
         | I agree that the subexpression issue is problematic, IIRC I
         | thought this was checked in SPARK code, but I could be wrong.
         | The big benefit is describing your intent and you know that the
         | stored value is in range, though whether it went outside of
         | that range in calculation might not be known. Another thing is
         | that these types define 'First, 'Last, 'Pred, 'Succ, and 'Range
         | attributes which you can use.                   for N of
         | Nibble'Range loop             -- do something         end loop;
        
           | MaxBarraclough wrote:
           | > I thought this was checked in SPARK code, but I could be
           | wrong
           | 
           | I believe that's correct, see my other comment. [0] Does
           | still feel a bit ugly though.
           | 
           | [0] https://news.ycombinator.com/item?id=29768848
        
       | Diggsey wrote:
       | > Here's a somewhat non-exhaustive list of all the undefined
       | behavior of signed integer arithmetic in LLVM which applies to
       | all languages which use LLVM:
       | 
       | > These are certain to produce invalid results in languages like
       | Go, Rust, and Odin.
       | 
       | This is clearly untrue, since an operation (or indeed a type) at
       | the language level need not translate to the same operation/type
       | within LLVM.
       | 
       | For example, none of these are "undefined behaviour" in Rust.
        
       | iddan wrote:
       | I would have really loved to have different number types in
       | JavaScript and to be able to define them with TypeScript. Not
       | from the performance perspective (like different sizes) but
       | integer vs float and unsigned vs signed can be really useful
        
       | davidfstr wrote:
       | For languages that don't give you a choice of signed vs. unsigned
       | - giving you signed and sometimes arbitrary-precision integers -
       | this debate is irrelevant.
       | 
       | I'll put it in my same mental bucket of "things that I no longer
       | worry about", along with buffer overflows, wild pointers, etc
       | that are eliminated by memory-safe languages.
        
       | int_19h wrote:
       | The problem isn't that signed or unsigned is the accepted
       | default. The problem is that bounded integers are the default,
       | and worse yet, that their behavior at the edges is counter-
       | intuitive from a common sense perspective.
       | 
       | This was perfectly reasonable back when RAM was measured in
       | kilobytes, and clock speed in megahertz. But these days, we write
       | apps in HTML and JS, and package them with a browser to run. And,
       | conversely, security is much more of a problem than it was
       | decades ago - and integer overflow is a very common cause of
       | security issues.
       | 
       | So, why aren't unbounded integers becoming the norm? I'm not
       | saying we should throw int32 etc out, but rather treat them as
       | low-level optimization tools - much like we treat, say, raw
       | pointers in C# or Rust. Surely the default should be safety over
       | performance?
        
       | captainmuon wrote:
       | I wonder, is there any language that automatically promotes ints
       | to more bits to ensure there is no overflow? The sum of two
       | eight-bit ints is a nine-bit int (probably occupying 16 bits in
       | memory, but that is an implementation detail):
       | uint8 a = 230;         uint8 b = 250;         auto c = a + b;  //
       | typeof(c) == uint9
       | 
       | Maybe it would even be able to detect "c / 2" fits in an uint8.
       | 
       | I guess this could lead to an explosion of bits required, but
       | only if you chain multiple operations and use type inference. If
       | you explicitly assign to a fixed-width integer, it would
       | truncate.
       | 
       | I know Python used bignums under the hood, but is there any
       | compiled and/or strictly typed language that does this? Because
       | it seems modulo arithmethic and silent under/overflow is most of
       | the time not what you want.
        
         | ncmncm wrote:
         | That is generally not possible in a strongly-typed language
         | unless you just use a bignum type to begin with.
         | 
         | Most numbers are small, and you aren't worried about overflow.
         | 
         | With big numbers, std::int64_t is hard to overflow. If you have
         | numbers from an untrusted source, you can and should range-
         | check them immediately on input.
        
       | halayli wrote:
       | What's more important than preaching about signed vs unsigned is
       | raising awareness not to use both.
       | 
       | Most signed/unsigned related bugs (outside numerical/math
       | sensitive domain) are due to using both simultaneously and the
       | side effect is of course going to be overflow/underflow.
       | 
       | On the other hand, I'd favor int over unsigned as it's more
       | practical when dealing with memory address arithmetic & array
       | indices due to address difference being signed.
        
       | jeffreyrogers wrote:
       | I sometimes use signed ints to indicate an error condition. Then
       | you know if the result is < 0 some error occurred, otherwise you
       | can continue with normal processing.
        
       | upofadown wrote:
       | This is an artifact of typing. Is your 16 bit value all ones?
       | Then it might represent -1 or 65535 depending on what you believe
       | the type to be. Perhaps it represents some sort of colour
       | instead.
       | 
       | When working in an untyped environment, this sort of question
       | doesn't come up. If the value is intrinsically unsigned then that
       | is what you and hence the program assume those bits mean. If the
       | value is intrinsically signed then that is your illusion. Any
       | ideas about overflow are entirely in your mind. You tend to see
       | techniques used in an untyped environment that would never occur
       | to anyone in a signed environment. It's all just bits.
        
       | PaulDavisThe1st wrote:
       | I haven't seen anyone mention the main reason I end up using
       | signed: to allow for an "out-of-bounds" value to indicate "value
       | not set". As long as the N-1 bit version of the integer is large
       | enough, not having to use an additional variable to indicate "var
       | not set" is really quite lovely. One could potentially replace
       | the use of a negative sentinel value with a maximum sentinel
       | value, and I've done this on occasion too (i.e. if (var == 2^N-1)
       | var-is-not-set). But the cognitive distinction between negative
       | == not set, positive == valid value is easier to deal with (for
       | me, at least).
        
         | ncmncm wrote:
         | This the proper place for std::optional<T>.
        
           | PaulDavisThe1st wrote:
           | that has quite the overhead for something that can be done
           | using a single bit of the actual value. roughly half the
           | programming i do is high-performance real-time constrained.
           | not really appropriate in that context.
        
       | the__alchemist wrote:
       | Choose the right tool for the job. Examples:                 -
       | Loop using an iterator: unsigned pointer size       - Array
       | index: unsigned pointer size       - Enum used to represent a
       | register value that's 8 bits or less: unsigned 8 bit       - ...
       | used to represent a register value that's up to 16 or 32 bits:
       | unsigned 16 or 32 bit number       - Integer value that could be
       | negative: Signed, with the appropriate size       - Doing
       | numerical operations and you have an FPU: Float. Unless you're
       | careful, you know what you're doing, and it's either performance
       | critical or you're using dedicated a dedicated fixed-pointer
       | hardware peiph... then maybe fixed point with a signed or
       | unsigned integer.       - Analog value that can only be positive;
       | maybe a voltage: unsigned integer according to precision of the
       | ADC etc       - Analog value that can be positive or negative;
       | maybe pH, audio, or something using a differential ADC: signed
       | integer       - Performing mathematical operations including
       | subtraction, don't expect fixed-point errors to come up, and you
       | don't want to use floating point or don't have an FPU: signed
       | integer
        
       | cjbgkagh wrote:
       | I mostly use signed... but one of my primary activities is to use
       | uint8, uint16, and uint32 for indexes and indexes into indexes
       | etc. The workflow was designed around the various uint size
       | limitations so that they are not exceeded. Uint32 is the largest
       | chunk size that I use. The fact that it is twice as big as an
       | Int32 is handy. It allows me to pass bigger chunks to the GPU.
       | Plus I'm already working with uint8 and uint16 so it's
       | consistent. I've not needed Uint64 or Int64 but it would vastly
       | increase memory size for no benefit for my use case. Memory is
       | cheap but I'm bottlenecked by memory bandwidth. I've looked into
       | using variable ints but I haven't needed them yet. For things
       | that I know I'm going to scan over having compressed variable
       | ints of a diffed sorted bag would be great but I would need to
       | write a ton of custom CPU and GPU code for it. You can index into
       | variable ints with a post process provided a maximum number
       | constraint and some padding on the end.
       | 
       | This sort of code is written very carefully and mistakes give
       | completely wrong answers so they are noticed right away.
        
       | flohofwoe wrote:
       | I'm in the "almost always signed camp" - at least in languages
       | without robust over-/underflow checking.
       | 
       | But I sometimes wonder if it would have been better to not carry
       | the 'signedness' in the variable type in high level languages,
       | but instead treat integer variables as 'signless' bags of bits,
       | and signed-vs-unsigned are just different views on those bags of
       | bits, just as in assembly.
       | 
       | Most operations on two's-complement numbers result in the exact
       | same bit pattern, no matter if the involved operands were
       | 'signed' or 'unsigned', and the signedness only matters when
       | printing the result, or for explicitely extending the sign bit
       | when casting to a wider type.
        
         | spc476 wrote:
         | Because when C was standardized in 1989, there were a few
         | vendors that wanted to support non-two's-complement hardware,
         | and C has been stuck with that ever since. But more to the
         | point, even on two's-complement hardware, the CMP instruction
         | doesn't care about the signedness, but it typically sets flags
         | (for those CPUs that have flags) for both signed _and_ unsigned
         | results, and there are different conditional branches one can
         | take for less-than, less-or-equal, greater-or-equal, and
         | greater-than.
        
           | flohofwoe wrote:
           | I often wished I had access to the flag bits in C (or at
           | least a somewhat portable subset). Would be very handy for
           | writing CPU emulators :)
        
             | spc476 wrote:
             | Having written a CPU emulator [1], I don't thunk it would
             | have been of much use. The Intel CPUs typically don't set
             | the flags on a MOV, but Motorola CPUs typically do.
             | 
             | [1] https://github.com/spc476/mc6809
        
       | kazinator wrote:
       | This is an misguided and dangerous article.
       | 
       | Suppose that a, b and c are small values close to zero and so can
       | be combined additively/subtractively in any combination without
       | overflow. If they are signed, we can rely on algebra, like:
       | a - b > c       // this expression       a - b - c > 0   // can
       | become this expression       a - c > b       // or this
       | 
       | This breaks for unsigned; you cannot just change the sign of c
       | and move it to the other side of the inequality.
       | 
       | Even when you're not actually doing this algebra in the code,
       | it's useful to be able to do it outside of the code. Sometimes in
       | the code too.
       | 
       | For instance, suppose now that the values are not trivial. We
       | have no assurance that a - b will not overflow, but we do have
       | assurance from somewhere that a - c will not. The rewrite a - c >
       | b is then helpful in avoiding overflow, and easily provable to be
       | equivalent to the a - b > c expression that naturally comes from
       | the problem specification.
        
       | quelsolaar wrote:
       | I agree with this almost entierly and i use almost exclsivly
       | unsigned ints in C.
       | 
       | However, its not true that unsigned is faster because the
       | compiler can optimize. Consider:
       | 
       | x = (x * 2) / 2;
       | 
       | If x is unsigned, and overflows, x will not be the same after
       | this operation so the compiler can not optimize away this line.
       | If x is signed and overflow is undefined, the compiler can assume
       | it wont overflow and optimize away the line. UB affords the
       | compiler a lot of optimizations.
        
         | Const-me wrote:
         | > its not true that unsigned is faster because the compiler can
         | optimize.
         | 
         | It's not false either. For instance, casting int32_t into
         | int64_t requires an instruction like movsxd which does sign
         | extend. Casting uint32_t into uint64_t can often be merged into
         | the instruction who computed the uint32_t value, because the
         | instruction which write 4 bytes registers like eax zero out old
         | data in the higher 4 bytes of the corresponding 8 bytes rax
         | register.
        
           | quelsolaar wrote:
           | Thanks! I didn't know that!
        
         | MaxBarraclough wrote:
         | The article acknowledges this:
         | 
         | > There are some optimizations compilers can make assuming
         | signed integers cannot underflow or overflow that unsigned does
         | not get to participate in.
        
       | ridiculous_fish wrote:
       | Several comments in this thread take the POV that "integer
       | overflows never happen in practice" but of course they do!
       | https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=integer+ove...
       | 
       | Of course uints overflow too, which just means arithmetic is
       | hard, and it's often (not always) made harder when you have to
       | consider negative values. Bounds checking, average, division,
       | etc. INT_MIN is a horrible edge case which is easy to forget.
        
         | jcelerier wrote:
         | I opened one at random and it's an uint overflow lol.
         | 
         | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1087...
        
           | ridiculous_fish wrote:
           | The patch is completely bogus:
           | 
           | https://github.com/teeworlds/teeworlds/commit/4d529dcd2d0102.
           | ..
           | 
           | Here it tries to avoid overflow by assigning the result of 32
           | bit arithmetic to a 64 bit type. That's a common mistake.
        
       | virtualritz wrote:
       | While wrapping parts of OpenSubdiv for Rust [1] I noticed the C++
       | API was littered with uses of int for indexing/lengths (both are
       | always positive) of geometry buffers.
       | 
       | So I opened a ticket with Pixar [2].
       | 
       | Some of the same strange arguments were brought forward (incl.
       | the Google style guide BS). I wrote a longish reply [3] that
       | mentions two comments [4,5] from an ill-guided article promoting
       | use of signed in C++.
       | 
       | I closed the ticket in the end because I felt that if people
       | really believe there are good reasons for signed in the cases at
       | hand they haven't grokked the problem sufficiently - trying to
       | convince them otherwise would not be a good use of my time.
       | 
       | [1] https://crates.io/crates/opensubdiv-petite
       | 
       | [2]
       | https://github.com/PixarAnimationStudios/OpenSubdiv/issues/1...
       | 
       | [3]
       | https://github.com/PixarAnimationStudios/OpenSubdiv/issues/1...
       | 
       | [4] https://www.learncpp.com/cpp-tutorial/unsigned-integers-
       | and-...
       | 
       | [5] https://www.learncpp.com/cpp-tutorial/unsigned-integers-
       | and-...
        
         | jcelerier wrote:
         | Quoting one of these comments:
         | 
         | > The argument with substracting 5 from 3 is not an argument
         | against using unsigned. When you use signed you can run into a
         | similar problem when the result underflows. This happens much
         | less frequent and causes UB, at INT_MIN, which is the worst
         | kind of bug. The bug is often not triggered durring testing and
         | you will only notice it when it is too late.
         | 
         | The "When you use signed you can run into a similar problem"
         | part is just not true in our world. 3 - 5 happens much more
         | often than anything that may cause signed .*flow.
         | "signed underflow" "bug report"
         | 
         | yields 8 results on google FFS
        
           | leni536 wrote:
           | No such thing as "underflow" for integers. Signed integers
           | overflow in both directions. Underflow is a floating point
           | concept.
           | 
           | Edit: https://en.m.wikipedia.org/wiki/Arithmetic_underflow
        
             | cies wrote:
             | So what about an unsigned int going below zero? I think
             | most in the comments here mean that by "underflow", but i
             | could be mistaken (and it should correctly be called
             | "overflow" as well).
             | 
             | I agree with the WP article saying integers more correctly
             | "wrap around" (than overflow).
        
               | leni536 wrote:
               | The C standard only calls certain integer arithmetic to
               | "overflow" if it results in undefined behavior. In that
               | sense undefined integer arithmetic never overflows, but
               | they "wrap around", as the behavior is defined there.
               | 
               | I can't actually find a normative definition for
               | "overflow" in the C or C++ standards, but there are
               | sections and notes that describe this interpretation:
               | 
               | C++: https://eel.is/c++draft/full#basic.fundamental-
               | note-2
               | 
               | C: http://web.archive.org/web/20211231011139/https://cigi
               | x.me/c...
               | 
               | There can be subtle differences between various
               | definitions of "overflow", and I imagine there could be
               | other subtly different definitions outside C and C++. But
               | "underflow" is exclusively a floating point concept, as
               | far as I know.
        
         | von_lohengramm wrote:
         | API contracts aside, signed indices do have a major benefit:
         | https://godbolt.org/z/q4eTdPdcd
        
           | strictfp wrote:
           | Thanks! If you don't mind explaining, what are we seeing
           | here?
        
             | masklinn wrote:
             | After the initial setup block,                   c1 =
             | block[i1]; c2 = block[i2];         if (c1 != c2) return (c1
             | > c2);         i1++; i2++;
             | 
             | becomes                       mov     cl, byte ptr [rdx +
             | rcx]             cmp     byte ptr [rdx + rax], cl
             | jne     .LBB0_6             lea     eax, [rdi + 2]
             | lea     ecx, [rsi + 2]
             | 
             | if the indices are unsigned, but                       mov
             | al, byte ptr [rcx + rdx + 1]             cmp     byte ptr
             | [rdi + rdx + 1], al             jne     .LBB1_6
             | 
             | if the indices are signed, that is the compiler just
             | removes the indices and traverses the block directly
             | (starting at the input offsets).
             | 
             | That's because in C (and C++) overflow is UB, so the
             | compiler can assume it doesn't happen. Clang is apparently
             | unable to make such a determination or work around it for
             | unsigned, so for every increment of the indices it actually
             | goes and computes the indices to fetch the items from the
             | array.
             | 
             | Incidentally, GCC does not care and generates the exact
             | same (rather different) code for both signednesses.
        
               | graphitemaster wrote:
               | In this case the problem is more specifically the use of
               | uint32_t. I never quite mentioned in the article that if
               | you're going to use unsigned integers for this sort of
               | thing to always use the native-word-size unsigned type,
               | e.g size_t, if you actually change that to uint64_t (or
               | size_t) the code generation is better as you can see
               | here: https://godbolt.org/z/11nEz6EPT
        
               | sparkie wrote:
               | I see no reason why the same addressing mode could not be
               | used in both versions. This just seems like an
               | optimization that has been made under the assumption that
               | in the addressing mode                   [reg1 + reg2 +
               | disp]
               | 
               | reg2 refers to a signed integer (Which may result in the
               | effective address being < reg1 if reg2 is negative).
               | 
               | Obviously you would not want to make the same assumption
               | if reg2 refers to an unsigned integer, because if the
               | most significant bit is set, it would result in an
               | effective address below reg1, which is definitely not
               | what we would expect from adding an unsigned integer.
               | 
               | But in this case, 64-bit addressing is being used, and
               | the value of reg2 is a 32-bit integer which has been
               | specifically zero-extended. We can make the assumption
               | that [reg1 + reg2 + disp] can never result in an
               | effective address below reg1 (assuming positive disp).
               | 
               | If you take the signed version, and replace the lines
               | movsxd  rdi, edi         movsxd  rcx, esi
               | 
               | with                   movzx  rdi, edi         movzx
               | rcx, esi
               | 
               | I believe you will have something functionally equivalent
               | to the unsigned version.
               | 
               | Of course, this same optimization could not apply if we
               | were using uint64, but it could in the case of int64.
               | 
               | Either way, buggy code with no bounds checking is not a
               | reliable method of determining what optimizations can be
               | done in production.
        
               | tom_ wrote:
               | The code is compiled as it is in order to handle the case
               | where the indexes need to wrap around. Suppose
               | i1=0xffffffff on entry - you then want to access
               | block[0x00000000ffffffff], block[0x0000000000000000],
               | block[0x00000000000000001]. I've written out all 64 bits
               | of the offset, in the hope this makes the problem
               | clearer: there's no addressing mode for getting the 32
               | bit truncation. That's why there's an LEAs after each
               | access. (Note that they write to the 32 bit part of each
               | register.)
               | 
               | In the int32_t case, suppose i1=-1. Then you want to
               | access block[0xffffffffffffffff],
               | block[0x0000000000000000], block[0x0000000000000001], and
               | so on. You can do an initial sign extension of the
               | indexes, and then the base64+index64+disp32 addressing
               | mode gives you the right result.
               | 
               | Make the unsigned version take uint64_t indices, and you
               | get the same code for both. Or #include <stddef.h>, and
               | make it take size_t - same thing. This is exactly the
               | sort of thing that size_t is there for.
        
       | croes wrote:
       | Wouldn't it be better to write
       | 
       | for (size_t i = size; i > 0; i--) { // ... }
       | 
       | and use i-1 inside the loop instead of using underflow?
        
         | xxs wrote:
         | or: "for (size_t i = size; i-- > 0;){...}"
        
         | desdiv wrote:
         | I foresee two problems with that:
         | 
         | Problem 1:
         | 
         | If i is used multiple times inside the loop, then you might
         | need to make a temporary variable for it, making it confusing:
         | 
         | for (size_t i_plus_one = size; i_plus_one > 0; i_plus_one--) {
         | 
         | size_t i = i_plus_one - 1;
         | 
         | f(i);
         | 
         | g(i);
         | 
         | h(i);
         | 
         | ...
         | 
         | (The alternative is a bunch of i-1 all over the place, which is
         | also confusing.)
         | 
         | Problem 2:
         | 
         | If operations inside the loop is short, then the extra "i-1"
         | arithmetic could result in unacceptable performance penalties.
        
       | thrower123 wrote:
       | Unfortunately if I were to try to use unsigned ints extensively,
       | I'd be casting them back and forth constantly, because nearly
       | every standard library function that I'd want to use expects int
       | inputs or outputs ints.
       | 
       | A small improvement in correctness is washed out by a huge
       | decrease in ergonomics.
        
         | ncmncm wrote:
         | You do know that signed and unsigned ints convert implicitly,
         | without casts?
        
         | ridiculous_fish wrote:
         | Which standard library?
        
       | Slartie wrote:
       | The one thing I really, really hate Java for is its lack of
       | unsigned integers (there are a lot of other minor inconveniences,
       | like in any language, but nothing comes close to the missing
       | unsigned types).
       | 
       | This of course has to do with me frequently writing and
       | maintaining low-level hardware and network protocol code, talking
       | to devices on the other end that had firmware written in C and
       | therefore expect you to be fluent in bit manipulation. But hey,
       | all that nice software is useless if it can't eventually
       | interface with the real world out there, so hardware will stay
       | relevant and necessary and the accompanying close-to-the-metal
       | code can't be optimized out of the system, hence I consider good
       | tools for doing bit-level work an important quality in any
       | general-purpose programming language. And besides bit-level
       | operators that means support for unsigned integers which are
       | needed to easily combine bit-level with classic arithmetic
       | operations.
       | 
       | You can actually live without either of these in practice and
       | still do low-level stuff, but it is very unintuitive, has poor
       | runtime performance and leads to hard-to-read and bug-prone code.
        
         | hashmash wrote:
         | > has poor runtime performance
         | 
         | Are you referring to performance issues caused by lack of
         | unsigned types? Do you have a specific example? Based on my
         | observations, the conversions necessary for acting on signed
         | types as if they were unsigned generates the correct machine
         | instructions designed for unsigned types.
        
         | matsemann wrote:
         | While Java doesn't have unsigned types, it at least with java8
         | got functions to operate on ints as if they were unsigned.
         | 
         | But Kotlin (also running on the jvm) got unsigned types with
         | built in operator overloading for them, making it easy to work
         | with. "val a = 100u" gives you an unsigned Int. If you use
         | Java, there are few reasons not to use Kotlin.
         | https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-u-int/
        
       | heisenbit wrote:
       | > for (size_t i = size - 1; i < size; i--) {
       | 
       | Am I alone here feeling that this while working is anything but
       | intuitive and is asking for trouble down the road? Just imagine
       | having to change the countdown for whatever reason in maintenance
       | to 1 instead of zero and you suddenly fiddle with the operator.
        
         | throwaway744678 wrote:
         | You are not alone! This is completely unclear: the next guy
         | reading your code will definitely "fix" the halting condition.
         | I'd either handle i == 0 outside of the loop, or use something
         | like:                   for (size_t j = size; j > 0; --j) {
         | size_t i = j - 1;             // etc.
         | 
         | Or even:                   for (size_t j = 0; j <= size; ++j) {
         | size_t i = size - j;             // etc.
        
       | BooneJS wrote:
       | Low-level programming, dealing with hardware registers, is so
       | often unsigned that I use unsigned everywhere I don't need
       | negative numbers. Youtube going 64-bit was inevitable, but
       | overflowing 2^31 should not have been one of them.
       | https://arstechnica.com/information-technology/2014/12/gangn...
        
       | leni536 wrote:
       | > Of course this is dangerous as it's a narrowing conversion,
       | with a cast which silences a legitimate warning. In C and C++ it
       | invokes undefined behavior when given specific large values and
       | most certainly is exploitable. Most applications would just crash
       | on inputs >= 0x7ffffffffffffffff.
       | 
       | No, it's not. Overflow on arithmetic is undefined, but on
       | conversion it's implementation defined. In C++20 it's defined.
        
       | Flocular wrote:
       | What fans of unsigned integers seem to really want is array
       | access in the form of x[i %mod% LEN]. While signed integers "kind
       | of" give you an error on overflow (Except your unlucky or
       | somebody was malicious). Is there a way to combine both? As in
       | give me an error on overflow for "debug builds" and always mod-
       | LEN on "release builds"?
        
       | monsecchris wrote:
       | Signed integer overflow causing undefined behaviour allows the
       | compiler to make significantly better optimisations. Here is
       | Chandler Carruth, and clang developer, talking about it:
       | https://www.youtube.com/watch?v=yG1OZ69H_-o&t=2356s&ab_chann...
        
       | yath_ wrote:
       | > // But it also even has an explicit n * m form as well. >
       | make([]Object, n, m);
       | 
       | make([]T, m, n) does not allocate a slice of length n*m, but of
       | length n and capacity m.
        
       | CJefferson wrote:
       | I really donnt buy it is a "trivial idiom" to check unsigned
       | undeflowing by writing 'i > size'. I'd have to check that was
       | right all the time.
       | 
       | Also in general saying all we have to do is (I'm paraphrasing a
       | bit) "not make mistakes" is, to me, a bad idea, as all
       | programmers make mistakes.
       | 
       | I sometimes assert after a calculation that a value is >= 0, it's
       | much harder to check that if it's underflowed because it was
       | unsigned.
        
       | atq2119 wrote:
       | Slight nitpick:
       | 
       |  _> Here's a somewhat non-exhaustive list of all the undefined
       | behavior of signed integer arithmetic in LLVM which applies to
       | all languages which use LLVM:
       | 
       | > [... division examples ...]
       | 
       | > INT_MAX - INT_MIN_
       | 
       | That's not true: 'sub i32 INT_MAX, INT_MIN' is defined perfectly
       | fine in LLVM in the (modular arithmetic) way you'd expect as long
       | as the 'nsw' (no signed wrap) flag isn't set on the instruction.
       | For the most part, LLVM IR by default doesn't care about the
       | signedness of values, integer types are just treated as a bunch
       | of bits with modular twos complement arithmetic. Exceptions to
       | this rule are explicit, e.g. the mentioned nsw flag (and there's
       | also nuw for "no unsigned wrap").
       | 
       | The division examples are correct because if you want a signed
       | divide, you have to explicitly encode it as a signed divide
       | instruction in LLVM IR, which has the listed undefined behaviors
       | baked in.
        
       | slaymaker1907 wrote:
       | I prefer unsigned in Rust but signed for C/C++. This is mostly
       | because I'm always afraid that the number will be silently
       | converted to a signed type at some point. Rust is much more
       | explicit about when these conversions happen so it worries me
       | less.
        
       | hirako2000 wrote:
       | I thought given the title that it was going to be tips and list
       | of services to make accountless web use.
        
       | defen wrote:
       | What I find interesting is that the implementation of `midpoint`
       | is incorrect (should be `U difference = (U)y - (U)x;`), which is
       | not a good look when you want to argue that unsigned makes
       | everything more correct.
        
       | jy14898 wrote:
       | The argument that most signed integers never use the negatives is
       | a bit misleading, as I'm sure most unsigned integers never use
       | the upper half of their range either
        
       | cesarb wrote:
       | For me, the main reason to prefer unsigned integers whenever
       | possible is: less special cases.
       | 
       | When a signed integer is used as an array index, the value can be
       | in one of three ranges: the <0 range, the >=0 && <size range, and
       | the >=size range. To validate the index, you need two
       | comparisons. When an unsigned integer is used as an array index,
       | there are only two ranges: the <size range, and the >=size range,
       | and you need only a single comparison.
       | 
       | And that's not the worst situation with signed integers. From a
       | more general point of view, there are _four_ "classes" of signed
       | integer: positive (>0), zero, negative (<0), and INT_MIN.
       | Everybody tends to forget about that last one, but it's "special"
       | in that it can break things in unexpected ways. Negating it
       | doesn't work (you'd expect negating any number less than zero to
       | result in a number greater than zero, but for INT_MIN that
       | doesn't happen). Dividing it can trap (see for instance
       | https://kqueue.org/blog/2012/12/31/idiv-dos/ which has a couple
       | of examples) or worse.
       | 
       | With unsigned integers, there are only two "classes", zero and
       | non-zero, and it's common to not even need special treatment for
       | zero, reducing the whole thing to a single "class" of values.
        
         | phkahler wrote:
         | >> To validate the index, you need two comparisons.
         | 
         | If your code doesn't have bugs, no validation of indexes is
         | required.
        
           | kingcharles wrote:
           | The number of programs ever written without bugs can be
           | counted in a uint8.
        
           | cesarb wrote:
           | > If your code doesn't have bugs, no validation of indexes is
           | required.
           | 
           | Your code doesn't have bugs _because_ you validated your
           | indexes. What you said makes some sense when the index comes
           | from within the program itself, but not when it comes from
           | outside the program. Consider for instance the case of
           | reading a data structure from a file or the network, where
           | one field is an index or an offset into another part of the
           | structure; you _must_ validate that this index or offset is
           | not outside the bounds, no matter how perfect your code is.
        
       | kstenerud wrote:
       | I've vascillated between one camp and the other over the years,
       | but in 25 years of professional development, other than pointer
       | arithmetic I can't think of a single time where I've dealt with
       | numbers that exceed a trillion in either direction. Integer
       | overflows are so rare that I can't even remember one happening
       | with 64-bit values. Underflow on the other hand... I've
       | encountered that plenty.
       | 
       | I'm now back in the signed camp. Signed integers make it a lot
       | easier to sanity check integer subtraction when negative values
       | are disallowed (a common situation), and it's easy to reason
       | about. Also, not converting between signed and unsigned avoids
       | accidental conversion overflows due to different expectations, so
       | keeping it signed bypasses that whole can of worms while
       | maintaining maximum flexibility.
       | 
       | Unsigned gives you 2x the positive numeric range, which 99.999%
       | of the time you'll never use (and if you did, underflow detection
       | would become a nightmare).
       | 
       | I can't think of a single time I've heard someone complain "I
       | wish Java's long type supported up to 16 quintillion instead of a
       | paltry 8 quintillion"
       | 
       | At the end of the day, code is for people, so keep it easy for
       | people.
        
         | StefanKarpinski wrote:
         | Fully agree with this, although one case where Java's lack of
         | unsigned integers is a quite bad is in representing binary data
         | because the byte type is signed. I recently had cause to write
         | some Java code that did a lot of modular arithmetic with arrays
         | of bytes and oof, what a mess. There were four or five subtle
         | bugs caused by the unexpected signedness of the byte type. I
         | also wrote the same thing in Julia using UInt8 vectors and had
         | no such bugs.
        
           | xxs wrote:
           | Java technically doesn't even have non-array bytes (or
           | short), all types are just that integers: either 4 or 8
           | bytes. So if you see a declaration of byte/short (and often
           | times char) is likely a mistake.
           | 
           | The correct way is using ints, e.g. "int x = b[i] & 0xff;".
           | Pretty much you should do math with int and long only.
           | java.util.Integer/Long nowadays have static methods to work
           | with unsigned types if you need them.
           | 
           | Once you remember that you need (the annoying) "0xff", you'll
           | be free from errors. It has been there for 24 or so years.
           | Other than that wrap your arrays into ByteBuffer and use
           | provided utility.
        
             | charleslmunger wrote:
             | Citation needed? Java primitive fields are padded for
             | alignment, but that's not unique to java.
             | 
             | https://www.baeldung.com/java-memory-layout#layout
        
               | xxs wrote:
               | It's pretty much a part of the bytecode spec [0]. The
               | stack model has only 4 or 8 bytes long operands. Yes, it
               | can convert int to byte, as method signitures actually
               | need the cast. However a operation involving two bytes
               | (say add, sub, shift) would result into an int, and
               | requires a downcast back to byte. "b+=anotherByte" sums b
               | and anotherByte and then downcasts it automatically.
               | 
               | The memory layout of the classes is quite immaterial here
               | as the operation do happen within CPU registers.
               | 
               | [0]: https://docs.oracle.com/javase/specs/jvms/se12/html/
               | index.ht... [1]: https://en.wikipedia.org/wiki/List_of_Ja
               | va_bytecode_instruct...
        
           | jstimpfle wrote:
           | I don't write Java, but a good way to deal with this might be
           | to cast to int (a larger type) immediately after loading a
           | byte and before working with it. So, complexity-wise, the
           | overhead of signed byte vs unsigned byte should be at most
           | two casts, and often casting to a larger type would be
           | required anyway even for operations with unsigned byte
           | values.
        
         | dc-programmer wrote:
         | I've seen projects that use a mix of unsigned int, size_t, and
         | off_t variable declarations with implicit conversions between
         | them. This seems like a security hazard if you are handling
         | user input because just checking the original input may be
         | insufficient. size_t in general seems extra ridiculous because
         | max(size_t) is a common sentinel value returned by methods and
         | if you aren't careful and add to max(size_t) it wraps around to
         | -1.
        
         | cma wrote:
         | > I can't think of a single time where I've dealt with numbers
         | that exceed a trillion in either direction.
         | 
         | A big one is dealing with currencies like Japanese yen in
         | financial software.
        
         | kevin_thibedeau wrote:
         | It isn't about squeezing out extra range. A silent overflow
         | mechanism with predictable behavior is useful and can't be
         | cheaply replicated with signed numbers.
         | 
         | Timestamp arithmetic is a common case where unsigned integers
         | with silent overflow is superior. You can avoid bounds checks
         | and special cases by always computing a delta between a newer
         | time and an earlier time. As long as you can guarantee that the
         | sample period is less than the clock rollover period there will
         | never be any ambiguity.
         | 
         | Ada tried imposing the purity of signed integers and had to add
         | modular types to get these semantics because of their utility.
        
         | virtualritz wrote:
         | I think types should express intend.
         | 
         | This adds more 'value' to the code for people than allowing
         | them to switch their brains off because something is 'unlikely'
         | to happen. A large class of bugs exists because people assume
         | stuff is unlikely.
         | 
         | I also think one has to distinguish between API and actual
         | code.
         | 
         | If you write a lib that takes inputs that should never be
         | negative you can express this without writing a single line of
         | documentation about it (or if you do, requiring the user to
         | read it) by using unsigned types in these places.
         | 
         | You can still use signed types in your actual library code if
         | you want/must/believe in that but do not pollute the outside
         | API with them if unnecessary.
        
           | jstimpfle wrote:
           | > I think types should express intent. > If you write a lib
           | that takes inputs that should never be negative you can
           | express this without writing a single line of documentation
           | 
           | So, let's make an odd_int_less_than_20587 type and a billion
           | of other types? "Not negative" normally helps only one tiny
           | bit towards documenting anything about the accepted values.
           | Making a separate type for that and complicating everything
           | is ridiculous.
           | 
           | > If you write a lib that takes inputs that should never be
           | negative
           | 
           | Have you ever compared the difference between two unsigned
           | integers? Because unsigned integers don't have "natural"
           | subtraction. Did you ever find the need for signed numbers to
           | interact with unsigned numbers? Because if you did, it's a
           | bad idea to separate them without need. Premature isolation
           | is a prime cause of complexity.
        
             | mamcx wrote:
             | Totally!
             | 
             | This is one of the best features of the pascal family:
             | 
             | http://www.delphibasics.co.uk/Article.asp?Name=Sets
             | 
             | (look in "SubRanges")
        
               | jstimpfle wrote:
               | Almost useless in my opinion (because not a lot of
               | practical invariants can be expressed with simple ranges)
               | and will only create confusion when using non-0, non-1
               | based indexing and also create confusion about the
               | representation/the space required for representation. The
               | examples shown there are similar to the Animal-Cat-Dog
               | examples that are popular to "prove" that classes and
               | inheritance are useful.
               | 
               | That, plus the type incompatibility that arises from
               | mixing such types with other integer types.
        
               | mamcx wrote:
               | Do you have uses Pascal?
               | 
               | The ones that used it like it...
        
               | jstimpfle wrote:
               | I have worked on an enterprise application written in
               | Delphi full-time for 6 months.
        
             | virtualritz wrote:
             | I think you assume my comments are regarding C/C++.
             | 
             | They are not. I write mostly Rust these days.
        
             | morelisp wrote:
             | > So, let's make an odd_int_less_than_20587 type and a
             | billion of other types?
             | 
             | Bounded integer types have been _extremely_ useful for me
             | on a few occassions, and I wish more languages had type
             | systems powerful enough to support them. They also address
             | your concerns about subtraction.
        
             | jcelerier wrote:
             | > So, let's make an odd_int_less_than_20587 type and a
             | billion of other types?
             | 
             | Yes, what's wrong with that ? Every variable's type should
             | be defined as precisely as possible when meaningful, that's
             | the whole point (and C/C++ not easily allowing to do that
             | is one of their biggest drawbacks imho.)
        
               | cle wrote:
               | There's a tradeoff around strictness of nominal types and
               | evolvability, e.g. changing the threshold on such a
               | strict type would be a breaking change.
        
               | yakubin wrote:
               | Changing the threshold without signaling the change in
               | the type system is also a breaking change. It's just that
               | your compiler won't warn you about it when you inevitably
               | forget to update your code. Instead your code is going to
               | break at runtime.
        
               | jstimpfle wrote:
               | Obviously if you have at most 8/16/32/64 bit types (only
               | making new types because of physical constraints), you'll
               | have way, way fewer breaking changes than if you try to
               | encode bounds extremely tightly and try to update them
               | whenever you 2 instead of 1 somewhere.
               | 
               | In fact most software is designed such that the physical
               | sizes are chosen first, and then the practical bounds
               | follow from that.
        
               | cle wrote:
               | Not necessarily, it depends on what you do with the
               | value.
        
               | jstimpfle wrote:
               | Are you seriously asking? Because what I suggested
               | rhetorically would be the death of all your programs for
               | the simple cause that line count would quadruple and you
               | would have to touch half your codebase every time you
               | make a tiny change.
        
               | jcelerier wrote:
               | When I make a change today like renaming a method I just
               | let my IDE apply it to the codebase, why would that be
               | different ?
        
               | jstimpfle wrote:
               | Because if you change one type to include the number
               | 235498 it will also affect 377 other types.
        
               | jcelerier wrote:
               | why would it ? if you set things such that expected
               | ranges are set where they matter (e.g. where you do
               | arithmetic) you should only have to change the types for
               | which range issues occur, like this (very quick and
               | dirty, likely wrong, but you get the idea):
               | https://gcc.godbolt.org/z/4v6dW9czP
        
             | JD557 wrote:
             | > So, let's make an odd_int_less_than_20587 type and a
             | billion of other types?
             | 
             | I see your point, but I think that (while not always), it
             | can still be pretty helpful to add your custom types to
             | make invalid states unrepresentable.
             | 
             | This can sound ridiculous on something like C, but if your
             | language has refined types, it's not that bad.
             | 
             | For example, this doesn't look awful to me:
             | 
             | ``` type WeirdId = Int Refined (Odd And Less[20587])
             | 
             | val myId: WeirdId = 5 // OK val myInt: Int = myId // Can be
             | used as an Int val myRuntimeId: Either[String, WeirdId] =
             | refineV[Odd And Less[20587]](myId + 2) // Runtime Check val
             | myInvalidId: WeirdId = 6 // Compilation Error ```
             | 
             | https://scastie.scala-lang.org/qoSHgL4PQCW6lC2MUHc5YA
        
               | jstimpfle wrote:
               | I've spent some time pondering things like that, but
               | ultimately concluded that we should keep these things
               | away because there is a duplication between type-level
               | and code level, and I'm completely fine with sprinkling a
               | few runtime assertions in most cases. Normal code syntax
               | is much more suited to expressing invariants. Obviously
               | I'm not into statically proving the correctness of my
               | programs, so if that's your cup of tea things are
               | probably looking different (and I'm sorry for you).
        
               | atq2119 wrote:
               | I would love to be able to do more formal proving of
               | programs in practice, but I 100% agree with you. A lot of
               | what the advanced typing crowd seem to be doing is to re-
               | invent programming but at the type level, and it's
               | unclear to me why that should count as progress. It
               | reminds me uncomfortably of C++ template metaprogramming
               | -- and for good reasons, C++ has made steps towards
               | replacing that with statically evaluated expressions
               | written in regular syntax.
               | 
               | My gut feeling is that the right way forward is to have a
               | fairly standard type system visible in the source
               | language augmented by contracts written in regular code.
               | Basically a slight augmentation of assert(). A formal
               | prover could internally reason about an extended type
               | system where those contracts are automatically lifted to
               | the type level if that helps for some reason, but
               | programmers wouldn't be bothered with it.
               | 
               | This seems unlikely to be a genuinely novel idea, so I'd
               | be curious to hear whether systems like that already
               | exist.
        
               | captainmuon wrote:
               | Exactly. It pains me when someone comes up with a new
               | language with dependent types, but then to encode a
               | simple constraint they first define integers via peano
               | axioms using some kind of template metaprogramming. (The
               | example I'm remembering was Coq or a Haskell dialect or
               | something.)
               | 
               | There was a dialect of C#, Sing# or Spec#, that allowed
               | you to specifying pre and post conditions in the same
               | expression language as the main language, and they were
               | checked at compile time. This is what comes closest I
               | think.
        
             | tel wrote:
             | Not every type pays it's way, but the distinction between N
             | and Z is well worn and useful. N measures the size of
             | things while Z is a little more abstract. I think u64 can
             | quickly convey a powerful intent.
        
               | jstimpfle wrote:
               | If we're talking about N and Z (which makes sense since
               | most programs are grounded in a mathematical model with
               | some physical constraints added on top), then please
               | acknowledge that N doesn't have subtraction defined,
               | which illustrates why it's not very useful for most code.
               | 
               | You could also argue that subtraction for unsigned
               | numbers is only partially defined, just like for signed
               | numbers. But that would be missing the point, since most
               | numbers in practical programs are small, and for these
               | numbers the signed range is indeed much more useful since
               | subtraction is defined for signed integers (e.g. i32) for
               | small numbers. This is not the case for unsigned, where
               | 3u-4u is usually not what you want.
        
             | masklinn wrote:
             | > So, let's make an odd_int_less_than_20587 type and a
             | billion of other types?
             | 
             | Yees?
             | 
             | Bounded integers are a relatively common feature of
             | programming languages (and one which is sorely missing from
             | Rust, it's technically possible currently but not super
             | fun, when fleshed out enough const generics should make
             | them much nicer, possibly even built-in)
        
               | jstimpfle wrote:
               | What is the point of bounded integers? Most quantities
               | don't have natural bounds, and practical bounds only
               | arise from physical limitations (8/16/32/64 bit etc).
        
               | atq2119 wrote:
               | Those practical bounds have a tendency to percolate into
               | bounding other things. So you can only have 2**10 foos
               | because they're indexed by a 10-bit bitfield somewhere?
               | Now maybe you can only have 204 bars, because every bar
               | has at least 5 foos. And so on.
               | 
               | If you _genuinely_ wanted to reason solidly about such
               | limits, you do end up needing arbitrarily bounded
               | integers. Of course, most people don 't want to do that,
               | for good reasons. And even if you wanted it, there's a
               | reasonable question whether adding them to the language
               | syntax is really the right thing to do.
        
             | jandrewrogers wrote:
             | > So, let's make an odd_int_less_than_20587 type and a
             | billion of other types?
             | 
             | Yes, and at least in C++ it isn't that difficult to
             | trivially generate these types as you need them with a
             | modicum of template-fu. You don't need that many integer
             | type templates to capture most of the common cases and
             | provide contextually sane operators. This might be a little
             | more difficult in other languages.
             | 
             | This is pretty standard type safety practice and usually
             | well worth it in terms of writing robust low-defect code.
        
           | 8note wrote:
           | Types aren't always expressive enough for that. I usually
           | can't specify that a number should always be negative for
           | instance, or that there's no 0.
           | 
           | My preference is to minimize surprise. If everyone is used to
           | using signed types for everything, you better have a good
           | reason for using something else, and leave that justification
           | in a comment
        
           | marcosdumay wrote:
           | > I think types should express intend.
           | 
           | Yes, and let's start with overflow behavior because it's much
           | more relevant than the type domain.
           | 
           | If overflow isn't encoded by the type, there is just no way
           | to correctly set a strict domain.
        
           | alerighi wrote:
           | And why also limit to that? What if I have a function that
           | accepts values in the range [0, 100]? Make a type to express
           | that? What if I have a function that takes only positive
           | integers (i.e. unsigned integers minus 0)? Chances are that
           | you should anyway validate your input. Also what if you have
           | a function that takes an unsigned and a signed is passed? On
           | most situations you only get a warning (or not even that) and
           | it's passed a completely wrong input to the function. Isn't
           | better to take an int and validate it before doing anything
           | with that?
           | 
           | You see, doesn't make a lot of sense to distinguish between
           | positive and negative in the end. From a mathematical point
           | of view negative integers have the same dignity of positive
           | integers since a couple of centuries. The same operations
           | that apply on positive integers apply on negative integers,
           | and not only that, an operation between two positive integers
           | can as well result in a negative integer!
           | 
           | The unsigned type was created just to have one more bit if
           | you *are sure* you have only positive integers. That could
           | had a sense in the era of 8/16 computers. Nowadays that we
           | have 32 or 64 bit computers does really that extra bit count
           | that much? I don't think so... 2 billions is plenty enough
           | for most usages.
        
           | CamperBob2 wrote:
           | "This value should never be negative, so I'll use an unsigned
           | type." There are two types of programmers: ones who are
           | reassured by that reasoning, and ones who shudder at it.
        
           | kazinator wrote:
           | In C or C++, if you write a function void api(unsigned), you
           | can cheerfully call it as api(-42) or api(b - c) where b - c
           | are int.
           | 
           | This is a poor, poor substitute for contract checking.
        
           | barrkel wrote:
           | What you want is range subtypes of signed integers.
        
             | kazinator wrote:
             | Important: without silent conversion. Languages like Pascal
             | and Ada require explicit conversions in more places to make
             | that sort of thing useful.
        
               | pyjarrett wrote:
               | It also allows creating type checked "new" numerical
               | types for semantics purposes to help improve interfaces.
               | This helps prevent things like accidentally passing an
               | integer in the wrong units for example. Two of the worst
               | offenders I've seen of this is: "Is this uint64_t (or
               | float) "dt" parameter in ticks, seconds, milliseconds,
               | microseconds or nanoseconds?" and "Is this angle
               | parameter in radians or degrees?"
        
           | kstenerud wrote:
           | In a perfect world, this would be so. And if there were types
           | that were performant AND could enforce such invariants
           | without the downsides, I'd welcome them gladly. But primitive
           | types come with baggage and limitations, so you have to make
           | compromises. And the compromises when using unsigned ints are
           | just too costly in my opinion. They're a good tool for
           | specific use cases, but not as good as signed for general
           | purpose code.
        
             | virtualritz wrote:
             | Could you list some of the downsides & compromises in the
             | case I made?
        
               | kstenerud wrote:
               | - silent unintended underflow behavior
               | 
               | - dangers of mixed signed/unsigned code (signed values
               | MUST exist, but unsigned values don't have to)
               | 
               | - more cognitive load to read and understand unsigned
               | underflow detection code + issues once you exceed the
               | halfway point
               | 
               | This is why Java offered signed integer types only (I
               | think they should have allowed unsigned as well, but for
               | different reasons)
        
               | virtualritz wrote:
               | > silent unintended underflow behavior
               | 
               | Please show me an example in the wild that could not be
               | trivially caught/alleviated. With 'trivially' I mean the
               | code that needs to be written to prevent it, not the
               | thinking you need to do to consider it.
               | 
               | As for the latter: this is what I get paid for. Writing
               | the code out is necessary but not what I really get paid
               | for (unless I were a very slow typist, that is).
               | 
               | I find that most of the time I was simply too lazy. Or
               | rather: I wrote C/C++ code for 20+ years. I used int and
               | didn't think (unintended rhyme).
               | 
               | Now I write (mostly) Rust I find myself considering the
               | edge cases regularly and also covering them because the
               | compiler forces me to. See also this comment [1].
               | 
               | I.e. a - 1 + b where a is always positive (incl. 0) and b
               | is always > 1 'just' works with int (and unsigned
               | [overflow] too!) in C/C++.
               | 
               | In Rust you will get a panic when a = 0 which will then
               | make you think what you're doing and simply reorder this
               | to a + b - 1.
               | 
               | I find that this is the 'cognitive load' that needs to be
               | applied commonly and I'm pretty fine with that.
               | 
               | > dangers of mixed signed/unsigned code
               | 
               | Alleviated by simply not mixing. There is a reason
               | languages like Rust simply do not allow this without
               | explicit typecasts. And those, in cases where one type is
               | signed and the other is not and they have equal width
               | translate to "all bets are off".
               | 
               | > more cognitive load to read and understand unsigned
               | underflow detection code [...]
               | 
               | See first point. IMHO this is simply the same point
               | expressed differently. Underflow happens when you
               | subtract.
               | 
               | Ensure you do not subtract a larger value from a smaller.
               | This is as easy as writing max(a, b) - min(a, b) or what
               | author of [1] does.
               | 
               | Same as abs(a - b) but works with unsigned. Cognitive
               | load? I do not see any and I agree with the author of the
               | article that this is easier to read.
               | 
               | [1] https://news.ycombinator.com/item?id=29767877
        
               | kstenerud wrote:
               | This is one of those "If everyone would just do X" kinds
               | of situations, which is why I'm against it. Things go
               | wrong all the time - in fact it's a miracle anything
               | works at all. So when I see "code that needs to be
               | written to prevent it", I let out a sardonic laugh;
               | people just don't work that way.
               | 
               | People WILL forget to check, or implement the check wrong
               | (and it won't be discovered until the next black swan
               | event), or some invariant will change in future and
               | nobody will know to update this code. The code doesn't
               | care because the compiler doesn't enforce the invariants,
               | so you're left with bare and likely confusing (and
               | probably eventually undefined) behavior.
               | 
               | I for one would LOVE it if popular languages could be
               | instructed to inject invariant enforcement code (not
               | asserts) at certain boundaries. It would help with a
               | whole host of problems!
               | 
               | When you cross zero with a signed integer calculation,
               | it's easy to find out, and the code has low cognitive
               | overhead. Also, with code generally structured this way,
               | bad input propagation tends to get stopped earlier
               | because of the number of functions that check against
               | negative values.
               | 
               | It's also possible to underflow check with unsigned, but
               | the code is more complicated and can't stop propagation
               | if it fails, and if you are allowing values > the signed
               | positive limit (or unwittingly allowing it), your
               | overflow detection code gets even more complicated (or
               | wrong) due to the reduced overhead space. You COULD do a
               | bunch of checks beforehand, but that's not always
               | feasible due to the combination of operations being done
               | on the operands, and once again people are people.
               | 
               | Even though many modern languages block uncast type
               | conversions, that still doesn't protect you from
               | converting a negative integer to an unsigned integer.
               | Yes, you SHOULD be checking before the cast, buuuuuuut...
               | 
               | What we want to do is reduce the number of ways things
               | can go wrong, reduce the fallout of an errant process,
               | and minimize cognitive load to reduce bugs overall.
               | Making that worse just to double the positive integer
               | space doesn't make sense to me.
        
               | whattheactualf3 wrote:
               | Here's an example:                 unsigned int length_a
               | = 10;       unsigned int length_b = 20;            if
               | (length_a - length_b < 0)       {
               | printf("negative diff\n");       }       else       {
               | printf("positive diff\n");       }
               | 
               | I would argue that there is plenty of code like that in
               | the wild. It looks innocent and harmless but it gives a
               | completely surprising result.
        
               | munch117 wrote:
               | If you ignore the compiler warning. This is trivial for
               | the compiler to detect.
        
               | jcelerier wrote:
               | only in the most trivial cases
               | 
               | https://gcc.godbolt.org/z/YWPP5qdbx
        
               | captainmuon wrote:
               | I don't understand why the programming language can't
               | promote the temporary to a larger signed int. Of course
               | you can't change it in C anymore, but its like this in
               | most languages. But you rarely ever want unsigned modulo
               | arithmetic here (and if you do it should be a special
               | syntax).
        
               | virtualritz wrote:
               | -Werror
        
           | Avshalom wrote:
           | "unsigned" doesn't express intent. It expresses
           | implementation, maybe it expresses assumption.
           | 
           | Ada's "Positive" or "Natural" or                 subtype X is
           | Integer range Y..Z;
           | 
           | expresses intent (well expresses requirement really), it also
           | enforces that constraint, so it's not merely an assumption.
        
           | [deleted]
        
           | UncleMeat wrote:
           | In most languages that offer this distinction, unsigned
           | integers also don't really express your intent in an
           | effective way.
           | 
           | Imagine you have a bug in your program. If you intend for a
           | value to be non-negative and use a signed integer, you get a
           | negative value. Your code is broken. You didn't want a
           | negative value. But you do have have a very easy way of
           | checking for this at runtime, at least.
           | 
           | Now imagine you instead use an unsigned integer. The bug is
           | still there but now it manifests as overflow or underflow.
           | Your program is still broken. Nothing about using an unsigned
           | integer saved you. And worse, you don't have an easy
           | predicate to check that something has gone wrong.
           | 
           | I think this can only be a reasonable choice if you are
           | working in a language that has dynamic checks for overflow
           | and so using an unsigned integer enforces not only the
           | predicate that the value is nonnegative but also that all
           | operations stay within its expected arithmetic domain.
        
             | [deleted]
        
           | whattheactualf3 wrote:
           | It does not express intent. I remember being a junior C
           | programmer and using "unsigned" to express _my_ intent (it 's
           | greater than or equal to zero).
           | 
           | In reality, like all C keywords, it's completely misleading
           | and actually really means
           | "weird_arithmetic_mode_that_will_mess_you_up".
        
             | carlmr wrote:
             | I don't know what's misleading about it or how the
             | arithmetic is weird?
        
               | b3morales wrote:
               | Imagine a parallel situation. There is a type
               | `NonEmptySet`, with an `intersection` operation. Now,
               | given `NonEmptySet(1, 2).intersection(NonEmptySet(3,
               | 4))`, and the requirement that `intersection` produces
               | another `NonEmptySet` what does that return? How about a
               | set containing everything _except_ 1, 2, 3, 4? Does that
               | seem reasonable? That 's analagous to what `(unsigned)3 -
               | (unsigned)4` does.
               | 
               | In short, the operations on `unsigned int` do not really
               | behave like "non-negative integer"; as such it's a weird
               | match to use them for that purpose.
        
               | SAI_Peregrinus wrote:
               | Signed and unsigned integer types both are finite fields,
               | rather than Abelian rings like the normal integers used
               | in most math. That difference makes the arithmetic
               | "weird", but going to signed integers doesn't change
               | anything, anything except bigints (or generic arbitrary
               | precision floating point) is a form of
               | "weird_arithmetic_mode_that_will_mess_you_up" if you
               | pretend you're operating over the real numbers.
               | 
               | Not sure what they mean about it being misleading though.
               | They might prefer it if they were called "modular
               | integers" instead of "unsigned integers" but that would
               | lead to confusion with modules IMO.
        
           | joppy wrote:
           | If my intent is that a certain integer should never be
           | negative, labelling it unsigned does a poor job at conveying
           | this intent because any arithmetic just silently does the
           | wrong thing. 3-4 is some huge number rather than "error".
        
             | cle wrote:
             | An unsigned integer will never be negative, that intent is
             | expressed clearly and correctly. What the operational
             | semantics _should_ be is unclear, because of the tradeoffs
             | involved in signaling an error.
             | 
             | In terms of the C standard, "3-4" for unsigned ints is
             | modular arithmetic, and the "wrong thing" is assuming that
             | it will do anything other than wrap around. This is very
             | clearly defined, and implied whenever you see an arithmetic
             | expression on unsigned integers.
        
               | dagss wrote:
               | Clearly defined, but very inconvenient and makes it too
               | easy to write buggy code that looks correct on surface.
        
               | cle wrote:
               | Inconvenient to whom? It's pretty inconvenient _not_ to
               | do that if you eg have strict memory /performance
               | requirements.
               | 
               | C made the right tradeoff IMO. You can protect yourself
               | against overflow if you need to, but if it always signals
               | errors you can't turn that off.
        
               | kazinator wrote:
               | Conversion from signed to unsigned is implicit and
               | silent. The expression being converted to your "can never
               | be negative" type isn't nonnegative!!! Its value
               | drastically changes; e.g. -3 becomes a huge number.
        
               | snovv_crash wrote:
               | Not if you pass -Wconversion to GCC
        
             | tromp wrote:
             | In Rust, 3u64 - 4u64 is "error", specifically
             | 
             | thread 'main' panicked at 'attempt to subtract with
             | overflow'
        
               | Macha wrote:
               | It panics _in debug builds_ but wraps in release builds
               | because including the checks in every arithmetic
               | operation is not zero cost.
        
               | masklinn wrote:
               | Though you can configure your release builds to panic as
               | well. That can be quite useful if you don't expect
               | trivial computations to be a large cost center (and you
               | can always bench to check that assumption).
               | 
               | Even more so now that custom profiles have been added to
               | Cargo (yay), you can enable overflow checking in release,
               | and add a `release-unchecked` or whatever.
        
               | tialaramex wrote:
               | Or, better in many cases, you can _write what you meant_
               | explicitly in Rust.
               | 
               | 3u64.checked_sub(4u64).expect("Overflow");
               | 
               | This allows you to spell out that you don't _expect_ this
               | to overflow, and Rust should panic if it does, regardless
               | of your compiler flags.
        
               | masklinn wrote:
               | Of course you can do that, but I would say that doing so
               | defensively for every operation is way more verbose and
               | unwieldy than you'd want.
               | 
               | Much simpler to just toggle on the relevant flag.
        
               | eyelidlessness wrote:
               | I think GP was saying you can do that when you expect
               | that the general case doesn't need to be checked but a
               | specific case does.
        
               | masklinn wrote:
               | > when you expect that the general case doesn't need to
               | be checked but a specific case does.
               | 
               | But surely that's almost never the case? Few programs
               | _actually_ desire or care for base 2 modular arithmetics,
               | and when they do it tends to be for very specific tasks
               | (usually cryptographic or cryptography-adjacent e.g.
               | hashing, checksumming, ...).
        
               | eyelidlessness wrote:
               | > But surely that's almost never the case?
               | 
               | Which is why it's not so impractical to make an
               | occasional exception.
        
             | raverbashing wrote:
             | But then your function should check if the value is within
             | reasonable parameters.
             | 
             | If it was an int at the function signature and you got -1
             | what would you expect to happen? It's the same thing.
        
               | marginalia_nu wrote:
               | > But then your function should check if the value is
               | within reasonable parameters
               | 
               | How could you possibly check this using types that cannot
               | represent what you are checking for?
        
               | chromatin wrote:
               | > How could you possibly check this using types that
               | cannot represent what you are checking for?
               | 
               | `f(u32 a, u32 b) { assert a > b ... `
               | 
               | (if the hypothetical following operation is `a-b` as
               | discussed higher in the thread)
        
               | pjmlp wrote:
               | Better ensure that assert is not turned off in release
               | mode then.
        
             | cesarb wrote:
             | > because any arithmetic just silently does the wrong thing
             | 
             | The same thing, but worse, can happen with signed integers.
             | (-3)-INT_MAX is either some huge _positive_ number, or
             | something crazy because it 's undefined behavior and the
             | compiler is allowed to do anything it wants.
        
             | willis936 wrote:
             | This sounds like an issue with the language. 3-4 is not a
             | large number and should not be by default. It should be a
             | case handled by the developer.
             | 
             | Maybe you want to do some weird bit banging trickery, and
             | that's fine too, but it should require an out-of-the-way
             | function call.
        
               | dragonwriter wrote:
               | > This sounds like an issue with the language. 3-4 is not
               | a large number and should not be by default
               | 
               | Many languages optimize numeric operations and DX around
               | numeric types for efficiency rather than correctness, and
               | make the latter high-friction. (Wrap-around subtraction,
               | decimal literals being treated as IEEE floats, etc.)
               | 
               | The exceptions (or cases where it is merely less true)
               | tend to be very high level, dynamic languages.
        
         | berkut wrote:
         | I've hit the 2147483647 limit of indices occasionally in
         | VFX/Graphics with large datasets: memory efficiency was also
         | important, so 32-bit was a necessity (random access is
         | required, so can't dynamically pack), so I tend to use unsigned
         | these days (uint32_t) within arrays, but native 64-bit lengths
         | (normally size_t) as standard local variables, and I normally
         | go with unsigned there as well.
         | 
         | Several APIs in the VFX arena (Pixar OpenSubD, Pixar USD, and
         | Foundry's Katana GeoLib API) all use signed ints for indices
         | which I've occasionally argued against, with the response being
         | "Google's coding standard says don't use them".
        
           | kstenerud wrote:
           | Yup, and that's a case of an API not aging well. But even in
           | that case, switching to unsigned is only kicking the can down
           | the road, not solving the problem. You'd get another year or
           | two and then hit the 4294967295 limit.
           | 
           | As for 64-bit values, unless you can foresee requiring > 8
           | quintillion, unsigned isn't buying you anything.
        
             | mjevans wrote:
             | Resource consumption does matter though. For example CPU
             | caches or the quantity of RAM within a system. If their
             | workset does fit within 32 bits of index the intentional
             | design choice very likely correlates to a measurable
             | improvement in speed due to cache locality and system
             | resource utilization.
        
               | kstenerud wrote:
               | Indeed it does matter! As I said this is an example of an
               | API that hasn't aged well (when they devised it, they
               | probably didn't envision this happening).
               | 
               | There are a number of ways to handle this:
               | 
               | - Use unsigned 32-bit (gives you a little bit more
               | runway, but you'll still hit the end hard)
               | 
               | - Use 64-bit values (ends your problem once and for all,
               | but costs double the memory so now you have a new
               | problem)
               | 
               | - Use 40-bit values (Gives you a lot more runway, but
               | costs CPU)
        
           | virtualritz wrote:
           | TBH I think this is pure laziness.
           | 
           | I looked at OpenSubdiv extensively. There are no good reasons
           | to use signed in the cases I opened the ticket for and making
           | sure the code inside the lib does the right thing with
           | unsigned arithmetic is trivial.
           | 
           | But someone still needs to do that and writing that "Google
           | (essentially) says you don't need to" in their style guide is
           | much less work. ;-)
        
         | spacechild1 wrote:
         | > Integer overflows are so rare that I can't even remember one
         | happening with 64-bit values. Underflow on the other hand...
         | I've encountered that plenty.
         | 
         | I totally agree!
         | 
         | After some horrible bugs caused by unsigned integers in
         | innocent looking C/C++ code, I'm 100% in the signed camp. The
         | only time I use unsigned is when I need well-defined overflow
         | behavior (which is rare).
         | 
         | As a bonus, using signed loop counters in C/C++ code enables
         | certain kinds of compiler optimizations (based on the fact that
         | signed integer overflow is undefined behavior):
         | http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
        
           | cwzwarich wrote:
           | The loop optimization you point to can be done for unsigned
           | loop counters even without exploiting undefined behavior.
           | It's part not why the standard C practice was to use unsigned
           | loop counters.
        
         | colanderman wrote:
         | A bonus is that you can enable UBSAN to automatically and
         | efficiently catch signed integer overflow, without changing
         | language semantics (since, unlike unsigned overflow, signed
         | overflow is undefined in C/C++). Here's an earlier post of mine
         | showing how: https://news.ycombinator.com/item?id=24578534
        
         | masklinn wrote:
         | > I can't think of a single time I've heard someone complain "I
         | wish Java's long type supported up to 16 quintillion instead of
         | a paltry 8 quintillion"
         | 
         | That Java has no support for unsigned integers is a very common
         | complaint.
        
           | marginalia_nu wrote:
           | It's one of my biggest complaints attempting to do systems
           | programming in the language, that and the lack of 64bit mmap.
        
             | hashmash wrote:
             | I've had to resort to rolling my own mapping layer using
             | JNA. Fortunately, 64-bit mmap support is being added as
             | part of the Panama foreign-memory project.
        
             | xxs wrote:
             | I have no issue with the lack of unsigned - that's ok. It's
             | a trivial workaround, as it's rare enough. However the lack
             | of 64bit indices over memory mapped files blows indeed,
             | with no easy (performant) replacement. It's doable but it'd
             | require a shim over an array of ByteBuffer with the double
             | indirection and pretty much inability for the JIT to
             | optimize away all the bound checks.
        
         | xondono wrote:
         | A lot of processors around you are still 16 and 32 bits. I've
         | hit the overflow _a lot_ too.
         | 
         | > At the end of the day, code is for people, so keep it easy
         | for people.
         | 
         | This is a dangerous heuristic, because _people_ here means
         | exclusively developers. It's easy for us to trade annoying but
         | mostly harmless bugs for rare but harmful ones.
        
           | kstenerud wrote:
           | Yes, I hit it a lot during my Z80 days, too. I also had to do
           | manual memory management using a memory-mapped page register.
           | We all have our constraints we must operate under, and a bag
           | of tools to help deal with those constraints.
           | 
           | But "unsigned by default" won't save you here; all it does is
           | give you a little more breathing room for your special case
           | code (and hey, maybe that doubled positive integer space is
           | all you needed, in which case congrats).
           | 
           | But all of the other problems are still there. That's why I'm
           | saying that "unsigned by default" is backwards. It should be
           | "signed by default", and unsigned for special situations that
           | warrant it.
        
         | tored wrote:
         | Actually I had a colleague that always complained to me that
         | Java didn't have unsigned types. He was of course a C++
         | programmer.
        
           | xxs wrote:
           | c'mon java does have an unsigned type, just that it's 16bit
           | only (char)
        
         | pjmlp wrote:
         | > 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.
         | 
         | http://www.gotw.ca/publications/c_family_interview.htm
         | 
         | Gosling on why Java only has signed arithmetic.
        
       | whattheactualf3 wrote:
       | The argument is really strange. It says that unsigned ints can't
       | become negative and it's a good thing. But they do that by
       | instead having crazy high values. How is that a win?
       | 
       | You could argue that it doesn't matter because an incorrect
       | program is incorrect regardless. True. At least with signed
       | integers you can represent differences between positive values.
       | How do you do that with unsigned?
        
         | ridiculous_fish wrote:
         | Signed integers may also have crazy high values so you're
         | already on the hook for dealing with those. The case for
         | unsigned values is that it frees you from having to think about
         | negative inputs. We all know to check for divide by zero; how
         | many remember to check for INT_MIN / -1?
         | 
         | Regarding differences, in practice you usually want those
         | differences to be non-negative. With uints, you compare first.
         | Not:                   int length = end - begin;         if
         | (length < 0) return error;
         | 
         | but:                   if (begin > end) return error;
         | unsigned length = end - begin;
         | 
         | This is also a good habit for signed ints, since it avoids
         | underflow in more (but not all) cases.
         | 
         | An old post with a few fun crashes for negative inputs:
         | https://kqueue.org/blog/2012/12/31/idiv-dos/
        
       | munch117 wrote:
       | The reason I've always preferred unsigned is that it's easier to
       | write code that is correct for the full input domain.
       | 
       | Consider for example:                 extern void g(int);
       | void f(int x, int y)       {           g(x - y);       }
       | 
       | The function f obviously doesn't work for all values of x and y:
       | Some values will overflow, some values will underflow.
       | 
       | How do you fix that? Writing an explicit check for whether x-y
       | overflows is highly impractical, no one does that.
       | 
       | Or you could document the limitation using pre- and
       | postconditions that narrow down the range of allowed values to
       | something you know will work:                 extern void g(int);
       | void f(int x, int y)       /*           PRECONDITION: -1000000 <=
       | x <= 1000000 and -1000000 <= y <= 1000000       */       {
       | g(x - y);       }
       | 
       | No one does that either. I don't know why. There's no principled
       | reason why you couldn't meticulously keep track of signed int
       | input and output ranges. But that's how it is, no one does it,
       | valid int ranges are always unstated.
       | 
       | Now for the unsigned version of the same problem:
       | extern void g1(unsigned);        extern void g2(unsigned);
       | void f(unsigned x, unsigned y)       {           if(x >= y)
       | g1(x - y);           else               g2(y - x);        }
       | 
       | Assuming g1 and g2 are well-defined for all unsigned inputs, so
       | is f. That was easy.
       | 
       | It may look like more code, because there are two g functions
       | now. But chances are you probably need to treat the x<y case
       | differently somewhere anyway; in the larger scheme of things,
       | unsigned is not more code.
        
         | clappski wrote:
         | What you should be doing for checked arithmetic with GCC is use
         | the builtins for those that aren't aware;
         | 
         | https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins...
        
           | munch117 wrote:
           | It would be nice to see something like that standardised so
           | you could use it in portable code.
           | 
           | It doesn't solve the problem though. You still need to write
           | some code for what to do if there's an overflow, and you need
           | separate handling for underflow. So the signed case is now:
           | extern void g1(int);        extern void g2(int);
           | extern void g3(int);        void f(int x, int y)       {
           | int x_minus_y;           if(__builtin_ssubl_overflow(x, y,
           | &x_minus_y))              g1(x_minus_y);           else if(x
           | > y)              g2(...); /* overflow */           else
           | g3(...); /* underflow */       }
           | 
           | Work in progress. I've given up on figuring out what
           | arguments to pass to g2 and g3. Since the difference won't
           | fit in an int, you would need to offset-adjust the value
           | somehow in order to fit it in an int. Seems messy. Maybe you
           | can think of something simpler.
        
       | graphitemaster wrote:
       | Author here. I believe many of the complaints here are concerned
       | about what is more common rather than what is universally better
       | over all possible inputs which is actually the point of view this
       | article is written from. The issue with the "general case" is
       | that exploits are never actually found there, they're always
       | found in the edge cases such as large, or "pathological" values
       | as it were. Signed integer arithmetic has more of these edge
       | cases than unsigned when it comes to sizes, indices, and offsets
       | used in expressions controlling memory (either directly, or
       | indirectly) which is the most common application of integers in a
       | codebase. The native-word-size unsigned integer type covers the
       | full numeric range for those operations, while signed simply
       | cannot. At the same time, preventing a whole numeric-range that
       | is simply incompatible with those operations (negative values),
       | and a whole class of issues related to undefined operations. The
       | only real edge-case unsigned has that is more error-prone is
       | values close to zero under subtraction and that's relatively easy
       | to account for which I go into great detail to explain.
        
       | lisper wrote:
       | > It turns out that computing differences safely is actually
       | quite hard for signed integers because of underflow, even in
       | languages which support wrapping behavior for them, e.g INT_MAX -
       | INT_MIN is still going to be incorrect even. There just isn't a
       | trivial way to do this safely
       | 
       | And that right there is a compelling reason not to use C or C++.
       | If the language is so broken that it doesn't even let me subtract
       | signed integers safely without risking shooting myself in the
       | foot it is worse than useless in today's world because it gives
       | me the illusion of programming in a high-level language when in
       | fact I am not.
       | 
       | When I want to subtract x from y, I want to be able to write y-x
       | and have the _language_ worry about the details. That is the
       | _whole point_ of having a high level language, to abstract away
       | the low-level details and let you write high-level abstractions
       | without having to worry about introducing bugs that could imperil
       | your entire enterprise. C and C++ don 't do that.
        
         | ncmncm wrote:
         | This is the argument not to use computers at all. It is not
         | persuasive.
         | 
         | There is never a reason to use C.
         | 
         | But C++ is a language for engineers. If you don't have the
         | patience to do engineering, then you should not. If you have a
         | need for engineering anyway, hire one.
        
       | pornel wrote:
       | > It's very trivial to check if x * y would overflow though and
       | you should just learn the extremely simple and obvious test.
       | if (y && x > (T)-1/y)
       | 
       | I think it's sarcasm, but with C guides like this it's never
       | certain :)
        
         | ufo wrote:
         | Maybe this is not a problem for calculating the size of a
         | memory allocation, but that division operation is going to be
         | several times slower than the multiplication it is guarding. Is
         | there a faster way to check for overflow, perhaps using
         | compiler builtins?
        
         | saberience wrote:
         | Maybe I'm just a moron but what makes this test "obvious"? What
         | is T?
        
       | InfiniteRand wrote:
       | One thing with array indices is that using signed means that you
       | need to always check for negative in addition to checking for too
       | large. Unsigned indices means you can just check against too
       | large.
       | 
       | I think a lot of the scenarios where signed is safer are also
       | situations where the application logic would be wrong, so I am a
       | bit skeptical of that argument.
       | 
       | But Ultimately the biggest factor to consider is what your team
       | or prospective team members in your industry are used to,
       | different industries tend to have people who never expect
       | unsigned values vs those who always assume unsigned will be used.
        
       | ulnarkressty wrote:
       | I've seen plenty of bugs due to misuse of unsigned types, mostly
       | coming from beginners that are trying to fix type conversion
       | during comparison compiler warnings. Then they go on and do some
       | arithmetic with the loop index, which results in an underflow or
       | two. We now have hard rules against unsigned usage if it is part
       | of an arithmetic operation.
       | 
       | Where things get a bit more tricky is when working with data that
       | has some native unsigned type - e.g. when reading an image from
       | file, it will mostly consist of unsigned chars. There you also
       | need conversion rules and everyone aligned on algorithms, APIs
       | etc.
       | 
       | Mostly though it's a matter of maintainability - it's easier to
       | wrap your head around signed integers (no pun intended), even for
       | beginners.
        
       | jcelerier wrote:
       | Unsigned does not mean "not negative", it means "modular
       | arithmetic".
       | 
       | The only few cases you want that are e.g. hash functions, crypto,
       | etc. In all the other cases it's a mistake, and the "patterns"
       | shown in this article to circumvent the issues with unsigned just
       | for the sake of using it are extremely ridiculous I think and
       | much less readable than the normal code using signed integers.
       | 
       | If you have things that must never be negative, define a type
       | that does that and gives an error (there are many good safe_int
       | examples in C++) whenever an operation gives a negative number as
       | in that case you've already lost and your business logic / input
       | filtering / .... is wrong and needs to be fixed.
       | 
       | Anecdotally, I've written a few hundred thousand kloc of c++ so
       | far and I've never ever had a bug due to signed overflow.
       | Unsigned underflow OTOH... I'm just thankful for clang's ubsan to
       | warn on it because of how many issues it caught. For the immense
       | majority of programs you'll never have sizes close to 63 bits
       | anyways since the CPUs we use barely have 52-bits of address
       | space at most (and if you have more you're likely already using
       | 128 bit sizes)
       | 
       | > 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.
       | 
       | I'd take negative indice over silently wrong indice any day of
       | the week. The first will be caught hyper quickly, the second will
       | send money to the wrong account silently for a couple years
       | before anyone notices and then you're in much deeper issues.
        
         | virtualritz wrote:
         | > Unsigned does not mean "not negative", it means "modular
         | arithmetic".
         | 
         | It does mean "not negative" in languages with bounds checks.
         | Like Rust.
        
           | cies wrote:
           | Though, in idiomatic Rust I did not find the use of unsigned
           | as mentioned in the article, signed for indexes is the norm.
        
             | tialaramex wrote:
             | > in idiomatic Rust I did not find the use of unsigned as
             | mentioned in the article, signed for indexes is the norm
             | 
             | Huh? If you write things[n] in Rust, and n isn't usize
             | that's an error, you can't index things with the other
             | primitive types at all out of the box.
        
             | berkut wrote:
             | Wait, what?
             | 
             | Pretty much everything for indices (at least in the stdlib)
             | seems to be 'usize' in my experience (i.e. Vec), which is
             | unsigned.
             | 
             | Am I missing something?
        
           | whattheactualf3 wrote:
           | Is it called "unsigned" though?
           | 
           | In Racket (and in mathematics) we call those natural numbers.
           | The very notion of "unsigned" was invented by video game
           | programmers at Bell Labs. In mathematics positive numbers are
           | still signed (they just have a positive sign).
        
             | ncmncm wrote:
             | Unsigned types are supported in machine language.
             | 
             | Any language that offers access to features of the machine
             | will support unsigned numeric types.
             | 
             | No language supports natural numbers. The best you can get
             | is bignums. A language that calls its numbers natural is
             | just lying. (Likewise, integers, and reals.)
             | 
             | "int" is not a lie, it is a hint.
        
             | gumby wrote:
             | > The very notion of "unsigned" was invented by video game
             | programmers at Bell Labs.
             | 
             | PL/I had (has) unsigned integers back in the 60s, picking
             | up the idea from earlier languages. I believe the novel
             | datatype in PL/I was the CHARACTER type; into the 80s I was
             | still programming machines with variable length bytes.
             | 
             | In many ways PL/I heralded the current Ordovician stage of
             | programming languages, with the enormous Cambrian
             | flourishing of hardware-specific (and company-specific)
             | languages slowly dying out. Interestingly the only
             | surviving languages older than it, FORTRAN and LISP, are
             | also machine-independent.
        
             | masklinn wrote:
             | > Is it called "unsigned" though?
             | 
             | Yes: https://doc.rust-lang.org/std/primitive.usize.html
             | 
             | > The pointer-sized unsigned integer type.
             | 
             | > In Racket (and in mathematics) we call those natural
             | numbers.
             | 
             | Naturals have no upper bound. Most languages don't have
             | naturals, because they do have upper bounds. Languages
             | which do provide naturals (limited by the host machine)
             | usually don't bother with signing segregation, and give you
             | mathematical integers (again limited by the host machine's
             | capabilities).
        
         | kazinator wrote:
         | Unsigned means "modular arithmetic with a modulus that you do
         | not choose and that is implementation-defined unless you have
         | pinned down the bit width of the unsigned type, and that has
         | implicit conversions from signed types that drastically alter
         | the value".
         | 
         | Note that when mathematicians work in the area of congruences,
         | they do not use unsigned integers.
         | 
         | For instance, when we look at Euler's Theorem:
         | 
         | https://en.wikipedia.org/wiki/Euler%27s_theorem
         | 
         | all the quantities in the formula can be understood as just
         | integers, not unsigned integers.
         | 
         | The exponentiation of a can be understood as regular
         | exponentiation. The modularity plays out in the triple-equal-
         | sign operator and the (mod n) parenthetical part which says
         | that the two sides of the equation are equivalent in a
         | particular way.
         | 
         | The left side of the equation uses ordinary exponentiation and
         | can produce values >= n; it is not wrapped.
         | 
         | It's crystal clear that we cannot replace the triple equal sign
         | with the regular one, and drop the (mod n).
        
           | SAI_Peregrinus wrote:
           | They also don't use signed integers, in the sense that
           | computer languages use the term. Signed integers are _also_
           | bounded in extent, and for most systems wrap on over
           | /underflow. Euler's Theorem doesn't hold for them any more
           | than it does for unsigned integers.
           | 
           | Don't treat computer "integers" like mathematical integers.
           | It leads to pain. They're members of a few finite fields.
        
         | quelsolaar wrote:
         | Interesting perspective. I dont have the same experience. To me
         | a wraping 32 bit index on a 64bit machine is much more likely
         | to seg fault if it reads 4 billion values forward, rather than
         | a few bytes backwards and therefore i find unsigned index
         | wrapps to be easier to debug. In all fairness wrapping indicies
         | are an extremely rare bug for me, so i cant say i have much
         | experience debugging it.
        
           | eterevsky wrote:
           | I think when you use a variable that can have values in the
           | range of millions, it's prudent to use 64-bit types and not
           | 32-bit unless you are very much memory-constrained. And I've
           | never seen a 64-bit signed variable overflow.
        
           | jcelerier wrote:
           | > In all fairness wrapping indicies are an extremely rare bug
           | for me, so i cant say i have much experience debugging it.
           | 
           | Do you run your code with -fsanitize=undefined
           | -fsanitize=integer ?
        
             | quelsolaar wrote:
             | No I do not. I run ISO conformant C without options.
        
               | jcelerier wrote:
               | You really should try it on your codebase if you have the
               | occasion
        
         | CasperDern wrote:
         | > Unsigned does not mean "not negative", it means "modular
         | arithmetic".
         | 
         | Unsigned does mean non-negative (or rather no indication of
         | negatives) as the sign of a number is just the positive or
         | negative factor of a number: 1 or -1.
        
         | hamburglar wrote:
         | The suggestion of using an unsigned loop counter starting at
         | size-1 and decrementing until "i<size" is no longer true starts
         | this article off with a "this is bad advice" flavor to it in my
         | book. If I encountered that code I'd stare at it way longer
         | than necessary because I'd be trying to figure out what
         | cleverness is hidden in the bizarre/backwards way the loop
         | continuation test is written. I don't know why you'd do this to
         | someone else who has to read your code.
        
           | reallybigballs wrote:
        
           | xxs wrote:
           | It's an idiom - obviously very little known, likely less
           | known than comparing floats to themselves being a NaN check.
           | 
           | The standard idiom for doing a reverse loop would be: "for
           | (int i = size; i-- > 0; ) ...". As a side benefit it has
           | lower registry pressure. Some folks have issues reading that
           | one as well.
           | 
           | Saying all that not being 'unsigned fan', just the popularity
           | of the idioms comes with their use frequency, and given that
           | 'unsigned; is not popular at all...
        
         | AnotherGoodName wrote:
         | >Unsigned does not mean "not negative", it means "modular
         | arithmetic".
         | 
         | Signed is modular arithmetic with an offset.
         | 
         | An unsigned char called i can be thought of as "i mod 256"
         | 
         | A signed char called i can be thought of as "((i+128) mod 256)
         | - 128"
         | 
         | All results for over/underflow hold for 'i' cast into a char if
         | you do the above in both cases.
         | 
         | I think the premise of the article is quite reasonable. It's
         | slightly easier to reason about over/underflow for unsigned
         | arithmetic since it's not offset.
        
           | jcelerier wrote:
           | > Signed is modular arithmetic with an offset.
           | 
           | it is not, it models integers (Z). The model only works in
           | the bounds of what the platform can offer of course ; when
           | you use "int" you say "this is an integer. In any case I'm
           | supposed to encounter, adding two positive integers will
           | yield a greater positive integer ; if I add numbers so big
           | that my platform cannot represent the sum my program is
           | meaningless anyways".
           | 
           | It is very unlike unsigned which models (much more accurately
           | since it's much easier for our computers with finite memory)
           | Z/pZ.
        
             | AnotherGoodName wrote:
             | Signed and unsigned are actually treated internally the
             | same on computers.
             | 
             | The reason you see -2 printed out when you add signed chars
             | -1 and -1 together is because adding 255 and 255 together
             | gives 254 under mod 256. The signed model that allows
             | negative numbers relies on the fact that all numbers on
             | computers are modular arithmetic.
             | 
             | So in that sense any argument that unsigned is modular and
             | signed isn't is incorrect. You're better off choosing the
             | one that forces you to consider how the computer actually
             | operates under the hood.
        
               | [deleted]
        
               | jcelerier wrote:
               | > Signed and unsigned are actually treated internally the
               | same on computers.
               | 
               | if you are writing code in C or C++ the computer you are
               | programming for is the C / C++ abstract machine. C++
               | recently sanctified complement-of-two as integer
               | representation, but C does not and supports machines with
               | one's complement representation.
        
               | kryptiskt wrote:
               | One's complement is on its way out in C too. The next C
               | standard will require two's complement.
        
               | leni536 wrote:
               | > C++ recently sanctified complement-of-two as integer
               | representation
               | 
               | While that's true, signed integer overflow on arithmetic
               | operations remains undefined behavior.
        
         | ghusbands wrote:
         | > Unsigned does not mean "not negative", it means "modular
         | arithmetic".
         | 
         | In which case it has a terrible name. The name clearly
         | indicates that the primary feature is that it is not signed,
         | and the logical conclusion from that is that it is not
         | negative.
         | 
         | That said, I agree; using signed types in C/C++ does generally
         | seem safer and a majority of the techniques listed in the
         | article are unsafe, logically confused, and/or inhibit
         | optimisation. Using signed types in languages like C# is also
         | generally more pleasant, because otherwise you end up with a
         | lot of casts just to interact with the base types (like
         | arrays), language features and third-party libraries.
        
           | Noughmad wrote:
           | > The name clearly indicates that the primary feature is that
           | it is not signed, and the logical conclusion from that is
           | that it is not negative.
           | 
           | No, the logical conclusion is that since it has no sign, you
           | don't know whether it's positive or negative. Otherwise the
           | name would be "positive" or "non-negative", not "unsigned".
           | 
           | And this conclusion would also be correct.
        
           | SAI_Peregrinus wrote:
           | Signed _also_ tends to mean modular arithmetic. It 's just
           | with a different definition of modular arithmetic than the
           | Euclidean one. See Boute's 1992 paper "The Euclidean
           | Definition of the Functions div and mod" [1] for details on
           | the various definitions.
           | 
           | Both signed and unsigned arithmetic are using finite fields,
           | instead of Abelian rings as most people expect.
           | 
           | [1]
           | https://biblio.ugent.be/publication/314490/file/452146.pdf
        
             | jacobolus wrote:
             | > _Both signed and unsigned arithmetic are using finite
             | fields,_
             | 
             | This is not correct. They are rings with some additional
             | (not mathematically standard) operations thrown in.
             | 
             | 'Finite field' means something very specific, and quite
             | different. https://en.wikipedia.org/wiki/Finite_field
             | 
             | Computer integer arithmetic hardware can be used to
             | implement finite field arithmetic, but even the 2^n case
             | takes quite a bit of trickery.
             | https://en.wikipedia.org/wiki/Finite_field_arithmetic
        
           | xxs wrote:
           | >The name clearly indicates that the primary feature is that
           | it is not signed, and the logical conclusion from that is
           | that it is not negative.
           | 
           | Years ago, when the names were designated the CPUs didn't
           | even have proper signed instructions. There was a sign flag,
           | and that was all. In that regard the unsigned was the natural
           | CPU sympathetic type.
        
             | int_19h wrote:
             | And yet the default type for variables and function
             | arguments in C was (before it got deprecated) signed int,
             | not unsigned.
        
               | xxs wrote:
               | signed/unsigned stuff predates C, though. My (lack of)
               | experience with C came much later, when I had used
               | assembly for 3 different CPUs already, so no recollection
               | of the times.
        
               | int_19h wrote:
               | If you mean the default being signed, it's there because
               | B - the predecessor of C - only had a signed integer
               | (machine word) type. But note that this is about the case
               | when the variable has no type at all - e.g. "auto x" is a
               | signed int. They still had the choice of "int x" being
               | equivalent to either "signed int" or "unsigned int" by
               | default, though - since there's no backwards
               | compatibility issue there - and chose signed.
               | 
               | FWIW, signed seems to be the default for most other
               | languages from that time period, and often the only
               | option - e.g. Algol-60 didn't have unsigned integers at
               | all. Pascal kinda sorta did, if you defined an integer
               | subtype with 0 as the lower bound... but the upper would
               | still be that of signed int, and it'd behave as such. And
               | Algol-68 called its unsigned type BITS - a pretty strong
               | hint that they didn't think of it as arithmetic.
               | 
               | I'm actually kinda curious now as to when the concept of
               | unsigned integers first appeared in a high-level
               | programming language first.
        
       | tialaramex wrote:
       | > this concept applies to all languages, regardless of their
       | choices
       | 
       | No it doesn't.
       | 
       | Neither signed nor unsigned integers in WUFFS permit overflow or
       | underflow, at runtime the integers behave the way you were taught
       | in school. Adding two positive numbers together always results in
       | another positive number, because duh. WUFFS catches all the cases
       | this programmer worries about _at compile time_ and insists you
       | fix them.
       | 
       | One of the first examples WUFFS presents you is what if we take
       | this correct code for a well known operation and we tweak it so
       | that it can overflow. The compiler rejects the modified code,
       | pointing out that it could now overflow and so isn't valid WUFFS.
       | 
       | Now, WUFFS is a special purpose language, you can't rewrite your
       | 5MLOC C++ system in WUFFS. But then again, this shouldn't make
       | you feel glad (because C++ is so powerful) but instead sad
       | (because C++ can't offer you this valuable defence against your
       | own stupidity). It should also make you determined to use WUFFS
       | everywhere you possibly could, although doubtless it won't.
        
       | ajuc wrote:
       | > delta = abs(x - y);
       | 
       | > The argument is that unsigned is dangerous here because if y >
       | x then you get underflow. The problem with this argument is it's
       | not valid because the code itself is simply incorrect regardless
       | of the signedness of x and y. There are values for both x and y
       | which will lead to signed integer underflow. So like before, in
       | languages like C and C++, you just unconditionally invoked
       | undefined behavior since signed integer underflow is undefined.
       | 
       | Yeah because x=1, y=2 is just as likely an input as
       | x=MIN_SIGNED_INT, y=1.
        
       | Asooka wrote:
       | The author makes good points, but the takeaway I get is that
       | signed under/overflow should always be defined, maybe in a
       | platform specific way. The one good argument for leaving it
       | undefined was for better loop optimisation, but if we're going to
       | use unsigned for loops, then what's the point.
        
       ___________________________________________________________________
       (page generated 2022-01-02 23:02 UTC)