Live data from Hacker News

Unsigned sizes: A five year mistake

c3-lang.org

71–80 of 153 posts

Re: Unsigned sizes: A five year mistake

#71

Earlier quoted context omitted.

> There are times when you really really need to use the full word range without negative values. There are a few of those, but that is the niche case. Certainly when we're talking about 64-bit size types. And if you want to cater to smaller size types, then just just template over the size type. Or, OK, some other trick if it's C rather than C++.

Sometimes (and very often in some scenarios/industries, i.e. HPC for graphics and simulation with indices for things like points, vertices, primvars, voxels, etc) you want pretty good efficiency of the size of the datatype as well for memory / cache performance reasons, because you're storing millions of them, and need to be random addressing (so can't really bit-pack to say 36 bytes, at least without overhead away f…

> HPC for graphics and simulation with indices

Those are not sizes of data structures.

> Losing half the range

It's not a part of the range of sizes they can use, with any typical data structure.

> Losing half the range to make them signed when you only care about positive values 95% of the time is just a bad trade-off.

It's the right choice sizes in the standard library (in C++) or standard-ish/popular libraries in C. And - again, it's the wrong type. For example, even if you only care about positive values, their difference is not necessarily positive.

Re: Unsigned sizes: A five year mistake

#72

Earlier quoted context omitted.

That’s definitely not true. Unsigned ints have no “negativity” semantic. Wrapping around is what happens when you decrement the minimum value of any integer type, including signed types. Regardless of the type you use to represent an integer value that cannot legally be negative, you will have to take care not to allow your program to return values lower than zero for things like indices or sizes.

> Wrapping around is what happens when you decrement the minimum value of any integer type, including signed types. No, signed wraparound is undefined behavior in C, whereas unsigneds are defined to wraparound. If you use -ftrapv, signed wraparound is an immediate abort().

This feels like you’re trying to “gotcha” me, but I didn’t say C specifies 2s complement wraparound, I said that’s what happens in virtually every language (including the major C/C++ implementations). It seems like you are “violently agreeing” with me.

Re: Unsigned sizes: A five year mistake

#73

Earlier quoted context omitted.

That’s definitely not true. Unsigned ints have no “negativity” semantic. Wrapping around is what happens when you decrement the minimum value of any integer type, including signed types. Regardless of the type you use to represent an integer value that cannot legally be negative, you will have to take care not to allow your program to return values lower than zero for things like indices or sizes.

> Unsigned ints have no “negativity” semantic. They do. The code: unsigned x; unsigned y = -x; is well-defined in C and C++. See this discussion on StackOverflow for spec text and reference: https://stackoverflow.com/q/8026694/1593077

That’s not a negativity semantic, it’s the behavior of the “-“ operator. If you print y or compare it to zero you will see that the result remains positive. Unsigned integers by definition have no negative semantic, hence the name.

Re: Unsigned sizes: A five year mistake

#74

I am personally moving in the opposite direction. I haven't meaningfully used a signed integer in years, and I see signed integers as being for more niche use-cases. I mainly only use a signed types when I want to do a "signed shift right". If there was a >>> operator in Zig I wouldn't even think of signed integers. Given your examples, I think you'd have fewer issues if you were working with unsigned integers exclus…

Zig also differentiates between the wrapping and non-wrapping operators. The for loop example would toss a runtime error when the index underflowed in most compiler modes.

The if statement won't work since Zig would force a cast.

The tricky wrap sucks unless you use a power of 2. Then the Zig type can match (u4, u5, u7, etc.) and you would use wrapping arithmetic operators. And on smaller CPUs you NEED to use a power of 2 because division and mod are expensive.

Re: Unsigned sizes: A five year mistake

#75
post #20
post #4

I know language designers have a lot of trade-offs to consider... But I would say if you know a value will logically always be >= 0, better to have a type that reflects that. The potential bugs listed would be prevented by, e.g. "x--" won't compile without explicitly supplying a case for x==0 OR by using some more verbose methods like "decrement_with_wrap". The trade-off is lack of C-like concise code, but more safe…

> But I would say if you know a value will logically always be >= 0, better to have a type that reflects that. Except that's not quite what unsigned types do. They are not (just) numbers that will always be >= 0, but numbers where the value of `1 - 2` is > 1 and depends on the type. This is not an accident but how these types are intended to behave because what they express is that you want modular arithmetic, not no…

> you also run into difficulties with `x - y`.

If you have "uint x" and "uint y", then for "x - y", the programmer should explicitly write two cases (a) no underflow, i.e. x >= y, and (b) underflow, x > what about variables that can only ever be even

Yes, maybe you should have an "EvenInt" type, if that is important. Maybe you should be able to declare a variable to be 7...13, just like a "uint8" can declare something 0...255. Of course, the type-checker can get complicated, and perhaps simply fail to type-check some things. But, having compile-time constraints to what you know your variables will be is good, IMHO.

Re: Unsigned sizes: A five year mistake

#76
post #43
post #29

Earlier quoted context omitted.

> As long as you keep your values and types separate, the unsigned type that you got a number from originally feeds just fine into the unsigned type that you send it to next. Part of me feels like direct numeric array indexing is one of the last holdouts of a low-level operation screaming for some standardized higher-level abstraction. I'm not saying to get rid of the ability to index directly, but if the error-resis…

Maybe this isn't what you're suggesting, but it's already possible to make an interface that prevents callers from doing math on indices in Rust — just return a struct that has a private member for the index. The caller can pass the value back at which point you can unwrap it and do index arithmetic.

More than that, in theory an opaque handle would also do things like statically prevent a handle taken from one array from being used to access a different array. I feel like this should be possible in Rust with type-level shenanigans (e.g. GhostCell).

Re: Unsigned sizes: A five year mistake

#77

Systems programmers love to hate on unsigned integers. Generations have been infected with the Java world model that integers have to be pretend number lines centered on zero. Guess what, you still have boundary conditions to deal with. There are times when you really really need to use the full word range without negative values. This happens more often with low level programming and machines with small word sizes,…

> Systems programmers love to hate on unsigned integers

I don't see this hate in Rust. I think this is a big thing in the C-related languages, and that the author has chosen to pretend that's the same for any "systems language" but it is not.

Re: Unsigned sizes: A five year mistake

#78

Bjarne agrees. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p14...

There is a really convincing set of arguments against this idea by Robert Seacord[1]. I used to be in the signed size camp, but I've come around to preferring unsigned as much as possible because it's much easier to reason about. I think there are far more footguns than people realize when it comes to signed integers.

[1] https://www.youtube.com/watch?v=82jVpEmAEV4

Re: Unsigned sizes: A five year mistake

#79
post #25

> But what about the range? While it’s true that you get twice the range, surprisingly often the code in the range above signed-int max is quite bug-ridden. Any code doing something like (2U * index) / 2U in this range will have quite the surprise coming. Alas, (2S * signed_index) / 2S will similarly result in surprises the moment the signed_index hits half the signed-int max. There's no free lunch when trying to che…

The difference is that in the unsigned case you get a seemingly plausible value, and in the signed case you get a negative value which you can be sure is wrong. This is the problem.

In some languages, the signed version is undefined behavior. You may get a negative value, INT_MAX / 2, or an error. Or the compiler may detect the undefined behavior, which according to the standard cannot happen, and mutilate your code in unexpected ways.

Re: Unsigned sizes: A five year mistake

#80

Earlier quoted context omitted.

Sometimes (and very often in some scenarios/industries, i.e. HPC for graphics and simulation with indices for things like points, vertices, primvars, voxels, etc) you want pretty good efficiency of the size of the datatype as well for memory / cache performance reasons, because you're storing millions of them, and need to be random addressing (so can't really bit-pack to say 36 bytes, at least without overhead away f…

As I generally believed in Moore's law, i.e., accepted the notion that transistors were exponential, I was surprised at how long the difference between a 2 GiB address space and a 3 GiB address space was relevant in practice. In theory, it should have been at most a year. In practice, Windows XP /3GB boot switch (allocates 3 GB of virtual address space user mode and 1 GiB for the kernel instead of the usual 2 and 2)…

If 64-bit was an easy option for you, the transition wasn't after the /3GB switch, it typically happened at about 1GB RAM and yeah, it wasn't very long as you imagined because of Moore's law.

So that /3GB switch is for people who are stuck on the wrong hardware for a variety of reasons, and the timing is about how long those people stayed trapped rather than how long before this became a bad idea (it was a bad idea before it even shipped, but it was necessary)

Linux had some more extreme splits including a 3.5:0.5 split and a nasty 4:4 split (in which all the userspace addresses are invalidated when in kernel space, ugh) and it's for the same reason, these aren't customers who chose not to go to 64-bit, they're customers who can't yet and will pay $$$$ to keep what they are doing working for just a while longer anyway despite that.

Post reply on HN