bitlvl
EN

Integer overflow & arithmetic calculator

Type an expression, pick a type and a language, and see exactly what the machine does: which operands get promoted or converted, where the result stops fitting, whether that is wraparound, a panic or undefined behaviour, and the bits before and after.

Why integers surprise people

A machine integer has a fixed number of bits, so every operation can produce a value that does not fit. What happens then is not decided by the hardware (which simply wraps) but by the language:

LanguageSigned overflowUnsigned overflowShift by ≥ widthDivision by zero
C / C++undefined behaviour: the optimiser assumes it never happenswraps modulo 2ⁿ (defined)undefinedundefined (SIGFPE on x86)
Rustpanic in debug, wrap in release; checked_/wrapping_/saturating_/overflowing_ are explicitsame as signedpanic in debugpanic
Gowraps (defined)wraps0 / −1, no panicpanic
Javawraps (defined); Math.addExact throwsno unsigned types (char is 16-bit unsigned)count masked to 5/6 bitsArithmeticException
JavaScriptnumbers are doubles: exact up to 2⁵³, then rounding; bitwise operators work on int32count masked to 5 bitsInfinity / NaN

The C rules that bite

  • Integer promotion. Anything smaller than int becomes int before arithmetic, so uint8_t a = 200, b = 100; a + b is 300, and only the assignment back to a uint8_t truncates to 44. ~x on a uint8_t gives a negative int.
  • Usual arithmetic conversions. When signed meets unsigned of the same width, the signed side becomes unsigned: -1 > 0u is true, if (len - 1 < 0) is never true when len is size_t.
  • Signed overflow is UB. if (x + 1 < x) as an overflow check gets deleted by GCC and Clang with optimisation on, because they may assume x + 1 never overflows. Check before the operation (x > INT_MAX - y) or use __builtin_add_overflow.
  • Shifts. Shifting by the width or more, or left-shifting a negative value, is undefined; x86 masks the count, ARM gives zero, the compiler does whatever its model says.
  • Division. Truncates toward zero, so -7 / 2 == -3 and -7 % 2 == -1; INT_MIN / -1 overflows and actually traps on x86.

Reading the output

The result is shown in the type it ends up in, with its bit pattern and the same bits read as the opposite signedness, which is what you see when a value crosses a (int) / (unsigned) boundary or is stored and read back through a different type. The step list is the order of evaluation with the type at each step; the notes are the places where the language did something other than plain arithmetic.

Questions

Why does the C result differ from what my program printed?

For undefined behaviour there is no single right answer: this page shows the wraparound value that unoptimised code produces, and flags the step as UB. With -O2 the compiler may have assumed the overflow cannot happen and produced different code. Compile with -fsanitize=undefined to see the exact spot.

What does the type selector do?

It is the type of literals that have no suffix or cast, which in C defaults to int. Choose u8 and type 200 + 100 to see Rust panic, or C promote to int and succeed.

How do I write a cast?

C style: (u8) expr, (uint32_t) expr, (int) expr. Rust style: expr as u8. Both work in every language mode. INT_MAX, INT_MIN, UINT_MAX, LONG_MAX, SIZE_MAX are available as names.

Does it do 128-bit?

Yes: i128 and u128 are evaluated exactly (all arithmetic is done with arbitrary precision and then fitted to the type).

What about floating point?

Not here; the IEEE 754 float visualizer on this site covers that. This tool is only about what happens to whole numbers in fixed-width types.