Skip to content

Numeral Systems ​

A number and its digits are different things. IntegerDigits(10, 2) is [1,0,1,0], but the ten did not change — only the spelling. A numeral system is a bijection between integers and digit strings, and once you say it that way it is clear that base-b is one member of a much larger family.

compute-engine already has IntegerDigits and FromDigits, and they already do fixed radix. @enumeratio/numerals adds no head for that — it widens the base slot to take a system, the way Wolfram's IntegerDigits[n, MixedRadix[…]] does. An integer base still goes to the native handler, unchanged.

One slot, many systems ​

SystemDigitsNotes
2, 16, …0…b−1compute-engine's own, untouched
MixedRadixNumerals([…])a different bound per placedays/hours/minutes/seconds
FactorialNumeralsplace k holds at most kthe Lehmer code
PrimorialNumeralsplace k below the (k+1)-th primefactoradic with primes
BalancedNumerals(b)−(b−1)/2 … (b−1)/2signless negatives
NegativeNumerals(b)0…b−1 over (−b)^ksignless negatives
BijectiveNumerals(k)1…k, no zero digitspreadsheet columns
ZeckendorfNumeralsbinary over Fibonacci placesno two adjacent ones
CombinatorialNumerals(k)a strictly decreasing k-tuplek-subset unranking
ResidueNumerals([…])independent residuesno place values at all
OstrowskiNumerals([…])digits over a continued fractiongeneralises Zeckendorf
AdicNumerals(b, prec?)base b, infinite to the LEFTnegatives are all nines

The old names (Factoradic, PrimorialRadix, BalancedRadix, NegativeRadix, BijectiveRadix, Zeckendorf, Ostrowski, CombinatorialSystem, ResidueSystem, MixedRadix) still work — each evaluates to its …Numerals spelling above.

Four of those are not "base-b with a twist": Zeckendorf and Ostrowski constrain digits by a forbidden pattern instead of a per-place bound, the residue system has no place values, and two of them represent negative integers with no sign. The last one is base b read in the other direction — digits that stop on the right and never on the left — and it comes with a value type and arithmetic of its own: b-adic numbers.

The base slot is a system

93 784 seconds is 1 day, 2 hours, 3 minutes, 4 seconds.

InIntegerDigits(93 784,MixedRadixNumerals([24,60,60]))\mathrm{IntegerDigits}(93\,784, \mathrm{MixedRadixNumerals}(\bigl\lbrack24, 60, 60\bigr\rbrack))Out[1,2,3,4]\bigl\lbrack1, 2, 3, 4\bigr\rbrackInFromDigits([1,2,3,4],MixedRadixNumerals([24,60,60]))\mathrm{FromDigits}(\bigl\lbrack1, 2, 3, 4\bigr\rbrack, \mathrm{MixedRadixNumerals}(\bigl\lbrack24, 60, 60\bigr\rbrack))Out93 78493\,784InIntegerDigits(255,16)\mathrm{IntegerDigits}(255, 16)Out[15,15]\bigl\lbrack15, 15\bigr\rbrack
source · editable

Two of these systems are unranking maps in disguise ​

This is the part that earns numeral systems a place next to the collections.

Factoradic digits are the Lehmer code. Writing n in the system whose place values are the factorials, and unranking the n-th permutation in lexicographic order, are the same computation. Pad to one digit per position and the two line up exactly:

Factoradic = Lehmer code

The padded factoradic digits of 5, and the 6th permutation of four things. The code says: take item 0, then item 2 of what is left, then item 1, then the last.

InIntegerDigits(5,FactorialNumerals,4)\mathrm{IntegerDigits}(5, \mathrm{FactorialNumerals}, 4)Out[0,2,1,0]\bigl\lbrack0, 2, 1, 0\bigr\rbrackInSymmetricGroup(4)[6]\mathrm{SymmetricGroup}(4)[6]OutPermutation⁡([1,4,3,2]){\operatorname{Permutation}}([1, 4, 3, 2])
source · editable

The combinatorial number system is k-subset unranking. Its digits are the subset: n = C(c_k,k) + … + C(c_1,1), and the c's — shifted from 0-based to 1-based — are exactly the n-th k-subset in colexicographic order. (KSubsets is lexicographic, so its n-th subset differs.)

The combinatorial number system

Digits [3,2,1] are the 0-based subset; add one to each and you get {2,3,4}, the 4th 3-subset in colexicographic order.

InIntegerDigits(3,CombinatorialNumerals(3))\mathrm{IntegerDigits}(3, \mathrm{CombinatorialNumerals}(3))Out[3,2,1]\bigl\lbrack3, 2, 1\bigr\rbrack
source · editable

So "write this number in that system" and "give me the n-th object of that kind" are one question asked twice. A mixed-radix numeral is a lexicographic rank.

Negatives without a sign ​

Balanced ternary and negabinary both represent every integer — negative ones included — with no sign and no two's-complement convention. In balanced ternary, negating a number is negating each digit.

Signless

5 is 9−3−1, and −5 is its digit-wise negation. Compare the last cell: fixed radix has to drop the sign.

InIntegerDigits(5,BalancedNumerals(3))\mathrm{IntegerDigits}(5, \mathrm{BalancedNumerals}(3))Out[1,−1,−1]\bigl\lbrack1, -1, -1\bigr\rbrackInIntegerDigits(−5,BalancedNumerals(3))\mathrm{IntegerDigits}(-5, \mathrm{BalancedNumerals}(3))Out[−1,1,1]\bigl\lbrack-1, 1, 1\bigr\rbrackInIntegerDigits(3,NegativeNumerals(2))\mathrm{IntegerDigits}(3, \mathrm{NegativeNumerals}(2))Out[1,1,1]\bigl\lbrack1, 1, 1\bigr\rbrackInIntegerDigits(−5,2)\mathrm{IntegerDigits}(-5, 2)Out[1,0,1]\bigl\lbrack1, 0, 1\bigr\rbrack
source · editable

A negative base is one step toward a complex one. Base −1+i-1 + i with digits 00 and 11 writes every Gaussian integer without a sign, and its numerals tile the twindragon. That system has its own guide, Complex bases.

A forbidden pattern instead of a bound ​

Zeckendorf's theorem: every positive integer is a sum of non-consecutive Fibonacci numbers, in exactly one way — and greedy finds it. So the digits are binary, but what makes a string a numeral is that no two ones are adjacent, not a per-place range. A string with two adjacent ones denotes nothing, and FromDigits says so by declining.

Zeckendorf

100 = 89 + 8 + 3. The last cell is not a numeral, so it is left standing.

InIntegerDigits(100,ZeckendorfNumerals)\mathrm{IntegerDigits}(100, \mathrm{ZeckendorfNumerals})Out[1,0,0,0,0,1,0,1,0,0]\bigl\lbrack1, 0, 0, 0, 0, 1, 0, 1, 0, 0\bigr\rbrackInFromDigits([1,0,0,0,0,1,0,1,0,0],ZeckendorfNumerals)\mathrm{FromDigits}(\bigl\lbrack1, 0, 0, 0, 0, 1, 0, 1, 0, 0\bigr\rbrack, \mathrm{ZeckendorfNumerals})Out100100InFromDigits([1,1],ZeckendorfNumerals)\mathrm{FromDigits}(\bigl\lbrack1, 1\bigr\rbrack, \mathrm{ZeckendorfNumerals})OutFromDigits([1,1],ZeckendorfNumerals)\mathrm{FromDigits}(\bigl\lbrack1, 1\bigr\rbrack, \mathrm{ZeckendorfNumerals})
source · editable

Zeckendorf is one continued fraction's worth ​

Ostrowski numeration is the general form. Take any irrational α with continued fraction [a0;a1,a2,…][a_0; a_1, a_2, \ldots]; the denominators of its convergents satisfy qk=akqk−1+qk−2q_k = a_k q_{k-1} + q_{k-2}, and those are the place values. Every integer in range then has exactly one representation

N  =  b1q0+b2q1+⋯+bmqm−1,N \;=\; b_1 q_0 + b_2 q_1 + \cdots + b_m q_{m-1},

subject to 0≤b1<a10 ≤ b_1 < a_1, 0≤bk≤ak0 ≤ b_k ≤ a_k, and — the rule that does the real work — bk−1=0b_{k-1} = 0 whenever bkb_k reaches its ceiling aka_k.

That last condition is what forbids a carry, and it is the general shape of "no two adjacent ones". Feed it the all-ones continued fraction, which is φ=[1;1,1,1,…]\varphi = [1; 1, 1, 1, \ldots], and the qkq_k are the Fibonacci numbers and the ceiling rule collapses to Zeckendorf's. So Zeckendorf is the α=φα = \varphi case — and the package checks the two against each other rather than taking that on trust.

A continued fraction as a numeral system

All quotients 1 is φ, and the digits are Zeckendorf's with one extra forced zero.

InIntegerDigits(20,OstrowskiNumerals([1,1,1,1,1,1,1,1]))\mathrm{IntegerDigits}(20, \mathrm{OstrowskiNumerals}(\bigl\lbrack1, 1, 1, 1, 1, 1, 1, 1\bigr\rbrack))Out[0,1,0,1,0,1,0,0]\bigl\lbrack0, 1, 0, 1, 0, 1, 0, 0\bigr\rbrackInIntegerDigits(20,ZeckendorfNumerals)\mathrm{IntegerDigits}(20, \mathrm{ZeckendorfNumerals})Out[1,0,1,0,1,0]\bigl\lbrack1, 0, 1, 0, 1, 0\bigr\rbrackInIntegerDigits(9,OstrowskiNumerals([2,2,2]))\mathrm{IntegerDigits}(9, \mathrm{OstrowskiNumerals}(\bigl\lbrack2, 2, 2\bigr\rbrack))Out[1,2,0]\bigl\lbrack1, 2, 0\bigr\rbrackInFromDigits([1,2,1],OstrowskiNumerals([2,2,2]))\mathrm{FromDigits}(\bigl\lbrack1, 2, 1\bigr\rbrack, \mathrm{OstrowskiNumerals}(\bigl\lbrack2, 2, 2\bigr\rbrack))OutFromDigits([1,2,1],OstrowskiNumerals([2,2,2]))\mathrm{FromDigits}(\bigl\lbrack1, 2, 1\bigr\rbrack, \mathrm{OstrowskiNumerals}(\bigl\lbrack2, 2, 2\bigr\rbrack))
source · editable

The last cell declines: the middle digit is already at its ceiling, so nothing below it may be non-zero. Which continued fraction you pick changes the arithmetic completely — this is the same [a0;a1,…][a_0; a_1, \ldots] that names elements of the modular group, wearing a different hat.

No place values at all ​

A residue number system writes n as its remainders against a list of moduli. There are no place values and no order — the digits are independent, which is exactly why addition and multiplication are carry-free and can be done in parallel, one channel at a time. By CRT it is a bijection onto [0, ∏mᵢ) precisely when the moduli are pairwise coprime; otherwise it is not a numeral system at all, and a digit string can be inconsistent.

Carry-free arithmetic

23 + 41 = 64, done channel by channel: (2+2, 3+1, 2+6) reduced is (1,4,1), which reads back as 64 — no carries between channels.

InIntegerDigits(23,ResidueNumerals([3,5,7]))\mathrm{IntegerDigits}(23, \mathrm{ResidueNumerals}(\bigl\lbrack3, 5, 7\bigr\rbrack))Out[2,3,2]\bigl\lbrack2, 3, 2\bigr\rbrackInIntegerDigits(41,ResidueNumerals([3,5,7]))\mathrm{IntegerDigits}(41, \mathrm{ResidueNumerals}(\bigl\lbrack3, 5, 7\bigr\rbrack))Out[2,1,6]\bigl\lbrack2, 1, 6\bigr\rbrackInFromDigits([1,4,1],ResidueNumerals([3,5,7]))\mathrm{FromDigits}(\bigl\lbrack1, 4, 1\bigr\rbrack, \mathrm{ResidueNumerals}(\bigl\lbrack3, 5, 7\bigr\rbrack))Out6464InNumeralSystemShape(ResidueNumerals([4,6]))\mathrm{NumeralSystemShape}(\mathrm{ResidueNumerals}(\bigl\lbrack4, 6\bigr\rbrack))Out{Bijective→False,  Integers→0..23,  Digits→[0..3,0..5],  Width→2}\left\lbrace \text{Bijective}→\mathrm{False},\;\text{Integers}→0..23,\;\text{Digits}→\bigl\lbrack0..3, 0..5\bigr\rbrack,\;\text{Width}→2\right\rbrace
source · editable

These are the same CRT channels the hypercomplex page uses to find split units: a residue numeral and a spectral sign vector are the same object, read for different purposes.

Each digit is a residue class — a ResidueClass, an element of ℤ/mᵢ that arithmetic stays inside — and reading the numeral back is the Chinese remainder theorem applied to those classes.

Digits are residue classes

The channels of 23 as elements of ℤ/3, ℤ/5 and ℤ/7; ChineseRemainder of the classes is 23 again, as a class mod 105.

In(23‾3)(41‾3)(\overline{23}_{3})(\overline{41}_{3})Out1‾3\overline{1}_{3}InChineseRemainder(2‾3,3‾5,2‾7)\mathrm{ChineseRemainder}(\overline{2}_{3}, \overline{3}_{5}, \overline{2}_{7})Out23‾105\overline{23}_{105}
source · editable

Bijective base 26 is spreadsheet columns ​

Digits 1…26 with no zero DIGIT — but zero itself is the empty numeral, which is what makes the correspondence a bijection: one string over {1..26}\{1..26\} per non-negative integer, and the empty one is left for zero. So every positive integer has exactly one spelling and there are no leading-zero ambiguities. Map 1…26 to A…Z and you get the column lettering: 26 is Z, 27 is AA, 702 is ZZ, 703 is AAA. Note that there is no numeral for zero at all.

A, …, Z, AA, …

InIntegerDigits(26,BijectiveNumerals(26))\mathrm{IntegerDigits}(26, \mathrm{BijectiveNumerals}(26))Out[26]\bigl\lbrack26\bigr\rbrackInIntegerDigits(27,BijectiveNumerals(26))\mathrm{IntegerDigits}(27, \mathrm{BijectiveNumerals}(26))Out[1,1]\bigl\lbrack1, 1\bigr\rbrackInIntegerDigits(703,BijectiveNumerals(26))\mathrm{IntegerDigits}(703, \mathrm{BijectiveNumerals}(26))Out[1,1,1]\bigl\lbrack1, 1, 1\bigr\rbrackInIntegerDigits(0,BijectiveNumerals(26))\mathrm{IntegerDigits}(0, \mathrm{BijectiveNumerals}(26))Out[]\bigl\lbrack\bigr\rbrack
source · editable

Things worth knowing ​

The round trip is the specification. A numeral system is a bijection, so the only property worth testing is that FromDigits(IntegerDigits(n, S), S) is n — and that is what the tests do, for every system, over a range, rather than pinning spot values. Each system's digit shape is then characterised separately and independently: factoradic digits satisfy d_k ≤ k, Zeckendorf digits have no adjacent ones, balanced digits are bounded by ±(b−1)/2, bijective digits avoid zero.

Declining is an answer. An integer with no numeral in a system (zero in bijective base, anything past ∏mᵢ in a residue system) and a digit string that denotes no integer (adjacent Zeckendorf ones, an out-of-range mixed-radix digit, inconsistent residues) both leave the call standing rather than returning something false.

Nothing native changed. IntegerDigits(n, b) for an integer b is still compute-engine's own handler, and a test pins that against a bare engine.

Not built yet. Non-integer bases — golden-ratio base φ is the interesting one, where the digit string of an integer is infinite to the right unless you allow a fractional part. Also no digit rendering: BaseForm exists natively and could learn these systems, and bijective base 26 obviously wants to print as letters rather than as a list of numbers.