5 ms·
I'm still convinced that C made the wrong decision to have abs(int) return signed int. Returning unsigned int would've been more semantically accurate, as well
by nyanpasu64 5y ago
I'm still convinced that C made the wrong decision to have abs(int) return signed int. Returning unsigned int would've been more semantically accurate, as well as eliminating a source of UB. At least Rust has unsigned_abs() now.
- dataflow 5y agoI think that would've only helped a little bit. I imagine most instances of this problem don't occur in abs() (which is just an illustration of the underlying problem), but when people perform simple negation.
- User23 5y agoThe decision is so (apparently) obviously wrong that I'd really like to know what the decision process was. My first guess is that it involved some kind of architectural peculiarity of the PDP11.
- bsder 5y ago1) Only "Two's Complement" signed arithmetic is asymmetric. One's Complement arithmetic wasn't uncommon on computers of the era and is fully symmetric. And, I believe that a PDP-7 uses one's complement. 2) "unsigned" didn't get implemented until about 5 years after C was initially implemented.
- kevin_thibedeau 5y agofabs() returns a signed result so abs() mirrors its behavior. Also if you're taking an absolute value, you're working in close proximity to negative numbers and risk accidentally performing subsequent operations on mixed types that cause an implicit cast from signed to unsigned. C's integer promotion rules allow this to happen.
- zarzavat 5y agoIf abs() returned unsigned then an expression such as -abs(x) would be surprising and wrong.
- jepler 5y agoAnalyzing uses of abs(SIGNED) in a large software system several years ago, I found that the most useful replacement was `UNSIGNED absdiff(SIGNED a, SIGNED b)`. The _absolute_ difference of two N-bit signed numbers can always fit in an N-bit unsigned number, but lots of `abs(a-b)` involve intermediate values which don't fit in the SIGNED type and thus invoke undefined behavior. The system was in C++, so it was possible to provide an absdiff template that covered all integral types. However, actually using it tended to introduce cascading type changes because the types of the expressions `abs(a-b)` and `absdiff(a,b)` differed.
- jlokier 5y agoIf abs(int) were being defined now in C, I would advocate that the return value be signed int. Why? Because it's quite likely the result will be used in arithmetic expressions involving other signed ints and/or subtraction. That goes with the kinds of things you'd use normally abs() for. There will be some cases where abs() is being used by someone on a value that could be INT_MIN. But when your code might have to handle the full range of possible integers (signed or unsigned), there are probably many other things to get right as well. In such code, abs() is the least of your worries. I think most uses of abs() are in arithmetic expressions of "numbers whose values we don't expect to overflow", and I'm fairly confident if abs() returned unsigned int, there would be more bugs that nobody spotted in the world than with it returning signed int. Not many more, because abs() is rarely used, but a few. I used to be one of those people who felt it made sense to use unsigned types in C for values that can never be negative. That was tidier and clearer. It stated my intent. After a few years of coding in C like this, I changed my mind: I spotted occasional little hidden bugs here and there, undetected by the compiler or the programmer, from overzealously using unsigned types just for "showing intent" where signed would have been fine. It's not like using unsigned types prevents arithmetic bugs in C in practice (unless you really have values exercising most of the unsigned range). The "use unsigned because types should reflect intended range" argument is muddier than it first looks: The range [0..2^B-1] is no more "correct" than [-2^(B-1)..2^(B-1)-1] for almost all quantites, as people often aren't paying attention to correct handling of numbers in the upper end of the unsigned range either. They rely on "practical numbers are small enough that it doesn't matter", same as with signed ints. If unsigned acted as a range-constrained arithmetic type in C, meaning "this variable can only hold a subset of the default integer type" and "arithmetic with other types is consistent", that would be different. But it doesn't. It acts more like an unsignedness virus in C arithmetic, adding unnecessary boundary conditions into innocent-looking expressions. Of course you can be aware of these issues and avoid them. I'm pretty experienced and can avoid such issues easily. But having to be careful for no added benefit, especially with multiple people, just raises risks. So now I default to, and advocate, sticking with signed integer types for values representing arithmetic quantities. If I were designing a language, I'd probably advocate for ranged types instead. That means limited to ranges like [0..2^(B-1)-1] (the upper half of the signed range), and have consistent arithmetic. But C is not like that. Unsigned types in C are of course completely appropriate for bitwise and modular arithmetic uses, and for holding raw data. I'm a big fan of using them for relative timestamps using modular arithmetic comparisons, as done in Linux. If you're writing compression routines or a database engine they will be very useful. But for general arithmetic quantities, nowadays my view is similar to this answer: https://stackoverflow.com/questions/51677855/is-using-an-unsigned-rather-than-signed-int-more-likely-to-cause-bugs-why/51682460#51682460 https://stackoverflow.com/questions/51677855/is-using-an-uns.... I'm sympathetic to the "unnecessary discontinuity near common values" and "arithmetic closure is more useful" view. People won't agree on this. They don't agree in answers to that SO question. All I can say is, I used to be zealously "use unsigned for non-negative quantities", and then after some experience it seemed more pragmatic and safe-by-default to stop doing that, and the type-as-intent was misguided anyway when the upper range of unsigned wasn't being used. You still have to be unsigned-aware in C due to size_t especially. So I still use unsigned-by-default for arithmetic dancing around object sizes, memory and offsets in C.