3 ms·
> "hey I added two numbers and got back a smaller number" is almost always not actually the function that you were trying to implement. As a bit of an aside, w
by arp242 3y ago
> "hey I added two numbers and got back a smaller number" is almost always not actually the function that you were trying to implement.
As a bit of an aside, when is this actually useful? The only example I know of is bytebeats[1], and it's unlikely this is what Ritchie had in mind when he created C. Wikipedia says "for some applications, such as timers and clocks, wrapping on overflow can be desirable", but even there wrapping an int usually seems wrong (e.g. you want to wrap minutes on 60, hours on 24, etc – not 255 or 65k or whatever the size is).
[1]: e.g. stuff like: main(t){for(;;t++) putchar(t*((t>>12|t>>8)&63&t>>4));} – pipe to aplay or /dev/dsp or whatever.
- gizmo686 3y agoIts useful for implementing arbitrary precision integers.
- m-ee 3y agoIn the embedded world I almost never care about real time in hours or minutes. I just want a timer to increment for use with input capture or determining the period of some function.
- nullc 3y agoArithmetic mod the word size is quite useful, but it's useful to specialized code-- hashes, checksums, periodic counters, fast division by constants, implementations of fast algebraic codes for things like combinatorial number (en/de)coding. For the counters, since you questioned it-- say you want your program to perform a calibration cycle every once in a while, the exact interval isn't critical. Just free run an unsigned iteration counter and then test that some masked version of it equals a constant. All that said, when you want wrapping behavior you know it. It's critical that it's available, first class supported, and fast-- but it wouldn't hurt if you had to ask for it. But the alternatives to wrapping aren't themselves magic bullets: Say you use saturating arithmetic, now lots of code that might have worked more or less fine with wrapping (or only been a little glitchy) goes into a hard lockup, esp since saturation breaks the property that var+1 != var, or fails in some more serious way, when e.g. because var+pos-pos != var. And the wrapping behavior at least has the property that its fast on extant hardware, while implementing saturating or trapping operations is inherently slower. Wrapping also manages to produce correct results by accident pretty often, like in my above point about var+pos-pos=var. Sometimes machine generated code does some nonsense that takes a variable out of range then pulls it back. Switching to saturating would just switch the cases that are magically correct, switching to trapping would turn any otherwise magically correct cases into failures. In some cases you might prefer a clean crash to potentially glitchy behavior, but in other cases there is no such thing as a "clean crash" and crashing on overflow is the worst failure possible (so whatever else might happen if it kept running could be no worse). There is, unfortunately, no replacement for thoroughly understanding the behavior of a piece of code nor any replacement for formal analysis. We shouldn't cry too much. Software is absurdly cheap to make compared to e.g. engineering and machining a part out of metal. But part of that ease is an illusion because it's only that easy if you don't care if the software is correct, and only need it to be somewhat correct or only correct some of the time. Few of the drivers for improved software quality are actually backed by research either. Would doing X or Y instead be better? We don't know. Alternatives start with at least a minor headwind that there is a lot of experience out there dealing with the way things have historically been done. Unfortunately researching the interaction between programming languages and programmers is itself really hard.
- comex 3y agoWhen 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.