<p>I've always wondered. Why do major languages let their addition and multiplication operators be so prone to integer overflow? My programming language would have separate operators to handle overflow. Here's Haskell-esque pseudocode:</p>
<pre><code>class RigCarry a where
    (+?) :: a -&gt; a -&gt; (a, a)
    (*?) :: a -&gt; a -&gt; (a, a)

class RigCarry a =&gt; RingCarry a where
    nagateCarry :: a -&gt; (a, a)
</code></pre>
<p>So for example, in <code>instance RigCarry Word8</code>, <code>0x80 +? 0x80</code> would give <code>(0x01, 0x00)</code>, and <code>0x80 *? 0x80</code> would give <code>(0x40, 0x00)</code>.</p>
<p>Why don't major languages do this? Is this because of a hardware issue?</p>

<operators><expressions><integers><overflow>
Why are expressions in languages so prone to integer overflow?
CC BY-SA 4.0
2023-07-03T17:17:58.197Z
Consider that addition and multiplication are really separate cases. Multiplying two N-bit unsigned values, for example, can produce up to a 2N bit result (and thus N bits of overflow, a completely separate value of the same size), whereas adding them can only produce a single bit of overflow regardless of N.
Karl Knechtel
<p>In addition to @Radvylf Programs' answer, some compilers for languages like C/C++ which abuse undefined behaviour can make use of assuming that overflow simply does not exist, and eliminate branches where overflows would occur for sometimes huge optimizations: <a href="https://stackoverflow.com/questions/54510094/why-how-does-gcc-compile-the-undefined-behaviour-in-this-signed-overflow-test-so">https://stackoverflow.com/questions/54510094/why-how-does-gcc-compile-the-undefined-behaviour-in-this-signed-overflow-test-so</a></p>

CC BY-SA 4.0
2023-05-17T16:58:08.067Z
2023-05-17T16:58:08.067Z
kouta-kun
CC BY-SA 4.0
2023-07-10T15:11:09.267Z
"abuse" here is used very subjectively.  I'd pronounce that as "_understand_".
Toby Speight
CC BY-SA 4.0
2024-12-19T21:24:28.147Z
@TobySpeight: The Standard recognizes that actions it characterizes as Undefined Behavior can occur in a correct program executes non-portable constructs, or in a correct and portable program which is fed erroneous data.  If an implementation is intended solely for processing portable programs that will never be exposed to erroneous or malicious inputs, aggressive optimizations like those described may be useful.  For most tasks, however, it would be absurd for a compiler to assume a program will contain only portable constructs, or that it would never receive erroneous or malicious inputs.
supercat
<p>Aside from the runtime cost, representing the overflow in integer computations has an added complexity cost for the programmer. Unlike floats, where there is a NaN available to signal that something bad happened in the same value, we typically have no such reserved value for integers. For a scripting language it therefore makes more sense to go for BigIntegers that do not overflow, and for low level languages some default behaviour is chosen (trap, wrap, UB) to make it simple to work with.</p>

CC BY-SA 4.0
2023-05-17T07:48:12.797Z
2023-05-17T07:48:12.797Z
Nuoji
CC BY-SA 4.0
2024-12-19T21:27:29.650Z
I would have liked to have seen the Standard allow implementations where `MIN_*` would be smaller than `MAX_*`, so as to allow any value whose upper machine word is 10000...000 to be treated as NaN.  I don't know if CPU designers would ever include "Add/subtract with sticky value on overflow" instructions, but even if they don't many instruction sets could allow reasonably fast detection of that special value.
supercat
CC BY-SA 4.0
2024-12-20T22:32:34.007Z
Yes and that is an interesting thing: processors have incentive to make changes that make people’s programs run faster. Most programs are at the bottom C (sometimes C++) which means new processors will optimize for what exists already which has C-like assumptions. So it’s a chicken and egg situation.
Nuoji
<p>While overflow-trapped arithmetic may impose only a moderate cost to a non-optimizing compiler, most languages that support overflow trapping treat any signals, traps, exceptions, etc. as strongly sequenced behaviors.  This means that a compiler given something like:</p>
<pre><code>extern int f1(void);
extern void f2(int, int, int);

void test(int x, int y)
{
  int temp = x*y;
  if (f1())
    f2(temp, x, y);
}
</code></pre>
<p>would be required to perform the multiplication before calling <code>f1()</code>, and would not be able to skip the useless multiplication in cases where <code>f1()</code> would return zero.</p>
<p>In order for a language to allow compilers to perform an optimizing transform to yield:</p>
<pre><code>extern int f1(void);
extern void f2(int, int, int);

void test(int x, int y)
{
  if (f1())
    f2(x*y, x, y);
}
</code></pre>
<p>it would be necessary to either:</p>
<ol>
<li><p>Specify that the multiplication will never have side effects,</p>
</li>
<li><p>Characterize as Undefined Behavior all situations where the multiplication could have observable side effects, or</p>
</li>
<li><p>Have a recognized category of &quot;loosely sequenced&quot; side effects which compilers would be allowed to reorder relative to other operations, and sometimes skip altogether, provided they didn't cross special ordering barriers.</p>
</li>
</ol>
<p>IMHO, #3 would be the best approach, but I've not seen language designers use it.  Of those two choices, #1 is vastly superior to #2, but #3 would be vastly better yet.</p>

CC BY-SA 4.0
2023-07-08T18:37:11.460Z
2023-07-08T18:37:11.460Z
supercat
<p>Expressions are prone to overflow because computers are designed top to bottom to have that proneness.</p>
<p>Both the term &quot;integer&quot; and the syntax for expressions is drawn from traditional mathematics, but in fact in computing integers are invariably fixed-width and have a circular progression (unlike in mathematics by default), and the operations on these fixed-width integers are specified in a way (using an &quot;expression&quot; syntax that was developed in mathematics, which operates under different assumptions) that it is easy to lose track of when a result may exceed the specified width of storage.</p>

CC BY-SA 4.0
2023-07-09T10:23:55.750Z
2023-07-09T10:23:55.750Z
Steve
<h1>Overflow checking has a (small) cost</h1>
<p>The hardware will always overflow. It doesn't care. In order to check if an overflow happened, you need extra instructions (typically just a single <code>jc</code>/<code>jo</code> [jump if carry/jump if overflow], I'd assume), which has a cost.</p>
<p>While this cost is small, since math additions are also very fast and you'll tend to do a lot of them, it adds up.</p>
<p>For example, in <code>debug</code> builds, Rust will check for overflows, but in <code>release</code> builds you have to opt in to that.</p>

CC BY-SA 4.0
2023-05-17T00:19:27.020Z
2023-05-17T00:19:27.020Z
rydwolf
CC BY-SA 4.0
2023-07-09T10:46:22.740Z
And carry flags are now out of fashion too, for obvious reasons. Modern OoO CPUs cannot afford having implicit dependencies between consequent ops (as in, there'll be as many carry flags as you have arithmetic ops in flight, selecting the right one without an explicit register reference is not easy). ISAs like RISC-V don't even have an implicit carry.
SK-logic
CC BY-SA 4.0
2023-07-18T12:24:12.930Z
@SK-logic It was not obvious to me at all, until I read your comment! _Very_ important point you make there.
Pablo H
CC BY-SA 4.0
2023-05-17T00:25:50.713Z
Swift always checks for this unless you explicitly tell it not to. Worse, it emits a separate `ud2` instruction for each operation, so sometimes your assembly contains a field of `ud2`s.
Bbrk24
# Why are expressions in languages so prone to integer overflow?
Posted by: username_0 on 2023-05-17
I've always wondered. Why do major languages let their addition and multiplication operators be so prone to integer overflow? My programming language would have separate operators to handle overflow. Here's Haskell-esque pseudocode:
class RigCarry a where
    (+?) :: a -> a -> (a, a)
    (*?) :: a -> a -> (a, a)

class RigCarry a => RingCarry a where
    nagateCarry :: a -> (a, a)

So for example, in instance RigCarry Word8, 0x80 +? 0x80 would give (0x01, 0x00), and 0x80 *? 0x80 would give (0x40, 0x00).
Why don't major languages do this? Is this because of a hardware issue?
**Tags:** operators expressions integers overflow
> **Comments:**
> **username_1**: Consider that addition and multiplication are really separate cases. Multiplying two N-bit unsigned values, for example, can produce up to a 2N bit result (and thus N bits of overflow, a completely separate value of the same size), whereas adding them can only produce a single bit of overflow regardless of N.  
---
## 5 answers
### Answer by username_7 on 2023-05-17. Score: 17 | Accepted answer
# Overflow checking has a (small) cost

The hardware will always overflow. It doesn't care. In order to check if an overflow happened, you need extra instructions (typically just a single `jc`/`jo` [jump if carry/jump if overflow], I'd assume), which has a cost.

While this cost is small, since math additions are also very fast and you'll tend to do a lot of them, it adds up.

For example, in `debug` builds, Rust will check for overflows, but in `release` builds you have to opt in to that.
> **Comments:**
> **username_10** (2023-05-17): Swift always checks for this unless you explicitly tell it not to. Worse, it emits a separate `ud2` instruction for each operation, so sometimes your assembly contains a field of `ud2`s.  
> **username_8** (2023-07-09): And carry flags are now out of fashion too, for obvious reasons. Modern OoO CPUs cannot afford having implicit dependencies between consequent ops (as in, there'll be as many carry flags as you have arithmetic ops in flight, selecting the right one without an explicit register reference is not easy). ISAs like RISC-V don't even have an implicit carry.  
> **username_9** (2023-07-18): @SK-logic It was not obvious to me at all, until I read your comment! _Very_ important point you make there.  
---
### Answer by username_2 on 2023-05-17. Score: 7
In addition to @Radvylf Programs' answer, some compilers for languages like C/C++ which abuse undefined behaviour can make use of assuming that overflow simply does not exist, and eliminate branches where overflows would occur for sometimes huge optimizations: <https://stackoverflow.com/questions/54510094/why-how-does-gcc-compile-the-undefined-behaviour-in-this-signed-overflow-test-so>
> **Comments:**
> **username_3** (2023-07-10): "abuse" here is used very subjectively.  I'd pronounce that as "_understand_".  
> **username_4** (2024-12-19): @TobySpeight: The Standard recognizes that actions it characterizes as Undefined Behavior can occur in a correct program executes non-portable constructs, or in a correct and portable program which is fed erroneous data.  If an implementation is intended solely for processing portable programs that will never be exposed to erroneous or malicious inputs, aggressive optimizations like those described may be useful.  For most tasks, however, it would be absurd for a compiler to assume a program will contain only portable constructs, or that it would never receive erroneous or malicious inputs.  
---
### Answer by username_4 on 2023-07-08. Score: 7
While overflow-trapped arithmetic may impose only a moderate cost to a non-optimizing compiler, most languages that support overflow trapping treat any signals, traps, exceptions, etc. as strongly sequenced behaviors.  This means that a compiler given something like:
extern int f1(void);
extern void f2(int, int, int);

void test(int x, int y)
{
  int temp = x*y;
  if (f1())
    f2(temp, x, y);
}

would be required to perform the multiplication before calling f1(), and would not be able to skip the useless multiplication in cases where f1() would return zero.
In order for a language to allow compilers to perform an optimizing transform to yield:
extern int f1(void);
extern void f2(int, int, int);

void test(int x, int y)
{
  if (f1())
    f2(x*y, x, y);
}

it would be necessary to either:

Specify that the multiplication will never have side effects,

Characterize as Undefined Behavior all situations where the multiplication could have observable side effects, or

Have a recognized category of "loosely sequenced" side effects which compilers would be allowed to reorder relative to other operations, and sometimes skip altogether, provided they didn't cross special ordering barriers.


IMHO, #3 would be the best approach, but I've not seen language designers use it.  Of those two choices, #1 is vastly superior to #2, but #3 would be vastly better yet.
---
### Answer by username_5 on 2023-05-17. Score: 6
Aside from the runtime cost, representing the overflow in integer computations has an added complexity cost for the programmer. Unlike floats, where there is a NaN available to signal that something bad happened in the same value, we typically have no such reserved value for integers. For a scripting language it therefore makes more sense to go for BigIntegers that do not overflow, and for low level languages some default behaviour is chosen (trap, wrap, UB) to make it simple to work with.
> **Comments:**
> **username_4** (2024-12-19): I would have liked to have seen the Standard allow implementations where `MIN_*` would be smaller than `MAX_*`, so as to allow any value whose upper machine word is 10000...000 to be treated as NaN.  I don't know if CPU designers would ever include "Add/subtract with sticky value on overflow" instructions, but even if they don't many instruction sets could allow reasonably fast detection of that special value.  
> **username_5** (2024-12-20): Yes and that is an interesting thing: processors have incentive to make changes that make people’s programs run faster. Most programs are at the bottom C (sometimes C++) which means new processors will optimize for what exists already which has C-like assumptions. So it’s a chicken and egg situation.  
---
### Answer by username_6 on 2023-07-09. Score: 3
Expressions are prone to overflow because computers are designed top to bottom to have that proneness.

Both the term "integer" and the syntax for expressions is drawn from traditional mathematics, but in fact in computing integers are invariably fixed-width and have a circular progression (unlike in mathematics by default), and the operations on these fixed-width integers are specified in a way (using an "expression" syntax that was developed in mathematics, which operates under different assumptions) that it is easy to lose track of when a result may exceed the specified width of storage.
---
