Ty Scales

Bitwise Addition

Here is a function that can add two numbers using bitwise operators.

func bitwise_add(a, b int) int {
    if b == 0 {
        return a
    }
    return bitwise_add(a^b, (a&b) << 1)
}

Why does this work? It’s helpful to look at this function not as performing “addition”, but in using the bitwise operators to rewrite a mathematical expression.

Let’s show this concretely with two numbers.

Let a = 5 as the expression $2^2 + 2^0$
Let b = 7 as the expression $2^2 + 2^1 + 2^0$
Let the sum of 5 and 7 be represented the expression $2^2 + 2^2 + 2^1 + 2^0 + 2^0$

How can this expression be simplified? Whenever we encounter two exponents raised to the same power, rewrite the expression such that $2^n + 2^n = 2^{(n+1)}$.

How do we represent that as code? We need the terms that appear in both a AND b.

// create a new expression that represents the like terms of both a and b
likeTerms := a&b

The like terms between a and b are $2^2$ and $2^0$. The new expression likeTerms represents $2^2 + 2^0$ and we want to turn that in to $2^3 + 2^1$. There’s a bitwise operator for that. It’s the bitwise SHIFT operator.

likeTerms := a&b
// for each exponent in likeTerms, shift 2^n to 2^(n + 1)
newExpressionB := likeTerms << 1

newExpressionB represents $2^3 + 2^1$ and can now be added to the remaining terms from a and b that were not merged together. In this case, $2^1$ from expression b is the only one exclusive to either expression. How do we extract it from the original expressions? We create a new expression uniqueTerms that contains the exponents exclusive to a and exclusive to b. There’s a bitwise operator for that. Its the XOR operator.

// create and expression of the terms that were exclusive to a and b, the terms that didn't get rewritten in to newExpressionB
uniqueTerms := a^b

we now have two new expressions. newExpressionB representing $2^3 + 2^1$ and uniqueTerms representing $2^1$ that we need to add together. We have a function for that. It’s the bitwise_add function that we’ve been crafting this entire time! Keep passing the new expressions recursively, until a consists of all unique terms and b is zero. At that point, we have obtained the answer.

Here are the remaining steps.
Rewrite $2^3 + 2^1 + 2^1$ as $2^3 + 2^2$
All terms are unique: $7 + 5 = 2^3 + 2^2 = 12$

func bitwise_add(a, b int) int {
    if b == 0 {
        return a
    }

    // find the unique terms(bits) between a and b
    uniqueTerms := a^b

    // find the like terms(bits) between a and b
    likeTerms := a&b

    // for each like term, shift exponent from 2^n to 2^(n + 1)
    newExpressionB := likeTerms << 1
  
    // call bitwise_add until there are no like terms, return a
    return bitwise_add(uniqueTerms, newExpressionB)
}