3 ms·
When Ritchie created C, he made arithmetic wrap because that’s what arithmetic did in assembly and C started as a thin layer over assembly. The real question i
by comex 3y ago
When Ritchie created C, he made arithmetic wrap because that’s what arithmetic did in assembly and C started as a thin layer over assembly. The real question is why did arithmetic do that in assembly – in other words, why didn’t the CPU automatically trap on overflow? Today, CPUs are designed to run the output of optimizing compilers, and optimizing compilers really like arithmetic to be “pure”, able to be hoisted in and out of loops and conditionals without having to worry about introducing or removing overflow errors. So there’s not much interest in hardware overflow checking. But when C was invented, CPUs were primarily designed to run hand-coded assembly. So why didn’t they trap by default? …Well, I don’t know. Probably because it’s simpler that way. But someone better acquainted with the history might have a better answer.
I can think of some cases where wrapping comes in handy.
Suppose you want to compute `a + b - c`. Mathematically speaking, the addition and subtraction should be associative. You shouldn’t have to care about the difference between `(a + b) - c` and `a + (b - c)`, since they should be equivalent. Using commutativity of addition, you can also rewrite it as `(a - c) + b`, or `b + (a - c)`, and those too should be equivalent. With wrapping, they are equivalent! All of those expressions will always compute the same value, and it will be the mathematically correct value as long as said value fits into the integer type you’re using – even if the intermediate values don’t. This property is true for any series of additions, subtractions, and multiplications (though not divisions). In contrast, with overflow-checked arithmetic you have to think about which order is ‘correct’, i.e. which one is guaranteed not to overflow given whatever invariants you have in place.
…But that’s only useful if the final result is guaranteed not to overflow. Or if you don’t care if it overflows. If it can overflow and you do care, then you probably need to overflow-check the intermediate computations anyway, so wrapping just gets in the way.
- Animats 3y agoBut when C was invented, CPUs were primarily designed to run hand-coded assembly. So why didn’t they trap by default? …Well, I don’t know. Probably because it’s simpler that way. But someone better acquainted with the history might have a better answer. VAXen had a trap on overflow capability. I once rebuilt the C compiler to set that bit in the function header mask, and recompiled many of the UNIX utilities. About half of them broke. It was too late for a retrofit by 1980. Burroughs machines had 48-bit numbers, which were signed-magnitude floats using a 32-bit mantissa with the binary point at the low end. So integers were floats with a zero mantissa. Integer overflow caused promotion to a float. So, the hardware people did try.
- mjw1007 3y agoOne reason CPUs don't trap on overflow is that it lets them use the same instruction for unsigned values and two's complement values. The instruction can then set one condition code that means "if you intended this as an unsigned addition, the answer was out of range" and a different one that means "if you intended this as a two's complement addition, the answer was out of range".