eisenstein Three boats. Same route. Different math. Watch what happens.

The Problem

Floating-point numbers drift. Not sometimes, not edge cases — every single operation adds a tiny error. Multiply that by thousands of operations and your position is wrong. Your rotation is off. Your game desyncs.

You can't fix this by throwing more bits at it. F64 uses 16 bytes per coordinate pair. It drifts less than F32's 8 bytes. But it still drifts. And you're paying 4× the memory, 4× the bandwidth, 4× the disk — for something that's still not exact.

The demo above shows this. The green boat (E12) uses integer arithmetic. The orange boat uses standard 32-bit floats. The red boat "turns up the precision" to 64-bit floats — four times the data per fix — and it still can't match the integer boat.

E12: 4 bytes, 0 drift F32: 8 bytes, drifts F64: 16 bytes, still drifts
Data per coordinate fix. More precision costs more. None of them are exact except E12.

What Are Eisenstein Integers

Every point on a hex grid can be written as a pair of integers (a, b). That's it. No floats. No square roots. The distance from the origin — the norm — is a² - ab + b², which is always a non-negative integer.

This isn't an approximation. It's the mathematical definition. Eisenstein integers are the ring Z[ω] where ω is a primitive cube root of unity. Crystallographers use them to describe hexagonal lattices. We just made them into a Rust library.

(0,0) norm = 0 (1,0) (0,1) (-1,1) (-1,0) (0,-1) (1,-1) All 6 neighbors at norm = 1 Ring 2 at norm ≤ 4
The Eisenstein hex lattice. Every point is a pair of integers. The 6 neighbors of the origin all have norm 1.

Why hex, not square? A square grid has 4 neighbors. A hex grid has 6. For 2D problems, hex is the natural topology — distances are more uniform, circles pack better, and rotation by 60° maps the lattice onto itself. The square grid can't do that. Rotating a square grid by 45° doesn't give you integer coordinates. Rotating a hex grid by 60° always does.

Show Me

Rust

use eisenstein::E12;

let a = E12::new(3, 1);  // point (3,1)
let b = E12::new(1, 2);  // point (1,2)

// 60° rotation — stays on the lattice
let rotated = a.rotate_60();

// Norm is an integer — no sqrt
let n = a.norm();  // 7

// Addition and multiplication
let sum = a + b;   // E12(4, 3)
let prod = a * b;  // E12(1, 7)

Python

from eisenstein import Eisenstein

a = Eisenstein(3, 1)
b = Eisenstein(1, 2)

# Same exact arithmetic
rotated = a.rotate60()
assert a.norm() == 7  # integer

# Zero dependencies
sum_ = a + b   # (4, 3)
prod = a * b   # (1, 7)

No floats anywhere. The norm a² - ab + b² is computed with integer multiplication and subtraction. Rotation by 60° is a coordinate transform: (a, b) → (-b, a - b). No trig functions involved. The result is always another pair of integers.

Why It Matters

Autopilot Navigation

A boat navigating a channel gets a GPS fix every few seconds. Each fix involves a float computation — position, heading correction, course adjustment. After 1,000 fixes, the accumulated heading error is a few meters. In a 20-meter channel, that's the difference between safe passage and grounding.

E12 coordinates don't drift. Every fix computes the same heading from the same position. After 10,000 fixes, the heading error is still zero. Not approximately zero — zero. The arithmetic never leaves the integer lattice.

channel wall E12 float drift accumulates → float hits rocks
Top-down view of a channel. E12 stays on the centerline. Float drifts sideways with each correction.

Multiplayer Game Desync

Two players see the same hex grid. Player A rotates a unit 60°. Player B rotates the same unit 60° on their device. With floats, they get different answers — different FPU rounding modes, different compiler optimizations, different results. The game desyncs.

With E12, both devices compute (-b, a - b) on the same integers. Same input, same output. Every time. No reconciliation protocol, no "resync" packets, no arguing about whose simulation is authoritative.

Player A rotate60((3,1)) = (-1, 2) ✓ Player B rotate60((3,1)) = (-1, 2) ✓ =
Same integers in, same integers out. No FPU rounding differences. No desync possible.

Sensor Fusion

Combining readings from gyroscope, compass, and GPS. Each sensor's float errors compound differently depending on the order of operations, the magnitude of values, and the specific hardware. The "truth" slowly diverges from reality.

E12's exact arithmetic means sensor readings combine without error accumulation. Two gyroscope readings that should cancel, cancel exactly. A compass bearing that should equal a GPS-derived heading, equals it exactly. The math is the same on every device, every time.

How It Works

60° Rotation

Multiplying an Eisenstein integer by ω rotates it by exactly 60°. In coordinates, this is (a, b) → (-b, a - b). No trig functions. No floating point. No lookup tables. Two integer subtractions and a negation.

Do it six times and you're back where you started. Exactly. Not approximately — the final coordinates are bit-identical to the starting coordinates. We test this with 10,000 random rotations.

(3,1) (-1,2) (-2,-1) (1,-3) (3,-2) (-1,1) back to (3,1) 6 rotations → exact return. Always.
The point (3,1) rotated through 6 positions. After 6 rotations, back to (3,1) — bit-identical.

The Norm Is Always an Integer

For a point (a, b), the Eisenstein norm is a² - ab + b². This is always a non-negative integer when a and b are integers. There's no square root in the formula — no approximation step where you'd lose precision.

Compare with Euclidean distance: sqrt(a² + b²). The square root is where the float creeps in. You can't compute sqrt(2) exactly in floating point. But a² - ab + b² for (a,b) = (1,0) is just 1. No sqrt needed. No approximation.

Square grid (Z²)

4 neighbors. Euclidean distance needs sqrt(a²+b²). Rotating 45° leaves the lattice. Distance comparisons require float epsilon.

Hex grid (Eisenstein)

6 neighbors. Norm is a²-ab+b² — pure integer. Rotating 60° stays on the lattice. Distance comparisons are integer comparisons.

The Disk Formula

How many hex grid points fit within distance R of the origin? Exactly 3R² + 3R + 1. This is a proven formula. At R=36, that's 3,997 exact vertices. At R=1000, it's 3,003,001.

The library iterates these points in cache-friendly order. No allocation. No hash map. Just a tight loop over a known set of integers.

The Numbers

Real benchmarks from the eisenstein-bench suite. Same hardware, same compiler, fair comparison.

OperationE12Float64Notes
Norm1 ns3.3 nsE12 is 3.3× faster (no sqrt)
60° Rotation2 ns12 nsFloat needs sin+cos lookup
Equality1 compare4 bytes + epsilonE12: just (a,b) == (c,d)
Drift after 10K rotations0.000~10⁻¹⁵Float drift is small here because 60° has "nice" trig values

Note: The float drift looks small because 60° rotations happen to have clean trig values. For arbitrary angles, drift is much worse. E12 doesn't have "nice" vs "ugly" angles — every rotation in its lattice is exact.

Data Cost Per Fix

TypeBytes per fixRelativeExact?
E12 (two i32)4yes
F32 (two f32)8no
F64 (two f64)16no

At 1,000 fixes per second on a cellular modem:

On a satellite link at $1/MB, E12 saves $43K/year vs F64. But the real cost isn't money — it's that F64 still isn't exact.

What Doesn't Work

We publish our negative results alongside the positive ones. Some things we tried that didn't pan out:

Negative results are data. We document them publicly. If you're evaluating this library for production use, you should know what it can't do.

Get Started

cargo add eisenstein

or pip install constraint-theory for Python, npm install @superinstance/polyformalism-a2a for JS

Further Reading

Eisenstein integers are one piece of a larger project: SuperInstance builds tools for exact computation in domains where floating-point drift causes real failures.

Constraint Theory

A framework for checking whether systems satisfy constraints — with zero false negatives. The core idea: represent constraints as Eisenstein integer operations, and the math guarantees soundness. Used for sensor fusion, autopilot validation, and real-time monitoring.

Embedded & Edge

The eisenstein crate is no_std with zero dependencies. The core type is ~600 lines of Rust. We've compiled it for ARM Cortex-M targets. A C runtime fits in 4KB — small enough for microcontrollers that don't have an FPU.

Developer Tools

Benchmarks, fuzz testing, and code generation — the infrastructure we built to verify the library works correctly.

CUDA Benchmarks

We ran 54 GPU experiments on an RTX 4050 (Ada) measuring constraint throughput with Eisenstein integer operations. The results are in constraint-theory-ecosystem. Highlights: INT8×8 achieves 341B constraints/sec, differential testing across 61M inputs produced zero mismatches, and CUDA Graphs gave 18× kernel launch speedup. The negative results (FP16 unsafe past 2048, tensor cores marginal, bank padding counterproductive) are documented in our errata.

Cross-Language Ports

The same Eisenstein arithmetic available in three languages, tested for behavioral parity.