Chapter 6: Bit Manipulation and Binary Representation
6.01Beginner6 topics20 min2 interactive animations

Binary foundations

Computers store everything as bits. Understanding binary, hexadecimal, bit positions and two's complement tells you what a number looks like in memory and why Java behaves the way it does with negative numbers and overflow.

Learning goals

  • Convert between decimal, binary and hexadecimal
  • Read a two's complement number and find its negative
  • Number bit positions and build masks
  • Know the width and range of Java's integer types

Prerequisites

Integer arithmeticPowers of two
6.01.1

Binary and hexadecimal

Binary is base 2: the digit at position i is worth 2^i. So 1011₂ = 8 + 0 + 2 + 1 = 11. Hexadecimal is base 16 and uses 0-9 and A-F. One hex digit is exactly four bits, so hex is a compact way to write binary: 0x2F = 0010 1111₂ = 47.

Java writes hexadecimal literals with 0x, binary with 0b, and allows underscores: 0xFF, 0b1010_1100, 1_000_000.

DecimalBinaryHex
101010A
151111F
470010 11112F
2551111 1111FF
2561 0000 0000100
Java
1int a = 0b1011; // 11
2int b = 0x2F; // 47
3int c = 0xFF; // 255
4System.out.println(Integer.toBinaryString(47)); // 101111
5System.out.println(Integer.toHexString(255)); // ff
6System.out.println(Integer.parseInt("101111", 2)); // 47
6.01.2

Signed two's complement

Java stores signed integers in two's complement. In an n-bit value the top bit has weight -2^(n-1), and the other bits have their usual positive weights. So 1111 1011 in 8 bits is -128 + 64 + 32 + 16 + 8 + 2 + 1 = -5.

To negate a number, invert every bit and add 1. That is why -x == ~x + 1. Try 5, then -5, and then -128 in the animation: -128 has no positive partner, so negating it gives -128 again. In Java Integer.MIN_VALUE behaves the same, and so does Math.abs(Integer.MIN_VALUE).

Two's complement: negate by invert and add one

Interactive · edit the input

Your input

Try

x = 5

07
06
05
04
03
12
01
10
Setup

In two's complement the top bit has weight -128 and the other bits have their usual positive weights. The top bit is 0, so x is not negative.

Step 1 / 3
Java
1int negate(int x) {
2 return ~x + 1; // invert every bit, then add one
3}

Live state

0 steps

x

5

No condition evaluated on this step
Execution trace (dry run table)Show 3 steps
StepLineStateWhat happens
1L1x=5In two's complement the top bit has weight -128 and the other bits have their usual positive weights. The top bit is 0, so x is not negative.
2L2x=5 ~x=-6Step 1: invert every bit. ~x = -6, which equals -x - 1.
3L2return=-5Step 2: add 1. The carry ripples through the trailing 1s. Result: -5, so -(5) = -5.
6.01.3

Bit positions

Bits are numbered from the right, starting at 0. The rightmost bit is the least significant bit (LSB), and the leftmost the most significant bit (MSB). Bit i has the value 2^i, which Java writes as 1 << i.

To check which positions are set in 44 = 0010 1100₂: bits 2, 3 and 5. Each animation in this chapter labels the position under every bit.

Position76543210
Weight 2^i1286432168421
44 in binary00101100
6.01.4

Bit masks

A mask is an integer used to select or change particular bits with &, | and ^. A single-bit mask is 1 << i. A mask for the lowest k bits is (1 << k) - 1. For example (1 << 4) - 1 = 0b1111 = 15.

Constants like 0xFF (the low 8 bits) and 0x0F (the low 4 bits) are masks. x & 0xFF extracts the low byte of x, which is how you read an unsigned byte value from a signed byte.

Java
1int lowByte = x & 0xFF; // keep the lowest 8 bits
2int upper = (x >>> 8) & 0xFF; // the next 8 bits
3int lowBits = x & ((1 << k) - 1); // keep the lowest k bits (x mod 2^k)
4int unsigned = someByte & 0xFF; // byte -56 -> 200
6.01.5

Binary conversion

To convert decimal to binary, divide by 2 repeatedly and collect the remainders; the first remainder is the lowest bit. Reading them in reverse gives the binary number. The animation shows the remainders arriving lowest bit first and then the reversed result.

To convert back, add 2^i for each set bit, or use Horner's rule: start at 0 and for each bit from the left do value = value * 2 + bit. Java does both for you with Integer.toBinaryString and Integer.parseInt(s, 2).

Java
1static int fromBinary(String s) {
2 int value = 0;
3 for (char c : s.toCharArray()) {
4 value = value * 2 + (c - '0'); // Horner's rule
5 }
6 return value;
7}

Convert decimal to binary by repeated division

Interactive · edit the input

Your input

Try

remainders (lowest bit first)

empty
Setup

Divide by 2 repeatedly. Each remainder is one bit, starting from the lowest.

Step 1 / 10
Java
1static String toBinary(int n) {
2 if (n == 0) return "0";
3 StringBuilder sb = new StringBuilder();
4 while (n > 0) {
5 sb.append(n % 2); // the remainder is the next bit, lowest first
6 n /= 2;
7 }
8 return sb.reverse().toString();
9}

Live state

0 divisions

n

156

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L3n=156Divide by 2 repeatedly. Each remainder is one bit, starting from the lowest.
2L5n=156 n % 2=0 n / 2=78156 % 2 = 0, so bit 0 is 0. Then n = 156 / 2 = 78.
3L5n=78 n % 2=0 n / 2=3978 % 2 = 0, so bit 1 is 0. Then n = 78 / 2 = 39.
4L5n=39 n % 2=1 n / 2=1939 % 2 = 1, so bit 2 is 1. Then n = 39 / 2 = 19.
5L5n=19 n % 2=1 n / 2=919 % 2 = 1, so bit 3 is 1. Then n = 19 / 2 = 9.
6L5n=9 n % 2=1 n / 2=49 % 2 = 1, so bit 4 is 1. Then n = 9 / 2 = 4.
7L5n=4 n % 2=0 n / 2=24 % 2 = 0, so bit 5 is 0. Then n = 4 / 2 = 2.
8L5n=2 n % 2=0 n / 2=12 % 2 = 0, so bit 6 is 0. Then n = 2 / 2 = 1.
9L5n=1 n % 2=1 n / 2=01 % 2 = 1, so bit 7 is 1. Then n = 1 / 2 = 0.
10L8return=10011100Read the remainders from last to first: 156 = 10011100 in binary. It took 8 divisions, one per bit (⌊log₂ n⌋ + 1).
6.01.6

Fixed-width representation

Java's integer types have fixed widths: byte 8 bits, short 16, int 32, long 64, and char is an unsigned 16 bits. A value that does not fit wraps around: (byte) 200 is -56, and Integer.MAX_VALUE + 1 is Integer.MIN_VALUE.

Fixed width matters in bit tricks: ~x flips all 32 bits of an int, and x << 33 shifts by 33 & 31 = 1 because the shift distance is taken modulo the width.

TypeBitsRange
byte8-128 to 127
short16-32,768 to 32,767
int32-2,147,483,648 to 2,147,483,647
long64about ±9.22 × 10^18
char16 (unsigned)0 to 65,535
Java
1byte b = (byte) 200; // -56 (200 - 256)
2int wrapped = Integer.MAX_VALUE + 1; // -2147483648
3int shift = 1 << 33; // 2, because 33 & 31 == 1
4long big = 1L << 40; // use 1L, not 1, for shifts beyond 31

Practice ladder

Now try it yourself

Easy

  • Convert 1101 0110₂ to decimal and hex
  • Write -9 as an 8-bit two's complement number
  • What is (byte) 130?

Medium

  • Convert a hexadecimal string to an int without parseInt
  • Extract bits 4 to 7 of a value
  • Pack three 10-bit values into one int and unpack them

Advanced

  • Implement toBinaryString for negative ints from scratch
  • Detect int overflow when adding two numbers without using long
  • Explain why Integer.MIN_VALUE == -Integer.MIN_VALUE

Key takeaways

  • Bit i is worth 2^i and is counted from the right starting at 0; one hex digit is four bits.
  • Two's complement: negate by inverting the bits and adding 1; the range is asymmetric.
  • Java ints are fixed-width and wrap around on overflow; use 1L for 64-bit shifts.