Xorshift 生成器
Xorshift Generators

原始链接: https://www.alanzucconi.com/2026/08/15/xorshift-generators/

本文探讨了 **xorshift 生成器**。这是一类高效且确定性的算法,在全球范围内被广泛用于模拟软件中的随机性,从《我的世界》(Minecraft)等过程生成类游戏,到驱动大多数浏览器的 V8 JavaScript 引擎,均有其应用。 xorshift 生成器由乔治·马萨利亚(George Marsaglia)于 2003 年开发,它依赖位运算中的异或(XOR)和移位操作,而非速度较慢的乘法或除法。其效能取决于对“极大值三元组”(maximal triplets)的选择,即通过特定的移位数值,使生成器在重复前能够遍历所有可能的非零值。 作者利用线性代数解析了这些算法背后的数学原理,将状态转换表示为矩阵,并使用伯勒坎普-梅西(Berlekamp-Massey)算法来识别本原多项式。尽管 xorshift 生成器性能极高,但它们是线性的,因此在加密安全性上存在缺陷;一旦获知内部状态,即可轻易预测后续数值。 为了消除线性伪影并提高统计质量,塞巴斯蒂安诺·维尼亚(Sebastiano Vigna)在后期的版本(如 **xorshift64*** 和 **xorshift128+**)中引入了非线性的“加扰”技术(例如乘法或加法)。尽管存在缺陷,这些算法依然无处不在,因为它们在速度与模拟、图形及游戏逻辑所需的随机性之间取得了完美平衡,证明了所谓的“随机性”往往只是高度结构化的数学。

抱歉。
相关文章

原文

This article is a deep dive into xorshift generators, one of the most adopted families of algorithms for the generation of pseudorandom numbers.

This is a companion article to a video documentary which will be released in the following weeks.

If you want to learn more about xorshift generators, including tables of maximal triplets, you can consult the Xorshift: Appendix.

Introduction

At this very moment, billions of devices are running these three lines of code.

uint64_t x;
uint64_t y;
            
uint64_t next()
{
	uint64_t t = x;
	const uint64_t s = y;
	x = s;
	t ^= t << 23; // a
	t ^= t >> 17; // b
	t ^= s ^ (s >> 26); // c
	y = t;
	return t + s;
}

They hide inside virtually every browser, and the servers they talk to.
They control the events that happen while you’re playing. From the worlds you explore, to the cards in your hand.

This algorithm—and the others like it—are the metaphorical dice that your computer rolls every time it makes a decision. But …what does it do, and how does it work?

I’m Alan Zucconi, and in this documentary we’ll travel to the very source of randomness, by dissecting the most popular family of pseudorandom algorithms: xorshift generators.

And we’ll do it by asking a very simple question: what makes 23, 17 and 26 special?

Part 1: Xorshift Generators

Pseudorandom numbers

Computers are terrible at being random. In fact, every “random” number you have ever generated was almost certainly completely deterministic.

But it turns out that with the right algorithm, deterministic sequences can look random enough to power games, simulations, procedural content, cryptography—well, okay, maybe not cryptography …but we’ll get back to that later.

Most of these pseudorandom techniques work on the same principle: there’s a function that we apply to the previous number in the sequence, to get the next one. The first one—the seed—is given, and the rest of the sequence follows from it.

That’s how games like No Man’s Sky and Minecraft are able to “compress” entire galaxies and worlds in a number. That’s the seed that kickstarts the sequence of pseudorandom numbers, and with the same seed, you get the same sequence. If you are curious to find out more about this, I made an entire documentary about the world generation of Minecraft.

Marsaglia’s Xorshift

In 2003, mathematician George Marsaglia published a short paper (“Xorshift RNGs“) introducing a family of generators that fall into this category, based almost on two operations: XOR and shifts. Unsurprisingly, they’re called xorshift generators.

This one here, for example, generates sequences of 32-bit numbers:

uint32_t x;
uint32_t xorshift32(void)
{
	x ^= x << 13;
	x ^= x >> 17;
	x ^= x << 5;
	return x;
}

📝 Variable types used by Marsaglia

Let’s see how it works, line by line!

It takes the previous number in the sequence (x), makes a copy, and shifts its bits 13 times to the left. It drops the bits that overflow, and fills the rest with zeros.

Rendered by QuickLaTeX.com

left shift x << 1

Rendered by QuickLaTeX.com

right shift x >> 1

Now: every time there’s a “1”, it flips the corresponding original bit: that’s a bitwise XOR.

Next, it does another xorshift: copy, shift, XOR. But this time, shifting by 17 bits to the right. And once more, by 5 bits to the left.

Diagram of xorshift32

No multiplications. No divisions. No lookup tables. Just a few bitwise instructions. If we paint a texture using these values as colours …well, it looks pretty random to me!

xorshift32 (13, 17, 5)

But why did Marsaglia himself recommend shifting by 13, 17, and 5 bits? Why not 42, 67, and 1729? And why exactly three xorshift operations, and not two, or four?

It’s questions like these that you should always ask when you see magic numbers in someone else’s code. Like 0x5F3759DF from Quake’s Fast Inverse Square Root function, or the golden ratio constant 0x9e3779b97f4a7c15 found in a lot of hashing algorithms.

These constants might be arbitrary, but they’re never random. Because they almost always reveal something deeper about the problem, and the way in which it was solved.

So… what happens if we try a different triplet!?

These sequences don’t look that random anymore! And that’s because the period of most triplets—which is, how many unique numbers they generate—is far too small to be used for procedural generation!

What makes 13, 17, and 5 special is that it’s a maximal triplet! Out of all the possible ones—that’s \left(32-1\right)^3=29,791—there are only 162 that generates all 32-bit numbers. Well, all except zero, which xorshifts can never generate anyway.

Over four billion states (2^{32}-1=4,294,967,295) from three lines of code.

If you’re thinking this works because these (13, 17, and 5) are all prime numbers… well, you’d be wrong! In fact, (2, 15, 25) and (16, 21, 9) work… just as fine!

And if you test all combinations, you’ll also find out that we need 3 xorshifts, because you can’t generate all 32-bit numbers with just two of them!

shifts \ bits32 bits64 bits
2 shifts
<< a
>> b
no solutionsxorshift64
(7, 9)

2 solutions

3 shifts
<< a
>> b
<< c
xorshift32
(13, 17, 5)

162 solutions

xorshift64
(13, 7, 17)

550 solutions

Table of popular xorshift algorithms using 2 and 3shifts

When visualised in three dimensions, the maximal triplets arrange on a complex structure. Below, you can see the arrangement of the canonical maximal triplets for N=256.

As N gets larger, the structure appears to get denser, not richer. This indicates that the lattice structure of maximal triplets is consistent across different values of N.

Finding triplets

But how do we find those maximal triplets? The most naïve way is to count how many unique numbers each one generates. With 32 bits we have almost 30.000 triplets to check (\left(n-1\right)^3=\left(32-1\right)^3=29,791), and each one generates up to almost 4.3 billion unique numbers (2^n-1=2^{32}-1=4,294,967,295). Which is… a lot, but nothing we can’t test in a few hours!

So what about 64-bit numbers? You may think that doubling the number of bits, also doubles the search time. But that would be …incorrect! It is every new bit that doubles the range of numbers we can represent. So going from 32 to 64 bits actually doubles the range—and the search time—32 times! That’s roughly …49 million years. Give or take.

So how could Marsaglia do this—in minutes—back in 2003? Well, he did it with two very clever tricks…

❓ The Hull-Dobell Theorem

Part 2: Matrix Approach

Underneath their code, xorshift generators are secretly just matrix multiplications:

(2)   \begin{equation*}      x_{n+1} = x_n \, T \end{equation*}

And this matters, because Linear Algebra can predict the length of a sequence, without calculating all of its elements.

Let’s treat the bits of our seed as the components of a vector. Every instruction in our code translates to a linear transformation between vectors and matrices. That’s our first trick, and it’s not a coincidence: the algorithm is literally designed like this to make the search for maximal triplets feasible.

Bitshifts

You probably already know that when a vector is multiplied by the identity matrix, it remains unchanged.

(3)   \begin{equation*}  x = x \, \mathbb{I}=x \, \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \end{equation*}

This happens because matrix multiplication is defined in such a way that the positions of the 1s in each column define which bits are linearly combined to produce the output vector.

Shifting the diagonal down shifts the output bits to the left. Let’s call this matrix L!

(4)   \begin{equation*}  L= \begin{pmatrix} 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \end{pmatrix} \end{equation*}

If we want to shift by two bits (x << 2), we multiply the result by L once more!

And because multiplication is associative, it turns out that to shift a vector by n bits (x << a), we multiply it by L raised to the a-th power.

(5)   \begin{equation*}  x \, \underset{a}{\underbrace{L \cdots L }} = x \, L^a \end{equation*}

And the exact same reasoning holds for a right shift (x >> a), which is equivalent to x \, R^a:

(6)   \begin{equation*}  R = \begin{pmatrix} 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 \end{pmatrix} \end{equation*}

Xor

The bitwise XOR (between two variables) happens to behave exactly like the component-wise addition (between two vectors).

And since all those vectors contain either zero or one, let’s say that 1+1=0. You can think about this as “addition without carry over”, or “addition followed by modulo 2”. But—for the hardcore fans of Field Theory—that’s simply a finite field with two elements.

❓ Finite field with two elements

❓Galois Fields and Boolean Algebra

❓ Galois field and prime numbers

❓ Galois Fields: GF(2), GF(2)ⁿ, and GF(2ⁿ)

Xorshift

We now have everything we need to express one single xorshift operation (x ^ (x << a)) in its matrix form: x \, \left(\mathbb{I} + L^a\right).

And if we want to perform three of them in series, we just multiply the original number by three separate matrices!
That’s our final matrix T, and it looks like this:

(7)   \begin{equation*}  T = \left(\mathbb{I}+L^{13}\right)   \left(\mathbb{I}+R^{17}\right)   \left(\mathbb{I}+L^{5}\right) \end{equation*}

It is important to remember that those are binary matrices: every cell can only contain zero or one. This means that we can visualise them as bitmaps:

Rendered by QuickLaTeX.com

Rendered by QuickLaTeX.com

Rendered by QuickLaTeX.com

And when we do that, the matrix T looks like this:

Rendered by QuickLaTeX.com

🟰 Show me the derivation

Part 3: Order Test

Now that the xorshift is framed in terms of vectors and matrices, we can take advantage of Linear Algebra. The period of a generator—which is, how many unique numbers it produces—corresponds to the multiplicative order of its matrix.

That is defined as the smallest value k for which T^k=\mathbb{I}:

(8)   \begin{equation*}  \operatorname{ord}\left(T\right) = \min\{k \in \mathbb{Z}^+ \, |\ , T^k = \mathbb{I} \} \end{equation*}

Hence, a triplet is maximal if and only if that multiplicative order is equal to all the possible non-zero numbers that can be represented with m bits:

(9)   \begin{equation*}  \underset{\text{maximal}}{\underbrace{\left(a, b, c\right) }} \Leftrightarrow \operatorname{ord}\left(T\right) = 2^m-1 \end{equation*}

Let’s see what all of this means in practice.

Each time we multiply by T, we progress in the sequence.

(10)   \begin{equation*}  \begin{array}{lcl} x_0 &  & \\ x_0 \, T &=& x_1 \\ x_1 \, T &=& x_2 \\ &\dots& \\ x_n \, T &=& x_{n+1} \\ \end{array} \end{equation*}

which means that:

(11)   \begin{equation*}  x_0 \,{\underset{n}{\underbrace{T \, \dots \, T}}} = x_{n} \end{equation*}

Matrix multiplication is associative, so we can group all the Ts together. The n-th element of the sequence is found by multiplying the first one by T^n, skipping all the intermediate ones!

(12)   \begin{equation*}  x_0 \, T^n = x_{n} \end{equation*}

After a certain number of iterations—let’s call it p for period—we get back to the starting element, and the sequence repeats:

(13)   \begin{equation*}  x_0 \, T^p = x_{0} \end{equation*}

Since x_0 is guaranteed to be non-null (as xorshift generators can never produce 0), we can simplify the x_0s on both sides, and find out that if a sequence repeats after p iterations, then T^p is the identity matrix! Let’s call this the identity test.

Maximal Order test

Now, if a number p passes the identity test, it doesn’t necessarily mean that’s the actual period of our sequence! If numbers repeat after 30 iterations; they’ll also repeat after 60, after 90, after 120 …because it’s like looping through the sequence two, three, four times…

In a nutshell, if n passes the identity test, so do all of its integer multiples k\,n (with k \in \mathbb{Z}+):

(14)   \begin{equation*}  T^n = \mathbb{I} \,\,\, \Rightarrow \,\,\, T^{k\,n} = \mathbb{I} \end{equation*}

This also means that if 30 passes the identity test, perhaps that’s because the actual period is 15 and we just looped twice! Or perhaps it was 10, and we looped 3 times. Or it was 6, and we looped 5 times!

You get where I’m going with this: for 30 to be the smallest period, none of its divisors should pass the identity test—including 5, 3, and 2.

In fact, (\eq{Tn_eq}) also implies that if a number doesn’t pass the identity test, then none of its proper divisors will:

(15)   \begin{equation*}  T^n \neq \mathbb{I} \,\,\, \Leftarrow \,\,\, T^{k\,n} \neq \mathbb{I} \end{equation*}

But we don’t have to test them all! 15 is 5 times 3: if 15 doesn’t pass the identity test, neither 5 nor 3 do! If 10 doesn’t pass the identity test, neither 5 nor 2 do. And the same for 6!

So what’s the minimum number of divisors to test?

Well, the prime factors of 30 are 2, 3, and 5; and its divisors are combinations of them.
Testing larger numbers first helps prune more divisors out, so we can remove one prime factor at a time from 30, and those are the minimum number of tests that covers all other cases.

Rendered by QuickLaTeX.com

Going back to our original 64bit problem: for a triplet to have period p=2^{64}-1, p itself must pass the identity test, while that same test must fail when we divide p by each of its prime factors.

That reduces the number of iterations from 127–all possible divisors including itself–to just 8!

That’s the second trick! And that’s how we find that with 64 bits, we have 550 maximal triplets. With Marsaglia choosing 13, 7 and 17.

🟰Prime factorisation of Fermat numbers

Part 4: Companion Matrices

We’ve done 32 bits… we’ve done 64… what about 128?

Our algorithm finds 1072 full-period triplets, but none of them can be found in the selected list presented by Marsaglia: (11, 8, 19), (5, 14, 1), (15, 4, 21), (23, 24, 3), and (5, 12, 29).

What’s going on? Well, modern architectures natively support bitwise operations with 32 and 64 bits. But that’s not typically the case for 128, making a “true”, “monolithic” xorshift128 neither efficient to run, nor elegant to write.

📄 Monolithic xorshift128 code

To get around this, Marsaglia proposed a similar but different algorithm: one that still uses bitwise operations, but stores the 128-bit state in four 32-bit integers: x, y, z, and w:

uint32_t x;
uint32_t y;
uint32_t z;
uint32_t w;

uint32_t next()
{
	uint32_t t;
	t = x ^ (x << 11); // a
	t = t ^ (t >>  8); // b
	x = y;
	y = z;
	z = w;
	w = w ^ (w >> 19); // c
	w = w ^ t;

	return w;
}

📄 Show me the original code

❓ Returning a partial state

❓ Finding consecutive numbers

Let’s see how this version works!

Lines 10-12 shift the lower 96 bits to the right, while the rest reshuffles x and w into the lower 32 bits.

The auxiliary variable t stores the result of a left xorshift on x, followed by a right xorshift.

w goes through a right xorshift, and the result is xor-ed with t.

Diagram of xorshift128

We can work backwards to reconstruct the matrix that maps the input state \left[x, y, z, w\right] to the output state \left[y, z, w \right] and a linear combination of x and w.

And that’s what it looks like, both in block matrix form …and in its full glory.

(20)   \begin{equation*}  T = \begin{pmatrix} 0 & 0 & 0 & A \\ \mathbb{I} & 0 & 0 & 0 \\ 0 & \mathbb{I} & 0 & 0 \\ 0 & 0 & \mathbb{I} & B \end{pmatrix} = \begin{pmatrix} 0 & 0 & 0 & \left(\mathbb{I} + L^a\right) \left(\mathbb{I} + R^b\right) \\ \mathbb{I} & 0 & 0 & 0 \\ 0 & \mathbb{I} & 0 & 0 \\ 0 & 0 & \mathbb{I} & \left(\mathbb{I} + R^c\right) \end{pmatrix} \end{equation*}

Rendered by QuickLaTeX.com

This looks dangerously similar to a Frobenius companion matrix—studied in the context of primitive polynomials for their connection to maximal sequences—hence making it a “natural choice” to restrict the search.

Part 5: Characteristic Polynomials

Later papers explored that connection further, ditching Marsaglia’s naïve order test, in favour of more targeted approaches. After all, we don’t need to find all maximal triplets: we just need to find one that’s good. And primitive polynomials have played a key part in that.

Every square matrix is paired with a characteristic polynomial, which is defined as:

(21)   \begin{equation*}  p\left(x\right) = \det\left(T-x \mathbb{I}\right) \end{equation*}

The symbol \lambda is often used for the variable (instead of the more common x), especially when eigenvalues and eigenvectors are involved. Regardless, the result is a polynomial of degree n, which is used to study certain properties of the matrix.

As it turns out, there’s a special connection between the maximality of a triplet, and its characteristic polynomial: its primitivity.

A polynomial is primitive over \mathrm{GF}\left(2\right) if any of its roots \alpha is a primitive element of \mathrm{GF}\left(2^n\right). In a nutshell, it means that:

(22)   \begin{equation*}  \mathrm{GF}\left(2^n\right) = \{0\} \cup \{   \alpha^0,   \alpha^1,   \ldots,   \alpha^{2^n-2} \} \end{equation*}

It means that integer powers of a root are cycling through all non-zero elements. Which is exactly what the multiplicative order or the matrix T captured.

Krylov Space

Testing for the primitivity of the characteristic polynomial is, generally speaking, much more efficient than testing the multiplicative order of the matrix. Matrix calculations are, in fact, expensive: the multiplication of a dense x \times n matrix by a vector has a computational complexity of \mathcal{O}\left(n^2\right), while multiplying two dense matrices is \mathcal{O}\left(n^3\right).

A “naïve” test for primitivity would first build the matrix T, then extract the characteristic polynomial by solving \det\left(T-x\mathbb{I}\right). All of those steps are very expensive, and would likely counteract any actual speedup when compared to the traditional order test seen before.

What makes this approach feasible, is that we can reconstruct the characteristic polynomial without building its associated matrix T. We do that by constructing the so-called Krylov space first, a part of which is then used by the Berlekamp-Massey algorithm to reconstruct the characteristic polynomial.

A Krylov space is built by multiplying a starting vector by a matrix over and over:

(23)   \begin{equation*}    v,   v \cdot T,   v \cdot T^2,   v \cdot T^3,  \cdots \end{equation*}

Normally, this would be expensive as it requires not only the construction of T, but also performing a vector multiplication for every element of the space.

For xorshift generators, that is not necessary: the Krylov sequence is nothing more than the sequence of states generated by the pseudorandom algorithm, which is exceptionally fast to compute!

❓ Which vector v to use?

❓ How many vectors to generate?

❓ What about xorshift128?

Berlekamp–Massey Algorithm

The sequence is then passed to the Berlekamp–Massey algorithm, an algorithm which reconstructs minimal polynomials from linear recurrent sequences in arbitrary fields.

The minimal polynomial m\left(x\right) is not exactly what we’re after, but it’s close! It’s guaranteed to be a divisor of the characteristic polynomial.

From this, we find ourselves in two scenarios:

This means that the BM algorithm either returns the characteristic polynomial, or one of its proper factors (revealing that the original triplet was not maximal). What it cannot do, on its own, it so tell us if a triplet is maximal. We still don’t know if the returned characteristic polynomial is irreducible, let alone primitive.

But once extracted, the characteristic polynomial can be tested for both conditions, using the same order test previously devised for matrices (but applied to polynomials over \mathrm{GF}\left(2\right)):

Rendered by QuickLaTeX.com

📝 Maximality and primitivity

Frobenius Companion Matrices

We have learnt in the previous section that a triplet is maximal if its associated characteristic polynomial is primitive. This connection goes both ways: every primitive polynomial represents a linear recurrence from which a generator can be extracted.

Rather than finding maximal triplets by brute force testing their maximality, it is sometimes more efficient to find a polynomial that is known to be primitive, and rebuild its associated matrix.

The simplest way to do that is through Frobenius companion matrices, which we have briefly mentioned before:

(24)   \begin{equation*}  C\left(p\right) = \begin{pmatrix} 0 & 0 & \cdots & 0 &-c_0 \\ 1 & 0 & \cdots & 0 &-c_1 \\ 0 & 1 & \cdots & 0 &  -c_2 \\ \vdots & \vdots & \ddots & \vdots &  \vdots \\ 0 & 0 & \cdots & 1 & -c_{n-1} \end{pmatrix} \end{equation*}

whose characteristic polynomial is expressed in the following form:

(25)   \begin{equation*}  p\left(x\right) =c_0 + c_1 x + \cdots + c_{n-1}x^{n-1} + x^n \end{equation*}

The beauty of a companion matrix is that it contains the coefficients of its characteristic polynomial. And if that is known to be primitive, its companion matrix will be maximal.

This is one of the ideas behind Marsaglia’s companion block matrices, which were indeed designed with a similar block structure in mind.

❓ Marsaglia’s companion block matrices are Frobenius companion matrices

📚 Linear-Feedback Shift Registers

Part 6: Testing

Just because a triplet is maximal, it doesn’t mean it’s also… good.

Different full-period sequences have different statistical properties, and not all of them make good random number generators.

When a sequence looks predictable, it produces linear artefacts which can manifest pretty strongly in procedural terrains and textures.

So what separates the good generators from the bad ones? It’s two things mostly: number one, they should be uniformly distributed, so that all numbers have roughly the same chance. And number two: those numbers should be independent of each other, which is really what makes them “unpredictable”—so to speak.

Basically, they have to behave like virtual dice.

One useful way to measure how “well-behaved” a sequence is, is—well—to test it using tools like dieharder or TestU01. They crunch millions of those numbers, and by measuring their distribution, they infer the underlying properties of their generator.

And perhaps that’s how Marsaglia chose (11, 8, 19) for his xorshift128, and not any of the other 46 maximal triplets.

And even though it was published in 2003, this implementation is still very relevant today because it’s the one used by the most popular game engine in the world. Out of the millions of Unity games out there, almost all of them are powered by the three lines of code that makes xorshift128.

So, wouldn’t it be funny if (11, 8, 19) wasn’t actually the best triplet!? And in fact, it isn’t! It’s probably (11, 5, 12), but—to be fair—that might be just marginally better.

Does it mean that Unity should change it? Hell, no! The integrity of so many games depend on the reliability of its generator, so it’s one of those things that can’t really be changed anymore without the risk of breaking thousands of games!

But in fairness, Unity could perhaps offer a number of different algorithms, like Java does with its java.util.random package.

❓ Random number generators used by game engines

Part 7: Beyond Marsaglia’s xorshift

By the early 2000s, Marsaglia’s xorshifts had become incredibly popular. They were tiny.

They were fast. And they had enormous periods.

But their strength—their mathematical elegance, their linearity—turns out to also be their biggest weakness.

Modern statistical test suites don’t just check whether numbers look random. They search for subtle correlations hidden deep inside the sequence. And because xorshifts are completely linear, some of those tests can detect structures that shouldn’t exist in a truly random source.

One solution to this problem was not to change the xorshift algorithm itself, but to apply a non-linear transformation just to its output.

xorshift64*

Back in 2014, Computer Scientist Sebastiano Vigna found himself in need of a better random number generator than the ones he had available at the time. He continued Marsaglia’s work in a paper titled “An experimental exploration of Marsaglia’s xorshift generators, scrambled“, introducing xorshift64*. That’s a traditional xorshift64, with its output multiplied by a constant before being returned:

uint64_t x;
uint64_t next(void)
{
	x ^= x >> 12; // a
	x ^= x << 25; // b
	x ^= x >> 27; // c
	return x * UINT64_C(2685821657736338717);
}

❓ Why 2685821657736338717?

The state of the xorshift is not changed, so all the results (and the triplets) found for xorshift64 also apply to xorshift64* as well. However, the multiplication scrambles the output bits enough to pass most dieharder and TestU01 tests.

Technically, that’s a linear operation between integer numbers, but multiplication and additions are not linear operations in Boolean algebra (where our matrices are defined).

As Vigna noted in his paper, the scrambling operation fundamentally changes not just the sequence, but also its statistical properties. While the maximality of a triplet remains unchanged, its quality might not. This means that the “best” triplets for xorshift64 and xorshift64* are likely different.

xorshift128+

In the same year, Vigna also published “Further scramblings of Marsaglia’s xorshift generators“, where he replaced the multiplication with a constant, with the addition between state variables.

He implemented a traditional xorshift128 generator using two 64-bit variables (below, x and y). But instead of returning just one of them, he returned their sum:

uint64_t x;
uint64_t y;
            
uint64_t next()
{
	uint64_t result = x + y;
	uint64_t t;
	t = x ^ (x << 23); // a
	t = t ^ (t >> 18); // b
	x = y;
	y = y ^ (y >>  5); // c
	y = y ^ t;
	return result;
}

📄 Original xorshift128p code

Once again, the state itself is left unchanged, but the addition effectively “scrambles” the output bits. And with such a simple change, most of the linear artefacts simply vanished. In comparison to xorshift64*, this version does not rely on an extra parameter.

Unbeknownst to him, his xorshift128+ was about to become one of the most widely deployed pseudorandom number generators ever written.

In 2015, Google announced the adoption of xorshift128+ in their V8 JavaScript engine. Chrome, Edge, Safari, Opera, Firefox, and even Node.js; under the hood, they are all running xorshift128p.

It’s hard to get precise numbers, but that’s roughly 4 to 5 billion active devices globally. And that’s not even counting all other xorshift variants out there. Like Vigna’s xorshiro and xoroshiro algorithms (adopted in various forms by the .NET, Java, and Rust ecosystems), or Marsaglia’s xorwow (which powers NVIDIA CUDA’s random engine), or the 1.5 billion devices running Unity games.

The limits of xorshift generators

Even though xorshift generators are pretty much ubiquitous, there are entire applications in which they simply cannot be used. If you know the state, you can predict the rest of the sequence. One number is typically not enough to do that, but observing just a few of them is all you need to crack the next one. It’s this “predictability” that makes them cryptographically unsafe.

After all, linear sequences like these will always exhibit some statistical weakness.

It’s not a matter of “if” they are going to fail tests suits like dieharder or TestU01: it’s a matter of how long before they detect linear artefacts.

For all those applications where quality and safety are not just desired but necessary, stream ciphers like AES in Counter Mode (AES-CTR) or ChaCha20 have effectively solved all the problems we’ve encountered so far. Because they effectively provided the cryptographic guarantees that xorshift generators never did.

Conclusion

Xorshift generators are and remain beautifully flawed tools. A hidden beauty that perhaps lies in the way randomness emerges from pure structure. And the unsettling knowledge that—at least from a practical and mathematical perspective—true randomness and hidden information are functionally indistinguishable.

So what makes 23, 17 and 26 special? The same thing that makes 11, 8, and 19 special. Or 13, 17 and 5. Or 2, 1, and 4.

They are simply three numbers among billions of possibilities. Yet those choices influence the worlds we explore, the cards we draw, the simulations we run, and the software billions of people use every day. And it’s difficult to imagine how different our world would be, had they been any different.

And perhaps very few people expressed this as eloquently as Robert Coveyou did in 1969:

The generation of random numbers is too important to be left to chance.

If you want to learn more about xorshift generators, including tables of maximal triplets, you can consult the Xorshift: Appendix.

联系我们 contact @ memedata.com