Bit representation

In programming, an $n$ bit integer is internally stored as a binary number that consists of $n$ bits. For example, the Rust type i32 is a 32-bit type, which means that every i32 number consists of 32 bits.

Here is the bit representation of the i32 number 43:

#![allow(unused)]
fn main() {
let number: i32 = 43;
println!("{number:032b}");
}

The bits in the representation are indexed from right to left. To convert a bit representation $b_k \cdots b_2 b_1 b_0$ into a number, we can use the formula \[ b_k 2^k + \ldots + b_2 2^2 + b_1 2^1 + b_0 2^0 \]

For example, \[ 1 \cdot 2^5 + 1 \cdot 2^3 + 1 \cdot 2^1 + 1 \cdot 2^0 = 43 \]

The bit representation of a number is either signed or unsigned. Usually a signed representation is used, which means that both negative and positive numbers can be represented. A signed variable of $n$ bits can contain any integer between $-2^{n-1}$ and $2^{n-1}-1$. For example, the i32 type in Rust is a signed type, so an i32 variable can contain any integer between $-2^{31}$ and $2^{31}-1$.

The first bit in a signed representation is the sign of the number (0 for nonnegative numbers and 1 for negative numbers), and the remaining $n-1$ bits contain the magnitude of the number. Two's complement is used, which means that the opposite number of a number is calculated by first inverting all the bits in the number, and then increasing the number by one.

For example, the bit representation of the i32 number $-43$ is

#![allow(unused)]
fn main() {
let number: i32 = -43;
println!("{number:032b}");
}

In an unsigned representation, only nonnegative numbers can be used, but the upper bound for the values is larger. An unsigned variable of $n$ bits can contain any integer between $0$ and $2^n-1$. For example, in Rust, an u32 variable can contain any integer between $0$ and $2^{32}-1$.