Balanced Ternary Arithmetic
Trits and Bits
A trit (ternary digit) is the fundamental unit of ternary computing. While a binary digit (bit) can represent two states (0 or 1), a trit represents three states. In balanced ternary, these states are {-1, 0, +1}, often written as {T, 0, 1} or {-, 0, +} for brevity.
The information content of a single trit is log2(3) = 1.585 bits. This means each trit carries approximately 58.5% more information than a single bit. To represent the same range of N values, you need ceiling(log3(N)) trits versus ceiling(log2(N)) bits -- fewer symbols, each doing more work.
| Unit | States | Information Content |
|---|---|---|
| Bit | 2 | 1.000 bits |
| Trit | 3 | 1.585 bits |
Balanced vs. Unbalanced Ternary
Standard (unbalanced) ternary uses the digit set {0, 1, 2}, analogous to how binary uses {0, 1}. Balanced ternary instead uses {-1, 0, +1}, centering the digit values symmetrically around zero. This seemingly small change has profound consequences:
- Signed numbers need no special encoding. In binary, representing negative numbers requires conventions like two's complement. In balanced ternary, negative numbers arise naturally -- the number -5 in balanced ternary is simply the negation of +5, obtained by flipping every trit.
- No wasted representations. Two's complement binary has an asymmetry: an n-bit number can represent one more negative value than positive. Balanced ternary with n trits represents values symmetrically from -(3^n - 1)/2 to +(3^n - 1)/2.
- Truncation equals rounding. Dropping the least significant trits of a balanced ternary number rounds to the nearest representable value, not toward zero as in binary.
Basic Arithmetic Operations
Ternary Addition
Addition in balanced ternary follows the same column-by-column logic as binary addition, but with three possible values per position. The addition table:
| + | -1 | 0 | +1 |
|---|---|---|---|
| -1 | -1, carry -1 | -1 | 0 |
| 0 | -1 | 0 | +1 |
| +1 | 0 | +1 | +1, carry +1 |
When the sum of two trits exceeds +1 or falls below -1, a carry propagates to the next position. For example, (+1) + (+1) = -1 with a carry of +1 (since 1 + 1 = 2, and 2 in balanced ternary is "1T", meaning +1 in the next position and -1 in the current position).
Ternary Multiplication
Multiplication by a single trit is trivial:
| x | -1 | 0 | +1 |
|---|---|---|---|
| -1 | +1 | 0 | -1 |
| 0 | 0 | 0 | 0 |
| +1 | -1 | 0 | +1 |
This is standard integer multiplication restricted to {-1, 0, +1}:
- Multiplying by +1 leaves the value unchanged (identity).
- Multiplying by -1 negates the value (sign flip).
- Multiplying by 0 produces zero (annihilation).
This property is critical for neural network inference. When model weights are ternary, matrix-vector multiplication reduces to additions and subtractions -- no floating-point multipliers are needed.
Negation
To negate a balanced ternary number, flip the sign of every trit:
+1 0 -1 +1 (the number +7 in balanced ternary)
-1 0 +1 -1 (the number -7 -- just flip all signs)
This is simpler than binary negation (which requires inverting all bits and adding one) and never produces edge cases or overflow.
Ternary Encoding in Trinity
Trinity represents trits in memory using a compact packed encoding that stores each trit in 2 bits. The mapping is:
| Trit Value | 2-bit Encoding |
|---|---|
| -1 (T) | 00 |
| 0 | 01 |
| +1 | 10 |
This encoding uses 2 bits per trit, achieving an effective density of 1.585 / 2 = 79.3% of the theoretical maximum. While not perfectly optimal (the theoretical minimum is log2(3) = 1.585 bits per trit), the 2-bit encoding enables fast bitwise operations and aligns naturally with byte boundaries.
The HybridBigInt type in Trinity manages this encoding transparently. It maintains two representations: a packed form for memory-efficient storage and an unpacked form (an array of individual trit values) for fast computation. Conversions between the two are performed lazily -- only when needed -- and are cached to avoid redundant work.
With this encoding, a 256-trit vector (a common dimension in Trinity's VSA operations) occupies just 64 bytes in packed form, compared to 256 bytes if each trit were stored in a full byte, or 1024 bytes if stored as 32-bit floats.
Comparison with Binary
| Property | Binary | Balanced Ternary |
|---|---|---|
| Digit values | {0, 1} | {-1, 0, +1} |
| Info per digit | 1.000 bits | 1.585 bits |
| Radix economy | 2.885 (94.7%) | 2.731 (100%) |
| Negation | Invert + add 1 | Flip all signs |
| Signed numbers | Two's complement | Native |
| Truncation | Rounds toward zero | Rounds to nearest |
| Multiplication | Full multiply | Add/subtract only (for single trit) |
Applications in Trinity
The balanced ternary representation is the foundation of every subsystem in Trinity:
- VSA operations (bind, unbind, bundle) operate element-wise on ternary vectors. Binding uses trit multiplication; unbinding is identical to binding (the operation is its own inverse for non-zero trits).
- BitNet inference (Firebird) quantizes LLM weights to {-1, 0, +1}, turning matrix multiplications into accumulations.
- The Ternary VM (VM) executes bytecode with a ternary instruction set, operating on ternary stack values.
Further Reading
- Ternary Computing Concepts -- overview and motivation
- The Trinity Identity -- why the golden ratio connects to base-3
- VSA API Reference -- ternary vector operations
- HybridBigInt API Reference -- packed trit storage