Number theory

Collatz

Take any number. Even, halve it. Odd, triple it and add one. Every number anybody has ever tried falls to 1. Nobody can prove it, and Erdős said mathematics is not ready for problems like this.

EXP-030

Move the cursor over the canvas — it is part of the simulation.

Drag your finger across the canvas — it is part of the simulation.

What to look for

The readout gives the longest path found. Under 2.200 it is 181 steps, and that record belongs to 1161; the next number to beat it is 2223, just outside the range. Push the count up and watch the coral thicken without ever growing a branch that escapes: every single one of those thousands of paths comes home.

The maths

f(n) = n/2 si n par; 3n+1 si n impar

The entire rule. It fits on a line and a child can follow it, which is precisely what makes the difficulty embarrassing.

3n+1 es par ⇒ el paso real es (3n+1)/2

Odd steps grow by about 1.5×, even steps halve. On average a random step multiplies by √(3/2)/... under 1, so heuristically everything should fall. Heuristics are not proofs, and the sequence for 27 climbs to 9232 before collapsing.

verificado hasta 2⁶⁸ ≈ 2.95 × 10²⁰

Every starting value below that has been checked by computer and every one reaches 1. That is overwhelming evidence and zero proof, and the distinction is the whole of mathematics.

How it is built

For each starting number the sequence is computed down to 1, then drawn backwards from a common root, turning one way on an even step and the other way on an odd one. Because every sequence ends at 1, every drawing starts at the same point, and the shared prefixes overlap into thick trunks while the rare paths become thin outer branches. It is rendered once to an offscreen canvas and cached, because 2.200 paths is far too much to redraw at 60fps.

The code, step by step

Cut straight from lab.js. These are the real lines that run above, not a simplified version.

01

Cache by everything that matters

const k = N + ':' + P.even + ':' + P.odd + ':' + w + ':' + h + ':' + acc;
if (key !== k) {
  key = k;
  cache = document.createElement('canvas');
  cache.width = w; cache.height = h;

The key includes the count, both angles, the canvas size and the accent colour. If any of them changes the drawing is rebuilt; otherwise the cached canvas is blitted. Drawing two thousand paths every frame would be pointless when the picture is completely static.

02

The sequence itself

let v = s, guard = 0;
while (v !== 1 && guard++ < 1000) { path.push(v % 2 === 0); v = v % 2 === 0 ? v / 2 : 3 * v + 1; }

Two operations and a stopping condition. What gets stored is not the numbers but the parities, because that is all the drawing needs. The guard is there because the conjecture is unproven and I would rather have a bounded loop than an act of faith in the render path.

03

Draw it backwards

let x = w / 2, y = h * 0.94, a = -Math.PI / 2;
c.beginPath(); c.moveTo(x, y);
for (let i = path.length - 1; i >= 0; i--) {
  a += path[i] ? ev : od;
  x += Math.cos(a) * seg; y += Math.sin(a) * seg;
  c.lineTo(x, y);

Every sequence ends at 1, so drawing from the end means every path starts at the same root. Paths that share a tail — and most of them do — overlap into the same strokes, which is why trunks emerge from pure transparency stacking rather than from any tree being built. Turn one way on an even step, the other way on an odd one.

Why it matters

Terence Tao got the closest anybody has in 2019, proving that almost all starting values eventually get almost bounded, which is a long way from all. The problem is famous because it is the clearest example of a statement that is trivial to check, impossible to prove, and completely useless if true.

The whole thing

Everything above, in one piece. This is the entire experiment as it lives in lab.js.

FULL

lab.js · collatz

{ id: 'collatz',
      name: L('Collatz', 'Collatz'),
      note: L('Halve it or triple-plus-one. Every path tested so far falls to 1. Nobody has proved it.',
              'Divide en dos o triplica y suma uno. Todo camino probado cae a 1. Nadie lo ha demostrado.'),
      params: [
        { key: 'count', label: L('Numbers', 'Números'), min: 100, max: 6000, step: 100, def: 2200 },
        { key: 'even',  label: L('Even turn', 'Giro par'), min: 0, max: 20, step: 0.5, def: 7 },
        { key: 'odd',   label: L('Odd turn', 'Giro impar'), min: -24, max: 0, step: 0.5, def: -11 }
      ],
      make() {
        let cache = null, key = '', maxLen = 0;
        return {
          resize() { key = ''; },
          step(ctx, w, h, t, acc, P, M) {
            const N = P.count | 0;
            const k = N + ':' + P.even + ':' + P.odd + ':' + w + ':' + h + ':' + acc;
            if (key !== k) {
              key = k;
              cache = document.createElement('canvas');
              cache.width = w; cache.height = h;
              const c = cache.getContext('2d');
              const ev = (P.even * Math.PI) / 180, od = (P.odd * Math.PI) / 180;
              const seg = Math.min(w, h) * 0.020;
              maxLen = 0;
              // Se dibuja la secuencia AL REVÉS, desde el 1. Todas las ramas
              // comparten raíz, y por eso el dibujo crece como un coral.
              for (let s = 2; s <= N; s++) {
                const path = [];
                let v = s, guard = 0;
                while (v !== 1 && guard++ < 1000) { path.push(v % 2 === 0); v = v % 2 === 0 ? v / 2 : 3 * v + 1; }
                if (path.length > maxLen) maxLen = path.length;
                let x = w / 2, y = h * 0.94, a = -Math.PI / 2;
                c.beginPath(); c.moveTo(x, y);
                for (let i = path.length - 1; i >= 0; i--) {
                  a += path[i] ? ev : od;
                  x += Math.cos(a) * seg; y += Math.sin(a) * seg;
                  c.lineTo(x, y);
                }
                c.strokeStyle = `rgba(${acc},0.035)`;
                c.lineWidth = 1;
                c.stroke();
              }
            }
            ctx.drawImage(cache, 0, 0);
            ctx.font = '11px monospace';
            ctx.fillStyle = `rgba(${acc},0.55)`;
            ctx.fillText('n even → n/2      n odd → 3n+1      drawn backwards from 1', 12, h - 28);
            ctx.fillStyle = `rgba(${acc},0.95)`;
            ctx.fillText(N.toLocaleString('en') + ' starting numbers   longest path ' + maxLen + ' steps', 12, h - 12);
          }
        };
      }
    },