SkillAgentSearch skills...

Juniper

Java pseudo-random number generation code with minimal dependencies.

Install / Use

/learn @tommyettinger/Juniper
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

juniper

Java pseudo-random number generation code with minimal dependencies.

Juniper the Dog

JavaDocs

JavaDocs are hosted here.

How to get it?

With Gradle, the dependency (of the core module, if you have multiple) is:

api "com.github.tommyettinger:juniper:0.10.2"

In a libGDX project that has a GWT/HTML backend, the html/build.gradle file should additionally have:

implementation "com.github.tommyettinger:digital:0.10.0:sources"
implementation "com.github.tommyettinger:juniper:0.10.2:sources"

And the GdxDefinition.gwt.xml file should have:

<inherits name="com.github.tommyettinger.digital" />
<inherits name="com.github.tommyettinger.juniper" />

If you don't use Gradle, then with Maven, the dependency is:

<dependency>
  <groupId>com.github.tommyettinger</groupId>
  <artifactId>juniper</artifactId>
  <version>0.10.2</version>
</dependency>

There are also releases here on GitHub if you don't use any project management tool. You will need to obtain digital as well, of the appropriate version for the juniper release you picked.

What is it?

Juniper provides a superset of the features of java.util.Random with an EnhancedRandom abstract class and various concrete implementations. Some of these implementations are well-known algorithms, such as RomuTrioRandom and Xoshiro256StarStarRandom, but most are essentially new here. All of them have been tested with PractRand and pass at least a 64TB battery of tests without any anomalies considered worse than "unusual". Many have also undergone additional, significantly-more-strenuous testing on the GPU, and the generators that fail that testing only do so after at least 100PB of data is generated.

This library is compatible with Java 8 language level and higher. It uses only language features from Java 8, and does not need any APIs introduced in Java 8. This should allow it to be used on Android, GWT, and RoboVM. Some extremely out-of-date versions of Android may not be compatible with Java 8 even with core library desugaring; Google is dropping support for older Android versions and developers should follow suit. RoboVM has always had support for language level 8, but in the main branch has never supported Java 8 APIs (not a problem here). GWT has had support for Java 8 in some form since the 2.8.x line of releases; using 2.11.0 or higher is recommended because it improves on this support. Most of these are made at least a little easier by using gdx-liftoff to generate projects (assuming you are making a libGDX app or game), since gdx-liftoff handles core library desugaring on Android and uses GWT 2.11.0 by default.

You can preview what some distributions look like on this page. It uses libGDX to compile to a webpage while still working if run as a desktop application, and several parts of this library have been tailored to fit as many libGDX target platforms as possible.

The name comes from my dog Juniper, who appears to have a deterministic, but seemingly-random, response to any new person she meets.

What are these generators?

tl;dr: You should use com.github.tommyettinger.random.AceRandom if you want the highest speed and the highest quality over a long sequence of outputs from one generator. You can instead use com.github.tommyettinger.random.DistinctRandom if you want one state that gets fully randomized from the first output, or com.github.tommyettinger.random.FlowRandom if you want the same idea as DistinctRandom but have two states that you want to use like inputs to a hash, or you want multiple approximately-independent streams. All of these have a period of at least 2 to the 64, which is probably enough for any game and many non-game tasks. If you target GWT, you should generally use com.github.tommyettinger.random.ChopRandom, since it is much faster on GWT than other generators here, and only slightly slower on desktop platforms.

Several high-quality and very-fast random number generators are here, such as com.github.tommyettinger.random.PouchRandom, com.github.tommyettinger.random.WhiskerRandom, com.github.tommyettinger.random.FlowRandom, com.github.tommyettinger.random.DistinctRandom, com.github.tommyettinger.random.AceRandom, and com.github.tommyettinger.random.PasarRandom. These extend the abstract class com.github.tommyettinger.random.EnhancedRandom, and that extends java.util.Random for compatibility.

The simplest starting point is DistinctRandom; it is much like Java 8's SplittableRandom algorithm, but doesn't support splitting (since the possibility of low-quality splits is a major criticism of SplittableRandom), and otherwise uses the same style of code. It simply adds to a counter by a large constant, takes the current value of that counter, gets a unary hash of it using a similar algorithm to MurmurHash's finalizer step, and returns that. "Unary hash" is another way of saying "a function that takes an n-bit input and transforms it into a random-seeming n-bit output." The main reasons you might want DistinctRandom are that it has exactly one long of state, and that it produces every possible output from nextLong() exactly once over its cycle, with no repeats until potentially years later. DistinctRandom is able to jump to any point in its cycle, which has a length of exactly 2 to the 64, in constant time using the skip() method.

This ability to skip is also shared by FlowRandom, but FlowRandom has many possible cycles (2 to the 64 possible cycles, each with 2 to the 64 long outputs) called streams. FlowRandom is very similar to DistinctRandom in most ways, except that it has two long states that each cycle with the same period. The relationship between the states is what determines the current stream, and you can access a FlowRandom's stream with getStream() or change it with setStream() or shiftStream(). Streams here are not correlated at all, as far as I have been able to determine. FlowRandom isn't as fast as some other generators here that have streams (such as LaserRandom), but it seems to be much more robust statistically when its stream changes. Unlike DistinctRandom, a given stream can produce the same result more than once, and will generally be unable to produce roughly 1/3 of possible long outputs. All possible streams, if concatenated, would include every long result exactly 2 to the 64 times each. Concatenating all possible streams is, roughly, how OrbitalRandom works; its code is almost identical to FlowRandom's, but it has one cycle of 2 to the 128 long outputs, at a small speed cost.

AceRandom is the main recommended generator, this time with 5 states. It is the fastest generator here when benchmarked on Java 17 and newer. One state is a counter, which makes AceRandom have a minimum period of 2 to the 64, though its maximum period is much, much higher and its expected period is much higher than I could reach by brute-force generation with current hardware given a century. Ace uses only add, rotate, XOR, and subtract operations. These operations each take the same amount of time on current CPUs, a property that some cryptographic RNGs use to avoid timing attacks. AceRandom is a good all-around default because it resists various ways generators can be constructed so they are correlated with each other; it is also almost always faster than PouchRandom, and much faster than FlowRandom.

WhiskerRandom is often considerably faster than DistinctRandom (which is no slouch either), and generally has very high quality, but does not have a guaranteed cycle length -- a given seed could be found that has an unusually short cycle, which would reduce the usefulness of the generator. But, finding such a seed at random is so improbable for a generator with 256 bits of state that it can essentially be ignored as a weakness unless considering adversarial access (and you should not use any of the generators here if that is the case, since none are cryptographically secure). A known potential flaw of WhiskerRandom (and many generators tested so far) is that generators with numerically similar initial states, such as with a generator initially set to the state 1, 1, 1, 1 and another generator set to 2, 1, 1, 1, are very often highly correlated. This isn't a problem if you use setSeed(), since it won't produce numerically similar states often (or possibly won't at all), but can be a problem if you try to use a WhiskerRandom as a hash.

PouchRandom is the fastest generator here when benchmarked on Java 8; it acts like WhiskerRandom but has a guaranteed minimum cycle length of 2 to the 63 (as long as it isn't somehow forced into an invalid state, which its own methods cannot do). While it disallows certain states (state D has to be an odd number, and the other states can't all be 0 at once), if that isn't a problem for your application, it is probably a solid choice. After producing about 25 outputs, numerically similar initial states won't appear correlated, and shouldn't become correlated again for a very long time. It has 4 states and uses multiplication (in this case, it multiplies one state by another, always odd, state).

There's lots of others here. TrimRandom, PasarRandom, ScruffRandom are all good but have the same or similar known flaw that WhiskerRandom has regarding numerically-similar initial states. TricycleRandom and FourWheelRandom don't have that flaw, but aren't quite as fast or high-quality as AceRandom or PouchRandom.

Except for DistinctRandom and FlowRandom, all of these mentioned generators are fast because they are designed to take advantage of ILP -- Instruction Level Parallelism. The idea here came from Mark Overton's Romu generators (see below for RomuTrioRandom), which also have their period sep

Related Skills

View on GitHub
GitHub Stars14
CategoryDevelopment
Updated3d ago
Forks0

Languages

Java

Security Score

90/100

Audited on Mar 31, 2026

No findings