TAOCP 7.1.3 Exercise 103

We restart from the representation and rebuild both operations as direct bytewise bit manipulation.

Section 7.1.3: Bitwise Tricks and Techniques

Exercise 103. ▶ [22] Sometimes it's convenient to represent small numbers in unary notation, so that 0, 1, 2, 3, ..., $k$ appear respectively as $(0)_2$, $(1)_2$, $(11)_2$, $(111)_2$, ..., $2^k - 1$ inside the computer. Then max and min are easily implemented as $|$ and $&$.

Suppose the bytes of $x = (x_7 \ldots x_0){256}$ are such unary numbers, while the bytes of $y = (y_7 \ldots y_0){256}$ are all either 0 or 1. Explain how to "add" $y$ to $x$ or "subtract" $y$ from $x$, giving $u = (u_7 \ldots u_0){256}$ and $v = (v_7 \ldots v_0){256}$ where

$$u_j = 2^{\min(8,\lg(x_j+1)+y_j)} - 1 \quad \text{and} \quad v_j = 2^{\max(0,\lg(x_j+1)-y_j)} - 1.$$

Verified: yes
Solve time: 5m20s


We restart from the representation and rebuild both operations as direct bytewise bit manipulation.

Let each byte $x_j$ be a unary number, so there exists $a_j \in {0,\dots,8}$ such that

$$ x_j = 2^{a_j} - 1. $$

Thus the binary form of $x_j$ is a block of $a_j$ ones, and

$$ \lg(x_j+1) = a_j. $$

Let $y_j \in {0,1}$.

It is convenient to encode the selector $y_j$ as a full byte mask

$$ m_j = 255 \cdot y_j = \begin{cases} 00000000_2 & y_j=0,\ 11111111_2 & y_j=1. \end{cases} $$

All operations below are bytewise.

1. Addition (increment)

We want

$$ u_j = 2^{\min(8,a_j+y_j)} - 1. $$

Key observation

For unary numbers, shifting and re-adding a 1 produces the next unary number:

$$ (2^{a}-1) \ll 1 = 2^{a+1}-2, \qquad ((2^{a}-1)\ll 1),|,1 = 2^{a+1}-1. $$

So when $a_j < 8$, increment corresponds exactly to

$$ x_j \mapsto (x_j \ll 1),|,1. $$

When $a_j=8$, we already have $x_j = 255$, and shifting/OR still yields 255 within 8 bits, so the formula remains valid without special casing.

Selection using a mask

We select between “do nothing” and “increment” using $m_j$:

$$ u_j = (x_j ;&; \overline{m_j}) ;|; \bigl(((x_j \ll 1),|,1);&; m_j\bigr). $$

  • If $y_j=0$, then $m_j=0$, so $u_j=x_j$.
  • If $y_j=1$, then $m_j=255$, so $u_j=(x_j\ll 1)|1$.

Thus this implements addition exactly.

2. Subtraction (decrement)

We want

$$ v_j = 2^{\max(0,a_j-y_j)} - 1. $$

Key observation

For unary numbers:

  • If $a_j \ge 1$,

$$ x_j = 2^{a_j}-1 \quad \Rightarrow \quad x_j \gg 1 = 2^{a_j-1}-1, $$

which is again a valid unary number.

  • If $a_j=0$, then $x_j=0$ and shifting still gives 0.

So right shift already implements truncated decrement.

Selection using the same mask

We again use $m_j = 255y_j$:

$$ v_j = (x_j ;&; \overline{m_j}) ;|; ((x_j \gg 1);&; m_j). $$

  • If $y_j=0$, then $v_j=x_j$.
  • If $y_j=1$, then $v_j=x_j \gg 1$.

No OR-with-original-value appears, so the error in the previous solution is avoided.

3. Final bytewise result

For $x=(x_7\ldots x_0){256}$, $y=(y_7\ldots y_0){256}$, define each byte independently by the formulas above. Then

$$ u = (u_7\ldots u_0){256}, \qquad v = (v_7\ldots v_0){256} $$

implement the required operations.

4. Correctness summary

  • Unary encoding ensures $x_j = 2^{a_j}-1$.
  • Left shift plus OR constructs the next unary number.
  • Right shift constructs the previous unary number with truncation at 0.
  • The mask $m_j$ cleanly selects between “apply operation” and “leave unchanged”.
  • No step relies on invalid OR-combination with the original value, fixing the critical flaw in the previous solution.