Last updated: 14 October 2022
A 1 bit number can store two numbers {0, 1}
, 2-bit number can store 4
{0,1,2,3 => 00,01,10,11}
and so on.
A n-bit number stores numbers 0 to \(2^n-1\).
Below is a function to compute the number of bits needed to store positive numbers up to a specific
number.
import math
def infer_num_bits_pos(num: int) -> int:
"""Number of bits needed to store 0 to positive numbers upto ``num``.
Sample input and output::
num = 3
output = 2
num = 4
output = 3
"""
if num <= 1: return 1
nbits_pos = math.log2(num)
if nbits_pos.is_integer():
# Multiple of 2
# For exact multiples of 2, we need one more bit. For example 8 = 2^3 => 1000.
return int(nbits_pos) + 1
else:
return math.ceil(nbits_pos)
def infer_num_bits_pos_2(num: int) -> int:
"""Number of bits needed to store positive numbers."""
if num == 0:
return 1
n = 0
while num:
num = num >> 1
n += 1
return n
However, we don’t have to restrict ourselves to non negative integers. Say we want to store
-4 to 3
, we have eight numbers, so a 3-bit store should be able to support this.
A n-bit number stores numbers \(-2^{(n-1)}\) to \(+2^{(n-1)} - 1\).
The reason we can store one fewer positive number is because we need to also represent 0.
Example: 2-bits can store -2 to 1
, 3-bits can store -4 to 3
, 4-bits can store -8 to 7
and so on.
We store negative integers in 2s-complement form. The most significant bit (MSB) is
a sign bit that is 0
if non-negative and 1
if negative. The negative of a number is the number
that needs to be added to the positive binary representation of the number to produce the
number \(2^{\text{num-bits}}\). This can be obtained adding 1
to the not of the
positive number. This technique works if you want to convert a negative number to positive
as well.
def flip_sign(num):
return ~num + 1
Example 1: Let’s use a 4-bit number. The numbers that can be represented here are -8 to 7. Say we would like to find the 2s complement representation of -6.
6 => 0 110 (MSB is the sign bit)
~6 => 001 + 1
=> 010
-6 => 1 010
Example 2:
Let’s use a 8-bit number. The numbers that can be represented here are -128 to 127
. Say
we would like to find the 2s complement representation of
-123
# This will be 127-7 ones, minus 4, 100.
# 2^7 is 128, for which we need 8 bits 1000_0000, so 0111_1111 will be 127.
123 => 0 111_1011
~123 => 000_0100 + 1
-123 => 1 000_0101
-32
# 2^5 (so needs 6 bits)
32 => 0 010_0000
~32 => 101_1111 + 1
-32 => 1 110_0000
-64
# 2^6 (so needs 7 bits)
64 => 0 100_0000
~64 => 011_1111 + 1
=> 100_0000
-64 => 1 100_0000
From above the pattern is clear, the negative integers which are powers of two are represented as ones followed by zeros.
-128. We actually need 8 unsigned bits to represent +128, 1000_0000
. If we do a negation
0111_1111
and add 1. We get 1000_0000
. That is all the 7 unsigned bits are 0. We allow
the carry over to fall through and set the MSB to 1 to mark negative. So as you would
expect, in a signed context -128 will be 1000_0000
.
def infer_num_bits_integers(num: int) -> int:
"""Number of bits needed to store negative, 0 and positive numbers with
absolute value is at least up to ``num``.
Sample input and output::
3 => -2^2 to 2^2-1 => 3 bits
-4 => -2^2 to 2^2-1 => 3 bits
5 => -2^3 to 2^3-1 => 4 bits
8 => -2^4 to 2^4-1 => 5 bits # special case
"""
if num == 0: return 2
nbits = ceil_log2(abs(num))
if nbits.is_integer() and num > 0:
# For exact positive multiples of 2, we need one extra bit. This is not true for
# negative multiples of 2. With 5 bits, we can store numbers between -16 to 15, ie.,
# -2^4 to (2^4 - 1). `34
return int(nbits) + 2
else:
return math.ceil(nbits) + 1
Something you can notice above in the two’s complement representation is that x and -x only have one common set bit which is the rightmost bit. Also for powers of two there’s exactly one set bit.
So for the leetcode problem find if a number is a power of 2, you can simply do
if n == 0:
return False
else:
return (x & (-x)) == x
>>> x =0
>>> x = x | 1 << 4
>>> x
16
In [1]: a = 25
In [2]: a >> 3
Out[2]: 3
In [3]: a << 2
Out[3]: 100
Bits are indexed 0
to n-1
. So a 8-bit number is indexed 0
for the LSB and 7
for the MSB.
Credits: Picture below from hackerrank tutorials.
For below remember i
begins at 0
.
i-th
bit is set: x & (1 << i)
i-th
bit (setting it to 1
): x | (1 << i)
i-the
bit (setting it to 0
): x & (~(1 << i))
# if num is 32 bit int, largest possible value is 2^31 - 1
if lone >= (1 << 31):
lone = lone - (1 << 32)