Almost Always Unsigned

(graphitemaster.github.io)

24 points | by gavide 5 hours ago ago

25 comments

  • dataflown 2 hours ago

    Stroustrup recommends int over unsigned. Dijkstra recommends int over unsigned. Google coding guidelines recommend int over unsigned.

    Blogger recommends unsigned over int.

    Tough choice.

    • StellarScience an hour ago

      I liked how the discussion of 'delta = x - y' moved right on to how really you usually want delta = abs(x - y), so let's talk about that instead...

      Even beyond Stroustrup, Dijkstra, and Google, this whole panel of C++ luminaries agrees to prefer signed types and explains pretty clearly why:

      - 12:12-13:08 - https://www.youtube.com/watch?v=Puio5dly9N8#t=12m12s

      - 42:40-45:26 - https://www.youtube.com/watch?v=Puio5dly9N8#t=42m40s

      - 1:02:50-1:03:15 - https://www.youtube.com/watch?v=Puio5dly9N8#t=1h2m50s

      • nazcan an hour ago

        Thanks for the excerpts! I was trying to understand the reasoning, which seem to just be in the 2nd excerpt:

        - The rules of signed/unsigned are complicated and there is too much auto-conversion - does that mean languages that make this more explicit means this is fine? It just seems ideal to have stronger typing. - It is mentioned that you can initialized an unsigned int to "-2" - but that presumably could also be fixed in the language.

        I'm trying to separate out which is "don't do this in C/C++" and which is "don't do this in any language".

        • Groxx 39 minutes ago

          that argument (and many others) also round to essentially "implicit imprecise integer casting is surprising but we do it anyway" which is... perhaps the actual problem???

          I've made lints for Go that simply disallow implicit number casting, and omfg the (real, occurring but unnoticed) bugs it found. those kinds of lints are trivial to build, you can just stop doing it. forcing visible casts made many of these problematic patterns extremely suspicious at a glance, catching issues at review time far more easily.

    • leecommamichael 24 minutes ago

      Commenter implies authority dictates strategy.

      We should be engaging with the article's content.

    • Groxx an hour ago

      signed overflow (or underflow) is frequently undefined behavior. (often because it's undefined in C)

      unsigned is frequently defined. (often because it's defined in C)

      tough choice.

      (honestly I just lean towards "over/underflow should raise unless explicitly allowed", the ratio of unintended to intended-and-fully-checked overflow behavior is almost certainly FAR beyond 100:1)

  • pikuseru 17 minutes ago

    What if I’m making a 2d game and need to go left

  • cpeterso an hour ago

    There was a golang proposal to change Go's default int type to arbitrary precision big int. It would avoid overflow bugs, but the proposal was closed (after eight years) due to concerns about compatibility reading serialized data and performance.

    https://github.com/golang/go/issues/19623

    • Groxx an hour ago

      as much as I'm not really a fan of Go, and I would love to spend some time in a language that defaults to unsigned ints by default to see just how pleasant or painful it really is, I do think Go makes a reasonable tradeoff here.

      signed ints are rather obviously the majority decision, so that default is defensible, and their const / compile-time math is arbitrary precision. so as long as you can describe your math as either wholly const or can move all overflow-risky stuff into a const equation, you're good - Go's compiler will fail if it exceeds the bounds of whatever number you try to cast it to (implicitly or explicitly).

      ignoring fun with float rounding, of course.

  • bogdanoff_2 41 minutes ago

    You can count down to zero with while(i--)

  • pmarreck 2 hours ago

    Related: I have a variable integer length encoding scheme that beats out LEB128, Protobuf, varint and ASN.1 while also encoding/declaring endianness (but notably, leaving signage information up to the application): https://github.com/pmarreck/BLIP

  • scared_together 2 hours ago

    There was a related article from the other side of the debate a while back: https://news.ycombinator.com/item?id=47989154

    It’s pretty sad that after all these years, dealing with fixed size integers is still so complicated. Yes, many of the problems are specific to low level languages with undefined behaviour and numeric for loops. But the issue of subtracting two numbers and possibly having an underflow is both common and a bit absurd.

    The code in the article for “safely” calculating the difference of two unsigned numbers, which is simpler than the equivalent for signed integers, is this little ritual:

    > delta = max(x, y) - min(x, y);

    Seriously??? Two function calls just for the difference of two numbers??

    Why can’t such “safe” operations have some of the sweet syntactic sugar, and the underflow-rampant “ordinary” operations have the bitter medicine of ritual?

    • xyzzyz 14 minutes ago

      For the stuff I do, I rarely use signed types. I find the example of finding difference between two numbers to be really silly. I don’t recall ever having to do anything like that, for a simple reason: I design my code so that I always know which number will be larger than the other.

      Let’s actually talk more about this max - min example. Let’s say you calculated this delta. What is it useful for in your code? Typically, in real algorithms, what you want is to not only know the delta, but also which number is larger.

    • jandrewrogers an hour ago

      The issue is that you need dozens of minor variants for the myriad cases involving integers, especially in C. That namespace becomes rather busy.

      This particular idiom for finding the absolute difference of an unsigned integer pair is pretty clean and rarely needed. In most contexts you know that one argument will always be greater than or equal to the other, so a simple subtraction will do.

    • tialaramex 2 hours ago

      I mean, Rust calls this function you want abs_diff

          let a = 1234_u32;
          let b = 5678_u32;
          let c = a.abs_diff(b); // Or equivalently u32::abs_diff(a, b);
      
      Some languages hate offering convenience functions, in particular in a language like C or one of the would-be C-replacements, those functions just clog up the same namespace as everything else, so having 100 intrinsic arithmetic functions for integers feels disproportionate. The C-like languages, even those which do have methods, often forbid methods on their "built-in" or "core" types like integers so they can't do what Rust did here.

      Edited: tweaked language

  • tialaramex 2 hours ago

    Should be (2022) apparently - surely if HN can automatically screw up titles for various reasons, we can have it add dates automatically [and sometimes get those wrong] too? DanG ?

    > for instance C and C++ leave signed integer wrap undefined

    I'm pretty sure the way to write what was meant here is "C and C++ leave signed integer overflow undefined". Wrapping would be a definite choice. Overflow is the situation we're considering, not a particular outcome to choose so that is what's undefined.

    The confusion gets worse later when it insists that just like in C or C++ these three Rust expressions will produce invalid results because of LLVM:

        x / 0
        INT_MIN / -1
        INT_MAX % -1
        INT_MAX - INT_MIN
    
    Assuming we defined INT_MAX and INT_MIN as say i32::MAX and i32::MIN (or whichever signed type you prefer) of course what these actually do in Rust is just panic. If you write this in a context where it'll be evaluated at compile time, your compilation fails. That's not "invalid" in any sense I understand.

    It mentions Odin too, I know less about Odin but I believe it too will reject this nonsense, on Godbolt it seems to either SIGILL (for zero) or SIGFPE (for other impossible operations)

    Edited: Apparently I copy-pasted wrong? Some of those invalid expressions were not as written on the blog post but are now hopefully fixed. They are, of course, still not invalid in Rust, some panic because they aren't valid questions (like dividing by zero), others are fine - neither case is a problem.

    • leecommamichael 21 minutes ago

      https://odin-lang.org/docs/overview/#integer-overflow

      > For signed integers, the operations +, -, *, /, and << may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation. Overflow does not cause a runtime panic. A compiler may not optimize code under the assumption that overflow does not occur. For instance, x < x+1 may not be assumed to be always true.

    • gumby an hour ago

      > for instance C and C++ leave signed integer wrap undefined

      Not in C++29, and I think the big 3 compilers (gcc, clang, MS) will have squashed this long before that version is officially approved.

      • tialaramex an hour ago

        > Not in C++29

        C++ 29 doesn't yet exist. It'll be finalised in (as its name suggests) 2029. Are you referring to some proposal you think has been accepted? Or is this one of those "I have concepts of a proposal?" ideas where you confused wishful thinking with reality ?

  • dataflow 2 hours ago

    > for (size_t i = size - 1; i < size; i--)

    Erm... just because you can, doesn't mean you should.

    Also, what if you want to go down to something other than 0?

    • nulltrace an hour ago

      That loop reads like a bug to anyone who hasn't memorized the wrapping rules. while (i-- > 0) on a signed index does the same thing.

    • StellarScience an hour ago

      > for (size_t i = size - 1; i < size; i--)

      Agreed, seeing that example briefly made me consider whether this blog post was a parody. Sure, it works for this exact example, by relying on i wrapping "down" to MAX_INT on the last iteration. But how long will it take the next developer who works on the code base to figure that out? Will they figure it out before or after committing changes that break it? Or worse yet, before or after shipping code?

    • AlotOfReading an hour ago

      I really don't see what's supposedly awful about that loop, but if you want to count down to x instead of 0 you just do:

          for (size_t i = size - 1; i >= x; i--)
      • dataflow an hour ago

        > I really don't see what's supposedly awful about that loop

        The stopping condition is incredibly confusing and non-obvious. Misleading at first glance, in fact. The whole thing is so unidiomatic that I don't think I've even seen it once in my life. It's a better contender for an underhanded C++ code contest than production code.

        > but if you want to count down to x instead of 0 you just do i >= x

        No you can't. That fails if x == 0. Which perfectly illustrates why using unsigned everywhere isn't so great. And I say this as someone who likes unsigned types and uses them more than average!

      • StellarScience an hour ago

        > I really don't see what's supposedly awful about that loop

        Exactly! That's precisely the problem with it.

        (Hint: think about your code when size = 0.)