Collatz

The Collatz map is defined as:

c(x) = if even(x) then x/2 else 3x + 1

then the Collatz problem is answering the question: do all positive numbers x, iterated by the Collatz function c, eventually reach the trivial cycle c(1) = 4, c(4) = 2, c(2) = 1.

Today, we're going to consider the problem in a different lens. Instead of defining the Collatz map in terms of c(x) we're going to define

d(x) = if even(x) then x/2^v2(x) else (3x + 1)/2

v2(x) is defined as the highest exponent i such that 2^i divides x.

Interestingly, there is a productive way to see d as a function on binary strings. To see this, lets consider the case where x is even: x/v2(x) essentially strips the leading zeros from the binary representation of x. This is straightforward, as the string of leading zeros is v2(x) long, and dividing by 2^v2(x) will remove this string. To consider the case where x is odd, we'll have to unpack the binary addition algorithm.

When represented in binary, the addition algorithm proceeds something like this, representing numbers as a list of their bits starting from the least significant:

add :: [Bool] -> [Bool] -> [Bool]

add xs ys = adder xs ys False

adder (x : xs) (y : ys) c = x xor y xor c : adder xs ys (maj x y c)

adder [] ys c = adder [False | _ <- ys] ys c

adder xs [] c = adder [False | _ <- xs] xs c

adder [] [] True = [True]

adder [] [] False = []

maj x y z = ind x + ind y + ind z >= 2

ind b = if b then 1 else 0

We can rewrite 3x + 1 as (2x + 1) + x, then we realize that 2x + 1 in binary is simply the binary form of x with a 1 appended as the least significant digit. Thus, we can see that (3x + 1)/2 = tail (add x (True : x)). We also know that the first bit of x is True, because x is odd.

add xs0 (True : xs0) = adder xs0 (True : xs0) False

let True : xs1 = xs0

adder (True : xs1) (True : True : xs1) False = False : adder xs1 (True : xs1) True

let x1 : xs2 = xs1

adder (x1 : xs2) (True : x1 : xs2) True = x1 : adder xs2 (x1 : xs2) True

let x2 : xs3 = xs2

adder (x2 : xs3) (x1 : x2 : xs3) True = not (x2 xor x1) : adder xs3 (x2 : xs3) (maj x2 x1 x0)

NB: x0 is True, but we don't simplify maj for clarity.

let x3 : xs4 = xs3

adder (x3 : xs4) (x2 : x3 : xs4) (maj x2 x1 x0) = x3 xor x2 xor (maj x2 x1 x0) : adder xs4 (x3 : xs4) (maj x3 x2 (maj x2 x1 x0))

let x4 : xs5 = xs4

adder (x4 : xs5) (x3 : x4 : xs5) (maj x3 x2 (x2 || x1))

= x4 xor x3 xor (maj x3 x2 (x2 || x1)) : adder xs5 (x4 : xs5) (maj x4 x3 (maj x3 x2 (x2 || x1)))

...

Overall, we can see a pattern: (3x + 1) / 2 has bits:

x1

not (x2 xor x1)

x3 xor x2 xor (maj x2 x1 x0)

...

xN xor xN-1 xor (maj xN-1 xN-2 (... (maj x2 x1 x0) ...))

XN xor (maj XN xN-2 (maj xN-1 xN-2 (... (maj x2 x1 x0) ...)))

Back to home