We will now cover three tricks—checking whether the signs of integers are different, checking whether a number is a power of 2
, and calculating the modulus of a number that is a power of 2
. We will show an operators-only notation and one using the corresponding NumPy functions:
XOR
or ^
operator. The XOR
operator is also called the inequality operator; so, if the sign bit of the two operands is different, the XOR
operation will lead to a negative number (see https://www.khanacademy.org/computing/computer-science/cryptography/ciphers/a/xor-bitwise-operation).The following truth table illustrates the XOR
operator:
Input 1 |
Input 2 |
XOR |
---|---|---|
True |
True |
False |
False |
True |
True |
True |
False |
True |
False |
False |
False |
The ^
operator corresponds to the bitwise_xor()
function, and the <
operator corresponds to the less()
function:
x = np.arange(-9, 9) y = -x print("Sign different?", (x ^ y) < 0) print("Sign different?", np.less(np.bitwise_xor(x, y), 0))
The result is shown as follows:
Sign different? [ True True True True True True True True True False True True True True True True True True] Sign different? [ True True True True True True True True True False True True True True True True True True]
As expected, all the signs differ, except for zero.
2
is represented by a 1
, followed by a series of trailing zeroes in binary notation. For instance, 10
, 100
, or 1000
. A number one less than a power of 2
will be represented by a row of ones in binary. For instance, 11
, 111
, or 1111
(or 3
, 7
, and 15
in the decimal system). Now, if we bitwise AND
a power of 2
, and the integer that is one less than that, then we should get 0
.The truth table for the AND
operator looks like the following:
Input 1 |
Input 2 |
AND |
---|---|---|
True |
True |
True |
False |
True |
False |
True |
False |
False |
False |
False |
False |
The NumPy counterpart of &
is bitwise_and()
, and the counterpart of ==
is the equal()
universal function:
print("Power of 2? ", x, " ", (x & (x - 1)) == 0) print("Power of 2? ", x, " ", np.equal(np.bitwise_and(x, (x - 1)), 0))
The result is shown as follows:
Power of 2? [-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8] [False False False False False False False False False True True True False True False False False True] Power of 2? [-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8] [False False False False False False False False False True True True False True False False False True]
2
such as 4
, 8
, 16
, and so on. A bitwise left shift leads to doubling of values (see https://wiki.python.org/moin/BitwiseOperators). We saw in the previous step that subtracting one from a power of 2
leads to a number in binary notation that has a row of ones such as 11
, 111
, or 1111
. This basically gives us a mask. Bitwise-ANDing with such a number gives you the remainder with a power of 2
. The NumPy equivalent of <<
is the left_shift()
universal function:print("Modulus 4 ", x, " ", x & ((1 << 2) - 1)) print("Modulus 4 ", x, " ", np.bitwise_and(x, np.left_shift(1, 2) - 1))
The result is shown as follows:
Modulus 4 [-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8] [3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0] Modulus 4 [-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8] [3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0]
We covered three bit twiddling hacks—checking whether the signs of integers are different, checking whether a number is a power of 2
, and calculating the modulus of a number that is a power of 2
. We saw the NumPy counterparts of the operators ^
, &
, <<
, and <
(see bittwidling.py
):
from __future__ import print_function import numpy as np x = np.arange(-9, 9) y = -x print("Sign different?", (x ^ y) < 0) print("Sign different?", np.less(np.bitwise_xor(x, y), 0)) print("Power of 2? ", x, " ", (x & (x - 1)) == 0) print("Power of 2? ", x, " ", np.equal(np.bitwise_and(x, (x - 1)), 0)) print("Modulus 4 ", x, " ", x & ((1 << 2) - 1)) print("Modulus 4 ", x, " ", np.bitwise_and(x, np.left_shift(1, 2) - 1))