Tom K
Tom K

Reputation: 319

Why do C variables maximum and minimum values touch?

I am working on a tutorial for binary numbers. Something I have wondered for a while is why all the integer maximum and minimum values touch. For example for an unsigned byte 255 + 1 = 0 and 0 - 1 = 255. I understand all the binary math that goes into it, but why was the decision made to have them work this way instead of a straight number line that gives an error when the extremes are breached?

Upvotes: 1

Views: 171

Answers (2)

anatolyg
anatolyg

Reputation: 28269

Why not "give an error when the extremes are breached"?

Error handling is one of the hardest things in software development. When an error happens, there are many possible ways software could be required to do:

  • Show an annoying message to the user? Like Hey user, you have just tried to add 1 to this variable, which is already too big. Stop that! - there often is no context to show the user, that would be of any help.
  • Throw an exception? (BTW C has support for that) - that would show a stack trace, if you happened to execute your code in a debugger. Otherwise, it would just crash - not bad (it won't corrupt your files) but not good either (can be exploited as a denial of service attack).
  • Write it to a log file? - sometimes it's the best thing to do - record the error and move on, so it can be debugged later.

The right thing to do depends on your code. So a generic programming language like C doesn't want to restrict you by providing any mandatory behavior.


Instead, C provides two guidelines:

  • For unsigned types like unsigned int or uint8_t or (usually) char - it provides silent wraparound, for best performance.
  • For signed types like int - it provides "undefined behavior", which makes it possible to "choose", in a very limited way, what will happen on overflow

The idea here is that you (the programmer) should think where checking for overflow is worth doing, and how to recover from overflow (if the language provided a standard error handling mechanism, it would deny you the latter part). This approach has maximum flexibility, (potentially) maximum performance, and (usually) hardest to do - which fits the philosophy of C.

Upvotes: 1

user555045
user555045

Reputation: 64904

Since your example is unsigned, I assume it's OK to limited the scope to unsigned types.

Allowing wrapping is useful. For example, it's what allows you (and the compiler) to always reorder (and constant-fold) a sequence of additions and subtractions. Even something such as x + 3 - 1 could not be optimized to x + 2 if the language requires trapping, because it changes the conditions under which the expression would trap. Wrapping also mixes better with bit manipulation, with the interpretation of an unsigned number as a vector of bits it makes very little sense if there's trapping. That applies especially to shifts, but addition, subtraction and even multiplication also make sense on bitvectors and combine usefully with the usual bitwise operations.

The algebraic structure you get when allowing wrapping, Z/2kZ, is fairly nice (perhaps not as nice as modulo a prime, but that would interact badly with the bitvector interpretation and it doesn't match typical hardware) and well known, so it's not like anything particularly unexpected or weird will happen, it's not like a wrapped result is a "uselessly arbitrary" result.

And of course testing the carry flag (or whatever may be required) after just about every operation has a big direct overhead as well.

Trapping on "unsigned overflow" is both expensive and undesirable, at least if it is the default behaviour.

Upvotes: 7

Related Questions