How numbers live inside a computer

A practical guide to binary, conversions, bit shifts, permission masks, overflow, and the difference between >> and >>> in Java.

Published 2026-07-12. Tags: Binary, Java, Computer Science.

We usually write numbers with ten digits: `0` through `9`. A computer, however, is built from electronic components that are very good at distinguishing between two states: on and off, high voltage and low voltage, or simply `1` and `0`.

That is where binary comes from: a number system that uses only two digits.

Binary is not just a strange way to write numbers. Understanding it explains how computers store information, manage permissions, turn flags on and off, perform fast operations, handle negative numbers, and sometimes produce surprising results because of overflow.

Base 10 versus base 2

In decimal notation, the value of each digit depends on its position and a power of 10.

583 = 5*100 + 8*10 + 3*1
    = 5*10^2 + 8*10^1 + 3*10^0

Binary uses the same idea, but each position is based on a power of 2:

... 32 16 8 4 2 1

For example:

1011_2

Place the bits under the column values:

value:  8 4 2 1
bit:    1 0 1 1

So:

1011_2 = 8 + 0 + 2 + 1 = 11_10

The rightmost digit always represents `2^0`, which is 1. Moving left, the positions are `2^1`, `2^2`, `2^3`, and so on.

Converting decimal to binary

One convenient method is to break the number into powers of 2.

Convert 45:

45 = 32 + 8 + 4 + 1

Write the columns:

32 16 8 4 2 1

Mark `1` where the value is used, and `0` where it is not:

32 16 8 4 2 1
 1  0 1 1 0 1

Therefore:

45_10 = 101101_2

Another method is to divide by 2 repeatedly and record the remainders. Then read the remainders from bottom to top.

Converting binary to decimal

Take:

1010110_2

Break it into columns:

64 32 16 8 4 2 1
 1  0  1 0 1 1 0

So:

64 + 16 + 4 + 2 = 86

Which means:

1010110_2 = 86_10

Binary addition and subtraction

Binary addition is similar to decimal addition, but there are only four basic cases:

0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 10

The result `10` means: write `0` in the current column and carry `1` into the next column.

  1011
+ 0101
------
 10000

For subtraction, when you need to compute `0-1`, you borrow from the column on the left. The borrowed bit becomes `10_2` in the current column, meaning two units.

  10101
- 00110
-------
  01111

Decimal check:

21 - 6 = 15
01111_2 = 15

During borrowing, the temporary values are column-by-column decompositions. They are not necessarily a normal binary number you should read as one continuous sequence.

Bit shifts in Java

Java has operators that shift all bits left or right.

Left shift: `<<`

int result = 13 << 1;

The binary representation of 13 is:

001101

After one left shift:

011010

So:

13 << 1 = 26

In general, shifting left by `n` positions is usually equivalent to multiplying by `2^n`:

x << 1  ~= x * 2
x << 2  ~= x * 4
x << 3  ~= x * 8

The `~=` matters. The equality holds only when no bits are lost and no overflow changes the result.

Right shift: `>>`

For a positive number, shifting right is usually equivalent to integer division by a power of 2:

int result = 29 >> 1; // 14

In binary:

11101 -> 01110

The bit that was discarded on the right represented the remainder. In this case it was `1`, so:

29 / 2 = 14 remainder 1

Logical operations on bits

Bitwise operations compare two numbers column by column.

Use these two values:

A = 10110
B = 01101

AND: `&`

The result is `1` only when both bits are `1`.

  10110
& 01101
-------
  00100

OR: `|`

The result is `1` when at least one bit is `1`.

  10110
| 01101
-------
  11111

XOR: `^`

The result is `1` when the bits are different.

  10110
^ 01101
-------
  11011

The key difference between OR and XOR appears when both bits are `1`:

1 OR  1 = 1
1 XOR 1 = 0

NOT: `~`

NOT flips every bit:

0 -> 1
1 -> 0

In a theoretical five-bit width:

~10110 = 01001

But a Java `int` has 32 bits, so `~` flips all 32 bits, including the ones we did not show in the short example.

Practical use: storing permissions in one number

Suppose a system has four permissions:

private static final int READ    = 1;      // 0001
private static final int WRITE   = 1 << 1; // 0010
private static final int EXECUTE = 1 << 2; // 0100
private static final int ADMIN   = 1 << 3; // 1000

The number:

0101

represents two active permissions:

READ    = 1
WRITE   = 0
EXECUTE = 1
ADMIN   = 0

So the user has read and execute permissions.

Enabling a permission with OR

permissions |= WRITE;
0101 | 0010 = 0111

Checking a permission with AND

boolean canExecute = (permissions & EXECUTE) != 0;

If the AND result is not zero, the bit exists.

Disabling a permission with AND and NOT

permissions &= ~READ;

`~READ` creates a mask where the READ bit is `0` and all other bits are `1`. AND then turns off only READ.

Toggling a permission with XOR

permissions ^= EXECUTE;

If EXECUTE was enabled, it becomes disabled. If it was disabled, it becomes enabled.

Checking one permission, any permission, or all permissions

Suppose the mask contains two permissions:

int mask = READ | WRITE;

To check whether at least one permission exists:

boolean hasAny = (permissions & mask) != 0;

To check whether all permissions in the mask exist:

boolean hasAll = (permissions & mask) == mask;

These are different questions.

permissions = 0101
mask        = 0011

AND result:

0101 & 0011 = 0001

So:

hasAny = true
hasAll = false

There is one shared permission, READ, but WRITE is missing.

Fixed width and overflow

In mathematics, you can add more digits when you need them. In a computer, each type has a fixed number of bits.

Suppose only four bits can be stored:

1101_2 = 13

A left shift produces the full result:

1101 << 1 = 11010_2 = 26

But `11010` needs five bits. If only four bits are allowed, the leftmost bit is discarded:

11010
 ↓↓↓↓
1010

The stored result is:

1010_2 = 10

The discarded bit represented 16:

26 - 16 = 10

That is overflow: the operation happened, but there was not enough room to represent the full result.

In Java, an `int` has 32 bits. When a result exceeds that range, the extra bits are discarded and the result can even change sign without throwing an error.

Negative numbers: the difference between `>>` and `>>>`

Java provides two right-shift operators.

Arithmetic shift: `>>`

The `>>` operator copies the sign bit from the left.

For a negative number, the sign bit is `1`, so the number remains negative:

-8 >> 1 == -4

Logical shift: `>>>`

The `>>>` operator always inserts `0` on the left, even when the number is negative.

-8 >>> 1 == 2147483644

The result is large and positive because the leftmost bit became `0`, while the remaining bits stayed active.

-16 >> 2  == -4
-16 >>> 2 == 1073741820

Why `/` and `>>` differ for negative odd numbers

Consider:

-11 / 2
-11 >> 1

The mathematical result is:

-11 / 2 = -5.5

Integer division with `/` in Java truncates toward zero:

-11 / 2 == -5

An arithmetic right shift behaves like rounding down in this case:

-11 >> 1 == -6

So you should not automatically replace division by 2 with a right shift when the value may be negative.

Also remember: `//` is not a division operator in Java. It starts a comment until the end of the line.

int result = -11 / 2; // the result is -5

A complete Java experiment

public class BinaryExperiment {

    private static final int READ    = 1;
    private static final int WRITE   = 1 << 1;
    private static final int EXECUTE = 1 << 2;
    private static final int ADMIN   = 1 << 3;

    public static void main(String[] args) {
        int number = 45;

        System.out.println(
                "45 in binary: " + Integer.toBinaryString(number)
        );

        int parsed = Integer.parseInt("101101", 2);
        System.out.println("101101 in decimal: " + parsed);

        System.out.println("13 << 2: " + (13 << 2));
        System.out.println("29 >> 1: " + (29 >> 1));

        int permissions = READ | EXECUTE;
        printPermissions("Initial", permissions);

        permissions |= WRITE;
        printPermissions("Enable WRITE", permissions);

        permissions ^= EXECUTE;
        printPermissions("Toggle EXECUTE", permissions);

        permissions &= ~READ;
        printPermissions("Disable READ", permissions);

        permissions |= ADMIN;
        printPermissions("Enable ADMIN", permissions);

        int mask = WRITE | ADMIN;

        boolean hasAny = (permissions & mask) != 0;
        boolean hasAll = (permissions & mask) == mask;

        System.out.println("Has any required permission: " + hasAny);
        System.out.println("Has all required permissions: " + hasAll);

        System.out.println("-11 / 2: " + (-11 / 2));
        System.out.println("-11 >> 1: " + (-11 >> 1));
        System.out.println("-16 >>> 2: " + (-16 >>> 2));
    }

    private static void printPermissions(String title, int value) {
        String binary = String.format(
                "%4s",
                Integer.toBinaryString(value)
        ).replace(' ', '0');

        System.out.println(title + ": " + binary + " (" + value + ")");
    }
}

Common mistakes

The first mistake is reading a binary number as if it were decimal. `1011_2` is not one thousand and eleven. It is 11.

Another mistake is assuming that shifting left always means multiplication and shifting right always means division. That is only safe when no bits are lost, no overflow occurs, and negative numbers are handled correctly.

With bit masks, this check:

(permissions & mask) != 0

asks whether at least one shared bit exists. It does not check whether all bits in the mask exist. To check all of them, use:

(permissions & mask) == mask

It is also important to distinguish between the result of AND, which is a number, and the comparison to `0`, which produces a `boolean`.

Finally, Java's `~` operator does not flip only the bits displayed on the screen. It flips every bit of the type.

Summary

Binary is the same positional system we know from base 10, only with powers of 2 and the digits `0` and `1`.

Understanding binary makes it easier to read and build bit masks, manage permissions and flags, understand shifts, recognize overflow, and avoid wrong assumptions about negative numbers.

The goal is not to do every binary calculation in your head. The goal is to understand what the bits represent, how an operation changes them, and what can happen when the number has a fixed width.

Once you see a number as a set of individual bits, operators that used to feel mysterious, such as `&`, `|`, `^`, `~`, `<<`, `>>`, and `>>>`, become clear and useful tools.