X Xerobit

Binary Arithmetic — Addition, Subtraction, and Two's Complement

Learn how computers perform binary arithmetic: binary addition with carry, two's complement for negative numbers, overflow detection, and how CPUs handle integer operations in...

Mian Ali Khalid · · 5 min read
Use the tool
Number Base Converter
Convert between binary, octal, decimal, hexadecimal, and text (UTF-8). Handles arbitrary lengths. Per-byte and per-character views.
Open Number Base Converter →

Binary arithmetic is the foundation of all CPU operations. Understanding it helps debug integer overflow bugs, understand bit manipulation, and reason about low-level data formats.

Convert between binary, decimal, and hex with the Number Base Converter.

Binary addition

Rules: 0+0=0, 0+1=1, 1+0=1, 1+1=10 (0 carry 1)

  0110  (6)
+ 0111  (7)
------
  1101  (13)

Step by step:
  bit 0: 0+1 = 1
  bit 1: 1+1 = 0, carry 1
  bit 2: 1+1+1(carry) = 1, carry 1
  bit 3: 0+0+1(carry) = 1
Result: 1101 = 13 ✓
// JavaScript: bitwise addition (no carry beyond int size)
const a = 0b0110;  // 6
const b = 0b0111;  // 7
console.log(a + b);         // 13
console.log((a + b).toString(2));  // "1101"

// Simulate 4-bit addition with overflow wrap:
function add4bit(a, b) {
  return (a + b) & 0xF;  // Keep only low 4 bits
}
add4bit(0b1111, 0b0001);  // 1111 + 0001 = 10000, masked to 0000 = 0 (overflow)

Two’s complement: negative numbers

Computers represent negative integers using two’s complement. For an n-bit integer:

Two's complement of X = flip all bits + 1

Example: -5 in 8-bit two's complement
Step 1: 5 = 00000101
Step 2: Flip: 11111010
Step 3: Add 1: 11111011 = -5

Check: -5 + 5 = ?
  11111011  (-5)
+ 00000101  (5)
----------
 100000000  → the 9th bit is discarded → 00000000 = 0 ✓
// JavaScript uses 32-bit two's complement for bitwise ops:
console.log(-5 >>> 0);  // 4294967291 = 0xFFFFFFFB (unsigned view)
console.log((-5 >>> 0).toString(2));
// "11111111111111111111111111111011"

// Negative number detection:
function isNegative(n) {
  return (n >>> 31) === 1;  // Check sign bit (MSB)
}
isNegative(-5);   // true
isNegative(127);  // false

Range of signed vs unsigned integers

8-bit (1 byte):
  Unsigned:    0 to 255         (2^8 - 1)
  Signed:    -128 to 127        (-2^7 to 2^7 - 1)

16-bit (2 bytes):
  Unsigned:    0 to 65,535
  Signed:  -32,768 to 32,767

32-bit (4 bytes):
  Unsigned:    0 to 4,294,967,295
  Signed:  -2,147,483,648 to 2,147,483,647

64-bit (8 bytes):
  Unsigned:    0 to 18,446,744,073,709,551,615
  Signed:  -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Integer overflow

// JavaScript numbers are 64-bit floats — no int overflow below 2^53
Number.MAX_SAFE_INTEGER  // 9007199254740991 (2^53 - 1)
Number.MAX_SAFE_INTEGER + 1  // 9007199254740992
Number.MAX_SAFE_INTEGER + 2  // 9007199254740992 — same! (precision lost)

// For large integers: use BigInt
const big = 9007199254740991n + 2n;  // 9007199254740993n — correct

// C/Java 32-bit int overflow (simulated):
function int32Add(a, b) {
  const result = a + b;
  // Convert to 32-bit signed:
  return (result | 0);
}
int32Add(2147483647, 1);  // -2147483648 (overflow to minimum int32)

Binary subtraction via two’s complement

Subtraction is implemented as addition of the negated value:

7 - 5 = 7 + (-5)

  00000111  (7)
+ 11111011  (-5 in two's complement)
----------
 100000010  → discard carry → 00000010 = 2 ✓
// CPU does subtraction this way internally:
// SUB a, b = a + (~b + 1)

function subtract(a, b) {
  const negB = (~b + 1) & 0xFF;  // two's complement of b (8-bit)
  return (a + negB) & 0xFF;       // add and mask to 8 bits
}

subtract(7, 5);   // 2 ✓
subtract(5, 7);   // 254 = 0xFE = -2 in 8-bit signed

Overflow detection

Signed overflow occurs when:
- Adding two positives gives a negative
- Adding two negatives gives a positive

Detection (V flag in CPU status register):
V = CarryIn XOR CarryOut (of the most significant bit)

8-bit example:
  01111111 (127) + 00000001 (1):
  carry into bit 7 = 0
  carry out of bit 7 = 0
  V = 0 XOR 0 = 0 → no overflow? Wait:
  Result: 10000000 = -128 (signed overflow!)
  
  Actually: V = (both inputs positive) AND (result negative)
  Or in hardware: V = CarryIn[MSB] XOR CarryOut[MSB]
// Detect signed 8-bit overflow:
function addWithOverflow(a, b) {
  const result = a + b;
  const overflow = (a > 0 && b > 0 && result < 0) ||
                   (a < 0 && b < 0 && result > 0);
  return { result: (result << 24) >> 24, overflow };
}

addWithOverflow(120, 10);  // { result: -126, overflow: true }
addWithOverflow(50, 30);   // { result: 80, overflow: false }

BCD: Binary-Coded Decimal

Used in financial applications and hardware timers:

Each decimal digit stored in 4 bits:
123 in BCD = 0001 0010 0011

Normal binary: 123 = 01111011
BCD:           123 = 0001 0010 0011 (12 bits for same value)

BCD wastes space but prevents floating-point rounding errors.
Used in: cash registers, clocks, calculators.
// Convert decimal to BCD:
function decimalToBCD(n) {
  return n.toString().split('').map(d => parseInt(d).toString(2).padStart(4, '0')).join(' ');
}
decimalToBCD(123);  // "0001 0010 0011"

Related posts

Related tool

Number Base Converter

Convert between binary, octal, decimal, hexadecimal, and text (UTF-8). Handles arbitrary lengths. Per-byte and per-character views.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.