Apache Commons logo Apache Commons RNG

The Apache Commons RNG User Guide

Table of contents

1. Purpose

Commons RNG provides generators of "pseudo-randomness", i.e. the generators produce deterministic sequences of bytes, currently in chunks of 32 (a.k.a. int) or 64 bits (a.k.a. long), depending on the implementation.

The goal was to provide an API that is simple and unencumbered with old design decisions.

The design is clean and its rationale is explained in the code and Javadoc (as well as in the extensive discussions on the "Apache Commons" project's mailing list).

The code evolved during several months in order to accommodate the requirements gathered from the design issues identified in the org.apache.commons.math3.random package and the explicit design goal of severing ties to java.util.Random.

The library is divided into modules:

  • Client API (requires Java 8)

    This module provides the interface to be passed as argument to a procedure that needs to access to a sequence of random numbers.

  • Core (requires Java 8)

    This module contains the implementations of several generators of pseudo-random sequences of numbers. Code in this module is intended to be internal to this library and no user code should access it directly. With the advent of Java modularization, it is possible that future releases of the library will enforce access through the RandomSource factory.

  • Simple (requires Java 8)

    This module provides factory methods for creating instances of all the generators implemented in the commons-rng-core module.

  • Sampling (requires Java 8)

    This module provides implementations that: generate a sequence of numbers according to some specified probability distribution; sample coordinates from geometric shapes; sample from generic collections of items; and other sampling utilities. It is an example of usage of the API provided in the commons-rng-client-api module.

  • Examples

    This module provides miscellaneous complete applications that illustrate usage of the library. Please note that this module is not part of the library's API; no compatibility should be expected in successive releases of "Commons RNG". The examples can be download in the source distribution.

    As of version 1.1, the following modules are provided:

    • examples-jmh: JMH benchmarking (requires Java 8)

      This module uses the JMH micro-benchmark framework in order to assess the relative performance of the generators (see tables below).

    • examples-stress: Stress testing (requires Java 8)

      This module implements a wrapper that calls external tools that can assess the quality of the generators by submitting their output to a battery of "stress tests" (see tables below).

    • examples-sampling: Probability density (requires Java 8)

      This module contains the code that generates the data used to produce the probability density plots shown in this userguide.

    • examples-jpms: JPMS integration (requires Java 11)

      This module implements a dummy application that shows how to use the artefacts (produced from the maven modules described above) as Java modules (JPMS).

    • examples-quadrature: Quadrature (requires Java 8)

      This module contains an application that estimates the number 𝞹 using quasi-Montecarlo integration.

2. Usage overview

Please refer to the generated documentation (of the appropriate module) for details on the API illustrated by the following examples.

  • Random number generator objects are instantiated through factory methods defined in RandomSource, an enum that declares all the available implementations.
    import org.apache.commons.rng.UniformRandomProvider;
    import org.apache.commons.rng.simple.RandomSource;
    
    UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
  • A generator can return a randomly selected element from a range of possible values of some Java (primitive) type.
    boolean isOn = rng.nextBoolean(); // "true" or "false".
    int n = rng.nextInt();         // Integer.MIN_VALUE <= n <= Integer.MAX_VALUE.
    int m = rng.nextInt(max);      // 0 <= m < max.
    int l = rng.nextInt(min, max); // min <= l < max.
    long n = rng.nextLong();         // Long.MIN_VALUE <= n <= Long.MAX_VALUE.
    long m = rng.nextLong(max);      // 0 <= m < max.
    long l = rng.nextLong(min, max); // min <= l < max.
    float x = rng.nextFloat();         // 0 <= x < 1.
    float y = rng.nextFloat(max);      // 0 <= y < max.
    float z = rng.nextFloat(min, max); // min <= z < max.
    double x = rng.nextDouble();         // 0 <= x < 1.
    double y = rng.nextDouble(max);      // 0 <= y < max.
    double z = rng.nextDouble(min, max); // min <= z < max.
  • A generator can fill a given byte array with random values.
    byte[] a = new byte[47];
    // The elements of "a" are replaced with random values from the interval [-128, 127].
    rng.nextBytes(a);
    byte[] a = new byte[47];
    // Replace 3 elements of the array (at indices 15, 16 and 17) with random values.
    rng.nextBytes(a, 15, 3);
  • A generator can return a stream of primitive values.
    IntStream s1 = rng.ints();         // [Integer.MIN_VALUE, Integer.MAX_VALUE]
    IntStream s2 = rng.ints(max);      // [0, max)
    IntStream s3 = rng.ints(min, max); // [min, max)
    LongStream s1 = rng.longs();         // [Long.MIN_VALUE, Long.MAX_VALUE]
    LongStream s2 = rng.longs(max);      // [0, max)
    LongStream s3 = rng.longs(min, max); // [min, max)
    DoubleStream s1 = rng.doubles();         // [0, 1)
    DoubleStream s2 = rng.doubles(max);      // [0, max)
    DoubleStream s3 = rng.doubles(min, max); // [min, max)

    Streams can be limited by a stream size argument.

    // Roll a die 1000 times
    int[] rolls = rng.ints(1000, 1, 7).toArray();

    It should be noted that streams returned by the interface default implementation perform repeat calls to the relevant next generation method and may have a performance overhead. Efficient streams can be created using an instance of a sampler which can precompute coefficients on construction (see the sampling module).

  • The UniformRandomProvider interface provides default implementations for all generation methods except nextLong. Implementation of a new generator must only provide a 64-bit source of randomness.
    UniformRandomProvider rng = new SecureRandom()::nextLong;

    Abstract classes for a 32-bit or 64-bit source of randomness, with additional functionality not present in the interface, are provided in the core module.

  • In order to generate reproducible sequences, generators must be instantiated with a user-defined seed.
    UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create(5776);

    If no seed is passed, a random seed is generated implicitly.

    Convenience methods are provided for explicitly generating random seeds of the various types.

    int seed = RandomSource.createInt();
    long seed = RandomSource.createLong();
    int[] seed = RandomSource.createIntArray(128); // Length of returned array is 128.
    long[] seed = RandomSource.createLongArray(128); // Length of returned array is 128.
  • Any of the following types can be passed to the create method as the "seed" argument:
    • int or Integer
    • long or Long
    • int[]
    • long[]
    • byte[]
    UniformRandomProvider rng = RandomSource.ISAAC.create(5776);
    UniformRandomProvider rng = RandomSource.ISAAC.create(new int[] { 6, 7, 7, 5, 6, 1, 0, 2 });
    UniformRandomProvider rng = RandomSource.ISAAC.create(new long[] { 0x638a3fd83bc0e851L, 0x9730fd12c75ae247L });

    Note however that, upon initialization, the underlying generation algorithm

    • may not use all the information contents of the seed,
    • may use a procedure (using the given seed as input) for further filling its internal state (in order to avoid a too uniform initial state).

    In both cases, the behavior is not standard but should not change between releases of the library (bugs notwithstanding).

    Each RNG implementation has a single "native" seed; when the seed argument passed to the create method is not of the native type, it is automatically converted. The conversion preserves the information contents but is otherwise not specified (i.e. different releases of the library may use different conversion procedures).

    Hence, if reproducibility of the generated sequences across successive releases of the library is necessary, users should ensure that they use native seeds.

    long seed = 9246234616L;
    if (!RandomSource.TWO_CMRES.isNativeSeed(seed)) {
        throw new IllegalArgumentException("Seed is not native");
    }

    For each available implementation, the native seed type is specified in the Javadoc.

  • Whenever a random source implementation is parameterized, the custom arguments are passed after the seed.
    int seed = 96912062;
    int first = 7; // Subcycle identifier.
    int second = 4; // Subcycle identifier.
    UniformRandomProvider rng = RandomSource.TWO_CMRES_SELECT.create(seed, first, second);

    In the above example, valid "subcycle identifiers" are in the interval [0, 13].

  • The current state of a generator can be saved and restored later on.
    import org.apache.commons.rng.RestorableUniformRandomProvider;
    import org.apache.commons.rng.RandomProviderState;
    
    RestorableUniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
    RandomProviderState state = rng.saveState();
    double x = rng.nextDouble();
    rng.restoreState(state);
    double y = rng.nextDouble(); // x == y.
  • The UniformRandomProvider objects returned from the create methods do not implement the java.io.Serializable interface.

    However, users can easily set up a custom serialization scheme if the random source is known at both ends of the communication channel. This would be useful namely to save the state to persistent storage, and restore it such that the sequence will continue from where it left off.

    import org.apache.commons.rng.RestorableUniformRandomProvider;
    import org.apache.commons.rng.simple.RandomSource;
    import org.apache.commons.rng.core.RandomProviderDefaultState;
    
    RandomSource source = RandomSource.KISS; // Known source identifier.
    
    RestorableUniformRandomProvider rngOrig = source.create(); // Original RNG instance.
    
    // Save and serialize state.
    RandomProviderState stateOrig = rngOrig.saveState(rngOrig);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(((RandomProviderDefaultState) stateOrig).getState());
    
    // Deserialize state.
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    RandomProviderState stateNew = new RandomProviderDefaultState((byte[]) ois.readObject());
    
    RestorableUniformRandomProvider rngNew = source.create(); // New RNG instance from the same "source".
    
    // Restore original state on the new instance.
    rngNew.restoreState(stateNew);
  • The JumpableUniformRandomProvider interface allows creation of a copy of the generator and advances the state of the current generator a large number of steps in a single jump. This can be used to create a set of generators that will not overlap in their output sequence for the length of the jump for use in parallel computations.
    import org.apache.commons.rng.UniformRandomProvider;
    import org.apache.commons.rng.JumpableUniformRandomProvider;
    import org.apache.commons.rng.simple.RandomSource;
    import java.util.concurrent.ForkJoinPool;
    
    RandomSource source = RandomSource.XO_RO_SHI_RO_128_SS; // Known to be jumpable.
    
    JumpableUniformRandomProvider jumpable = (JumpableUniformRandomProvider) source.create();
    
    // For use in parallel
    int streamSize = 10;
    jumpable.jumps(streamSize).forEach(rng -> {
        ForkJoinPool.commonPool().execute(() -> {
            // Task using the rng
        });
    });

    Note that here the stream of RNGs is sequential; each RNG is used within a potentially long-running task that can run concurrently with other tasks using an executor service.

  • The ArbitrarilyJumpableUniformRandomProvider interface allows creation of a copy of the generator and advances the state of the current generator an arbitrary number of steps in a single jump. Jump distances are supported using a double or using a power-of-2. Streams of jumpable generators can be created using a double distance. Since each copy generator is also an ArbitrarilyJumpableUniformRandomProvider with care it is possible to further distribute generators within the original jump distance and use the entire state cycle in different ways.
    import org.apache.commons.rng.UniformRandomProvider;
    import org.apache.commons.rng.ArbitrarilyJumpableUniformRandomProvider;
    import org.apache.commons.rng.simple.RandomSource;
    
    RandomSource source = RandomSource.PHILOX_4X64; // Known to be arbitrarily jumpable.
    
    ArbitrarilyJumpableUniformRandomProvider jumpable = (ArbitrarilyJumpableUniformRandomProvider) source.create();
    
    double distance = 42;
    for (int i = 0; i < 5; i++) {
        // Copy the state and then jump ahead
        UniformRandomProvider copy = jumpable.jump(distance);
    
        // Catch up the jump using the native 64-bit output
        for (int j = 0; j < distance; j++) {
            copy.nextLong();
        }
    
        // The copy matches the jumped generator
        assert copy.nextLong() == jumpable.nextLong();
    }
    
    int logDistance = 123;
    ArbitrarilyJumpableUniformRandomProvider copy = jumpable.jumpPowerOfTwo(logDistance);
    
    // Catch up the jump using: 4 * 2^119 + 2^121 + 2^122
    copy.jumpPowerOfTwo(logDistance - 4);
    copy.jumpPowerOfTwo(logDistance - 4);
    copy.jumpPowerOfTwo(logDistance - 2);
    copy.jumpPowerOfTwo(logDistance - 4);
    copy.jumpPowerOfTwo(logDistance - 1);
    copy.jumpPowerOfTwo(logDistance - 4);
    
    // The copy matches the jumped generator
    assert copy.nextLong() == jumpable.nextLong();

    In the above examples, the source is known to implement the appropriate jumpable interface. Not all generators support this functionality. You can determine if a RandomSource is jumpable without creating one using the instance methods isJumpable(), isLongJumpable() and isArbitrarilyJumpable.

    import org.apache.commons.rng.simple.RandomSource;
    
    public void initialise(RandomSource source) {
        if (!source.isJumpable()) {
            throw new IllegalArgumentException("Require a jumpable random source");
        }
        // ...
    }

    Jumping can be used to create a series of non-overlapping generators for use in multithreaded applications. Note that there is not a one-to-one relationship between the number of output random values from a provider and the number of steps from the underlying state cycle. This is due to:

    • Possible use of rejection algorithms to output a random value using multiple values from the state cycle.
    • The number of bits required to generate a random value differing from the number of bits generated by the underlying source of randomness. For example generation of a 64-bit long value using a 32-bit source of randomness.

    Users are advised to use jumping generators with care to avoid overlapping output of multiple generators in parallel computations. A cautious approach is to use a jump distance far larger than the expected output length used by each generator.

  • The SplittableUniformRandomProvider interface allows splitting a generator into two objects (the original and a new instance) each of which implements the same interface (and can be recursively split indefinitely). This can be used for parallel computations where the number of forks is unknown. These generators provide support for parallel streams. It should be noted that in general creation of a new generator instance may result in correlation of the output sequence with an existing generator. The generators that support this interface have algorithms designed to minimise correlation between instances. In particular the stream of generators provided by recursive splitting of a parallel stream are robust to collision of their sequence output.
    import org.apache.commons.rng.UniformRandomProvider;
    import org.apache.commons.rng.SplittableUniformRandomProvider;
    import org.apache.commons.rng.simple.RandomSource;
    
    RandomSource source = RandomSource.L64_X128_MIX; // Known to be splittable.
    
    SplittableUniformRandomProvider splittable = (SplittableUniformRandomProvider) source.create();
    
    // For use in parallel
    int streamSize = 10;
    jumpable.splits(streamSize).parallel().forEach(rng -> {
        // Task using the rng
    });

    Note that here the stream of RNGs is parallel; each RNG is used within a potentially long-running task that can run concurrently with other tasks if the enclosing stream parallel support utilises multiple threads.

    In the above example, the source is known to implement the SplittableUniformRandomProvider interface. Not all generators support this functionality. You can determine if a RandomSource is splittable without creating one using the instance method isSplittable().

    import org.apache.commons.rng.simple.RandomSource;
    
    public void initialise(RandomSource source) {
        if (!source.isSplittable()) {
            throw new IllegalArgumentException("Require a splittable random source");
        }
        // ...
    }
  • Generation of random deviates for various distributions.
    import org.apache.commons.rng.sampling.distribution.ContinuousSampler;
    import org.apache.commons.rng.sampling.distribution.GaussianSampler;
    import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
    
    ContinuousSampler sampler = GaussianSampler.of(ZigguratSampler.NormalizedGaussian.of(RandomSource.ISAAC.create()),
                                                   45.6, 2.3);
    double random = sampler.sample();
    import org.apache.commons.rng.sampling.distribution.DiscreteSampler;
    import org.apache.commons.rng.sampling.distribution.RejectionInversionZipfSampler;
    
    DiscreteSampler sampler = RejectionInversionZipfSampler.of(RandomSource.ISAAC.create(),
                                                               5, 1.2);
    int random = sampler.sample();
  • Sampler interfaces are provided for generation of the primitive types int, long, and double and objects of type T. The samples method creates a stream of sample values using the Java 8 streaming API:
    import org.apache.commons.rng.sampling.distribution.PoissonSampler;
    import org.apache.commons.rng.simple.RandomSource;
    
    double mean = 15.5;
    int streamSize = 100;
    int[] counts = PoissonSampler.of(RandomSource.L64_X128_MIX.create(), mean)
                                 .samples(streamSize)
                                 .toArray();
    import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
    import org.apache.commons.rng.simple.RandomSource;
    
    // Lower-truncated Normal distribution samples
    double low = -1.23;
    double[] samples = ZigguratSampler.NormalizedGaussian.of(RandomSource.L64_X128_MIX.create())
                                                         .samples()
                                                         .filter(x -> x > low)
                                                         .limit(100)
                                                         .toArray();
  • The SharedStateSampler interface allows creation of a copy of the sampler using a new generator. The samplers share only their immutable state and can be used in parallel computations.
    import org.apache.commons.rng.UniformRandomProvider;
    import org.apache.commons.rng.sampling.distribution.MarsagliaTsangWangDiscreteSampler;
    import org.apache.commons.rng.sampling.distribution.SharedStateDiscreteSampler;
    import org.apache.commons.rng.simple.RandomSource;
    
    RandomSource source = RandomSource.XO_RO_SHI_RO_128_PP;
    
    double[] probabilities = {0.1, 0.2, 0.3, 0.4};
    SharedStateDiscreteSampler sampler1 = MarsagliaTsangWangDiscreteSampler.Enumerated.of(source.create(),
                                                                                          probabilities);
    
    // For use in parallel
    SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(source.create());

    All samplers support the SharedStateSampler interface.

  • Permutation, Combination, sampling from a Collection and shuffling utilities.
    import org.apache.commons.rng.sampling.PermutationSampler;
    import org.apache.commons.rng.sampling.CombinationSampler;
    
    // 3 elements from the (0, 1, 2, 3, 4, 5) tuplet.
    int n = 6;
    int k = 3;
    
    // If the order of the elements matters.
    PermutationSampler permutationSampler = new PermutationSampler(RandomSource.KISS.create(),
                                                                   n, k);
    // n! / (n - k)! = 120 permutations.
    int[] permutation = permutationSampler.sample();
    
    // If the order of the elements does not matter.
    CombinationSampler combinationSampler = new CombinationSampler(RandomSource.KISS.create(),
                                                                   n, k);
    // n! / (k! (n - k)!) = 20 combinations.
    int[] combination = combinationSampler.sample();
    import java.util.HashSet;
    import org.apache.commons.rng.sampling.CollectionSampler;
    
    HashSet<String> elements = new HashSet<>();
    elements.add("Apache");
    elements.add("Commons");
    elements.add("RNG");
    
    CollectionSampler<String> sampler = new CollectionSampler<>(RandomSource.MWC_256.create(),
                                                                elements);
    String word = sampler.sample();
    import java.util.Arrays;
    import java.util.List;
    import org.apache.commons.rng.UniformRandomProvider;
    import org.apache.commons.rng.sampling.ListSampler;
    
    List<String> list = Arrays.asList("Apache", "Commons", "RNG");
    
    UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
    
    // Get 2 random items
    int k = 2;
    List<String> sample = ListSampler.sample(rng, list, k);
    
    // Shuffle the list
    ListSampler.shuffle(rng, list)
  • Sampling from geometric shapes: Box, Ball, Line, Triangle, and Tetrahedron.
    import org.apache.commons.rng.sampling.shape.BoxSampler;
    
    double[] lower = {1, 2, 3};
    double[] upper = {15, 16, 17};
    BoxSampler sampler = BoxSampler.of(RandomSource.KISS.create(),
                                       lower, upper);
    double[] coordinate = sampler.sample();
    double[][] coordinates = sampler.samples(100).toArray(double[][]::new);
  • The CompositeSamplers utility class can create a composite sampler that is a weighted combination of samplers that return the same type.

    The following example will create a sampler to uniformly sample the border of a triangle using the line segment lengths as weights:

    import org.apache.commons.rng.sampling.shape.LineSampler;
    
    UniformRandomProvider rng = RandomSource.JSF_64.create();
    
    // Triangle vertices
    double[] a = {1.23, 4.56};
    double[] b = {6.78, 9.01};
    double[] c = {3.45, 2.34};
    // Line lengths
    double ab = Math.hypot(a[0] - b[0], a[1] - b[1]);
    double bc = Math.hypot(b[0] - c[0], b[1] - c[1]);
    double ca = Math.hypot(c[0] - a[0], c[1] - a[1]);
    
    ObjectSampler<double[]> sampler =
        CompositeSamplers.<double[]>newObjectSamplerBuilder()
            .add(LineSampler.of(rng, a, b), ab)
            .add(LineSampler.of(rng, b, c), bc)
            .add(LineSampler.of(rng, c, a), ca)
            .build(rng);
    
    double[] coordinate = sampler.sample();

3. Library layout

The API for client code consists of classes and interfaces defined in package org.apache.commons.rng.

  • Interface UniformRandomProvider provides access to a sequence of random values uniformly distributed within some range.
  • Interfaces RestorableUniformRandomProvider and RandomProviderState provide the "save/restore" API.
  • Interfaces JumpableUniformRandomProvider, LongJumpableUniformRandomProvider and ArbitrarilyJumpableUniformRandomProvider provide the "copy and jump" API for parallel computations. These are suitable for tasks where the number of instances to use in parallel is known.
  • Interface SplittableUniformRandomProvider provides the "split" API for parallel computations. This is suitable for tasks where the number of instances to use in parallel is unknown, for example execution of tasks within a stream using the JDK parallelism support.

The API for instantiating generators is defined in package org.apache.commons.rng.simple.

  • Enum RandomSource determines which algorithm to use for generating the sequence of random values.

The org.apache.commons.rng.simple.internal package contains classes for supporting initialization (a.k.a. "seeding") of the generators. They must not be used directly in applications, as all the necessary utilities are accessible through methods defined in RandomSource.

  • ProviderBuilder: contains methods for instantiating the concrete RNG implementations based on the source identifier; it also takes care of calling the appropriate classes for seed type conversion.
  • SeedFactory: contains factory methods for generating random seeds.
  • SeedConverter: interface for classes that transform between supported seed types.
  • Various classes that implement SeedConverter in order to transform from caller's seed to "native" seed.

The org.apache.commons.rng.core package contains the implementation of the algorithms for the generation of pseudo-random sequences. Applications should not directly import or use classes defined in this package: all generators can be instantiated through the RandomSource factory.

  • Class RandomProviderDefaultState implements the RandomProviderState interface to enable "save/restore" for all RestorableUniformRandomProvider instances created through the RandomSource factory methods.
  • BaseProvider: base class for all concrete RNG implementations; it contains higher-level algorithms nextInt(int n) and nextLong(long n) common to all implementations.
  • org.apache.commons.rng.core.util
    • NumberFactory: contains utilities for interpreting and combining the output (int or long) of the underlying source of randomness into the requested output, i.e. one of the Java primitive types supported by UniformRandomProvider.
    • RandomStreams: contains utilities for generating a stream of objects created using a random seed and source of randomness.
  • org.apache.commons.rng.core.source32
    • RandomIntSource: describes an algorithm that generates randomness in 32-bits chunks (a.k.a Java int).
    • IntProvider: base class for concrete classes that implement RandomIntSource.
    • Concrete RNG algorithms that are subclasses of IntProvider.
  • org.apache.commons.rng.core.source64
    • RandomLongSource: describes an algorithm that generates randomness in 64-bits chunks (a.k.a Java long).
    • LongProvider: base class for concrete classes that implement RandomLongSource.
    • Concrete RNG algorithms that are subclasses of LongProvider.

4. Performance

This section reports performance benchmarks of the RNG implementations. All runs were performed on a platform with the following characteristics:

  • CPU: Apple M2 Max
  • Java version: 21
  • JVM: OpenJDK 64-Bit Server VM Homebrew (build 21.0.9, mixed mode, sharing)

Performance was measured using the Java Micro-benchmark Harness (JMH) and the code is provided in the Examples.

Timings are representative of performance; the relative ranking of results may change depending on the JVM, operating system and hardware.

In these tables:

  • The first column is the RNG identifier (see RandomSource)
  • Lower is better.

4.1 Generating primitive values

The following table indicates the performance for generating:

  • a sequence of true/false values (a.k.a. Java type boolean)
  • a sequence of 64-bit floating point numbers (a.k.a. Java type double)
  • a sequence of 64-bit integers (a.k.a. Java type long)
  • a sequence of 32-bit floating point numbers (a.k.a. Java type float)
  • a sequence of 32-bit integers (a.k.a. Java type int)

Scores are normalized to the score of RandomSource.JDK.

Note that the core implementations use all the bits from the random source. For example a native generator of 32-bit int values requires 1 generation call per 32 boolean values; a native generator of 64-bit long values requires 1 generation call per 2 int values. This implementation is fast for all generators but requires a high quality random source. See the Quality section.

RNG identifier boolean double long float int
JDK 1.00000 1.00000 1.00000 1.00000 1.00000
WELL_512_A 0.98406 0.39947 0.41063 0.49594 0.47166
WELL_1024_A 1.00844 0.37799 0.37230 0.40633 0.38654
WELL_19937_A 0.99749 0.43187 0.48963 0.48491 0.46106
WELL_19937_C 1.03270 0.49032 0.48756 0.49783 0.47090
WELL_44497_A 1.02807 0.46649 0.44125 0.48693 0.46331
WELL_44497_B 1.05406 0.50246 0.48431 0.54510 0.48850
MT 0.93440 0.24441 0.24019 0.26725 0.27400
ISAAC 0.96870 0.42654 0.43685 0.54373 0.51136
SPLIT_MIX_64 1.09912 0.09394 0.09414 0.31157 0.29574
XOR_SHIFT_1024_S 1.19090 0.15728 0.16436 0.15319 0.13644
TWO_CMRES 1.14304 0.15882 0.16118 0.15503 0.14543
MT_64 1.12332 0.12830 0.13547 0.13100 0.11757
MWC_256 0.92903 0.32660 0.35377 0.46743 0.44503
KISS 0.89721 0.27929 0.29906 0.38787 0.39688
XOR_SHIFT_1024_S_PHI 1.21406 0.15735 0.16299 0.14430 0.13764
XO_RO_SHI_RO_64_S 0.86964 0.28881 0.27716 0.35317 0.33174
XO_RO_SHI_RO_64_SS 0.87965 0.30068 0.35496 0.37802 0.44112
XO_SHI_RO_128_PLUS 0.87328 0.22295 0.18769 0.35164 0.34567
XO_SHI_RO_128_SS 0.87516 0.25105 0.23422 0.32450 0.27225
XO_RO_SHI_RO_128_PLUS 1.12940 0.15030 0.15852 0.14714 0.13403
XO_RO_SHI_RO_128_SS 1.10529 0.17659 0.22253 0.15107 0.15285
XO_SHI_RO_256_PLUS 1.09700 0.13169 0.17398 0.17743 0.16242
XO_SHI_RO_256_SS 1.10314 0.14432 0.13481 0.18434 0.17936
XO_SHI_RO_512_PLUS 1.12281 0.13157 0.18988 0.14470 0.16157
XO_SHI_RO_512_SS 1.13177 0.13415 0.21849 0.17113 0.16460
PCG_XSH_RR_32 0.87186 0.18591 0.18147 0.26266 0.23753
PCG_XSH_RS_32 0.86321 0.18775 0.18550 0.27155 0.24805
PCG_RXS_M_XS_64 1.10923 0.12366 0.11974 0.24369 0.23146
PCG_MCG_XSH_RR_32 0.90426 0.13419 0.12457 0.24044 0.20793
PCG_MCG_XSH_RS_32 0.86853 0.12945 0.12226 0.23505 0.21101
MSWS 0.97807 0.22247 0.20847 0.32584 0.31681
SFC_32 0.86725 0.22908 0.24208 0.27041 0.35094
SFC_64 1.10769 0.16930 0.17019 0.15732 0.15014
JSF_32 0.93478 0.18231 0.18021 0.35872 0.28842
JSF_64 1.12192 0.17763 0.16808 0.18130 0.16090
XO_SHI_RO_128_PP 0.90638 0.23333 0.19644 0.30718 0.34596
XO_RO_SHI_RO_128_PP 1.08265 0.16797 0.15479 0.14812 0.21495
XO_SHI_RO_256_PP 1.09547 0.13462 0.18466 0.13471 0.11804
XO_SHI_RO_512_PP 1.11719 0.13308 0.13519 0.18637 0.12390
XO_RO_SHI_RO_1024_PP 1.11454 0.11654 0.11859 0.12228 0.10176
XO_RO_SHI_RO_1024_S 1.11434 0.11567 0.11981 0.11188 0.09909
XO_RO_SHI_RO_1024_SS 1.83765 0.12472 0.12456 0.12780 0.11088
PCG_XSH_RR_32_OS 0.87458 0.18953 0.18210 0.26172 0.23733
PCG_XSH_RS_32_OS 0.87389 0.18785 0.18671 0.26381 0.24775
PCG_RXS_M_XS_64_OS 1.09256 0.12434 0.11960 0.24351 0.22953
L64_X128_SS 1.19064 0.16720 0.16213 0.21711 0.21302
L64_X128_MIX 1.11597 0.18518 0.18237 0.22892 0.22044
L64_X256_MIX 1.13742 0.15913 0.19086 0.18938 0.18010
L64_X1024_MIX 1.13353 0.12702 0.12948 0.14959 0.12512
L128_X128_MIX 1.14595 0.20402 0.21031 0.21287 0.19027
L128_X256_MIX 1.12560 0.42397 0.20869 0.41922 0.19029
L128_X1024_MIX 1.20275 0.21251 0.21320 0.21454 0.40626
L32_X64_MIX 0.91357 0.31074 0.35720 0.39415 0.36384
PHILOX_4X32 1.08214 0.57003 0.40638 0.39130 0.33465
PHILOX_4X64 1.45384 0.14840 0.13167 0.17748 0.15659

Notes:

The RandomSource.JDK generator uses thread-safe (synchronized) int generation which has a performance overhead (see the int generation results). Note that the output will be low quality and this generator should not be used. See the Quality section for details. Multi-threaded applications should use a generator for each thread.

The speed of boolean generation is related to the base implementation that caches the 32-bit or 64-bit output from the generator. In these results the 32-bit generators have the better performance. These timings are relative and all implements are very fast. A RNG to compute boolean samples should be chosen based on the quality of the output.

The RandomSource.PHILOX_4X64 generator uses multiply high methods from java.lang.Math if available. The multiplyHigh (JDK 9+) and unsignedMultiplyHigh (JDK 18+) significantly increase performance if the 128-bit product of two 64-bit factors is supported by hardware instructions. These results are on a platform with supported hardware.

4.2 Generating Gaussian samples

The following table compares the BoxMullerNormalizedGaussianSampler, MarsagliaNormalizedGaussianSampler, ZigguratNormalizedGaussianSampler, and ZigguratSampler.NormalizedGaussian.

Each score is normalized to the score of nextGaussian() method of java.util.Random which internally uses the Box-Muller algorithm in a synchronized method.

RNG identifier BoxMullerNormalizedGaussianSampler MarsagliaNormalizedGaussianSampler ZigguratNormalizedGaussianSampler ZigguratSampler.NormalizedGaussian
JDK 0.68777 0.81843 0.63294 0.64404
WELL_512_A 0.75101 0.76327 0.33895 0.31547
WELL_1024_A 0.73373 0.77183 0.31694 0.30482
WELL_19937_A 0.98253 1.09219 0.39356 0.37716
WELL_19937_C 1.01706 1.33765 0.42391 0.41528
WELL_44497_A 0.94706 1.10975 0.40873 0.38855
WELL_44497_B 1.07158 1.45448 0.43492 0.43214
MT 0.73850 0.71401 0.22460 0.22366
ISAAC 0.78218 0.76143 0.34581 0.33332
SPLIT_MIX_64 0.67904 0.40813 0.10945 0.09004
XOR_SHIFT_1024_S 0.60941 0.46003 0.14489 0.13733
TWO_CMRES 0.59634 0.43879 0.14193 0.13045
MT_64 0.64925 0.52747 0.14545 0.12482
MWC_256 0.59802 0.51695 0.18319 0.17699
KISS 0.70982 0.54934 0.25360 0.24010
XOR_SHIFT_1024_S_PHI 0.62823 0.47054 0.14687 0.14116
XO_RO_SHI_RO_64_S 0.64133 0.49153 0.24208 0.27114
XO_RO_SHI_RO_64_SS 0.68614 0.50950 0.24196 0.27433
XO_SHI_RO_128_PLUS 0.59398 0.40979 0.17945 0.15669
XO_SHI_RO_128_SS 0.64363 0.44195 0.19775 0.17960
XO_RO_SHI_RO_128_PLUS 0.57394 0.36482 0.14416 0.17495
XO_RO_SHI_RO_128_SS 0.58773 0.38655 0.15829 0.17833
XO_SHI_RO_256_PLUS 0.57167 0.36364 0.15275 0.11214
XO_SHI_RO_256_SS 0.58883 0.38003 0.13301 0.11511
XO_SHI_RO_512_PLUS 0.57162 0.37981 0.12786 0.15007
XO_SHI_RO_512_SS 0.60068 0.38822 0.16683 0.11567
PCG_XSH_RR_32 0.63515 0.43201 0.16654 0.16535
PCG_XSH_RS_32 0.62909 0.41471 0.16475 0.15260
PCG_RXS_M_XS_64 0.62697 0.39972 0.12651 0.11403
PCG_MCG_XSH_RR_32 0.63055 0.42255 0.13217 0.12350
PCG_MCG_XSH_RS_32 0.61018 0.39879 0.12573 0.11380
MSWS 0.63346 0.43842 0.19578 0.18195
SFC_32 0.60542 0.42090 0.22415 0.21120
SFC_64 0.55247 0.37325 0.15304 0.10980
JSF_32 0.58862 0.41063 0.21022 0.17664
JSF_64 0.55953 0.37250 0.15263 0.11131
XO_SHI_RO_128_PP 0.62308 0.43179 0.18214 0.16867
XO_RO_SHI_RO_128_PP 0.58869 0.37695 0.14941 0.18867
XO_SHI_RO_256_PP 0.62218 0.40224 0.17566 0.13831
XO_SHI_RO_512_PP 0.63007 0.38958 0.13534 0.16165
XO_RO_SHI_RO_1024_PP 0.59214 0.43627 0.12489 0.11273
XO_RO_SHI_RO_1024_S 0.55932 0.41749 0.12111 0.10776
XO_RO_SHI_RO_1024_SS 0.58143 0.43408 0.13265 0.11737
PCG_XSH_RR_32_OS 0.63783 0.43019 0.16740 0.16241
PCG_XSH_RS_32_OS 0.61018 0.40376 0.16254 0.15108
PCG_RXS_M_XS_64_OS 0.62263 0.39527 0.12241 0.10696
L64_X128_SS 0.61355 0.39603 0.19675 0.18417
L64_X128_MIX 0.71570 0.43616 0.20192 0.19443
L64_X256_MIX 0.67443 0.43273 0.14705 0.17261
L64_X1024_MIX 0.62038 0.49208 0.14579 0.12868
L128_X128_MIX 0.75821 0.57463 0.20535 0.18531
L128_X256_MIX 0.76666 0.56904 0.21025 0.18677
L128_X1024_MIX 0.65788 0.58239 0.22308 0.19761
L32_X64_MIX 0.74795 0.55989 0.25632 0.27548
PHILOX_4X32 1.08207 1.07704 0.38838 0.37289
PHILOX_4X64 0.91379 0.91941 0.26007 0.24542

Notes:

The reference java.util.Random nextGaussian() method uses synchronized method calls per sample. The RandomSource.JDK RNG will use synchronized method calls when generating numbers for the BoxMullerNormalizedGaussianSampler but the calls to obtain the samples are not synchronized, hence the observed difference. All the other RNGs are not synchronized.

The RandomSource.PHILOX_4X64 generator uses multiply high methods from java.lang.Math if available. The multiplyHigh (JDK 9+) and unsignedMultiplyHigh (JDK 18+) significantly increase performance if the 128-bit product of two 64-bit factors is supported by hardware instructions. These results are on a platform with supported hardware.

5. Quality

This section reports results of performing "stress tests" that aim at detecting failures of an implementation to produce sequences of numbers that follow a uniform distribution.

Three different test suites were used:

Note that the Dieharder and TestU01 test suites accept 32-bit integer values. Any generator of 64-bit long values has the upper and lower 32-bits passed to the test suite. PractRand supports 64-bit generators.

The first column is the RNG identifier (see RandomSource). The remaining columns contain the results of separate runs of the test suite using different random seeds. Click on one of the entries of the comma-separated list in order to see the text report of the corresponding run.

The Dieharder and TestU01 test suites contain many tests each requiring an approximately fixed size of random output; in the case of multiple tests different output is used for each test. Dieharder was run using the full set of tests. TestU01 was run using BigCrush. The number in the table indicates the number of failed tests, i.e. tests reported as below the accepted threshold for considering the sequence as uniformly random; hence lower is better. Note: For Dieharder the flawed "Diehard Sums Test" is ignored from the failure counts.

PractRand tests a length of the RNG output with all the selected tests; this is repeated with doubling lengths until a failure is detected or the maximum size is reached. PractRand was run using the core tests and smart folding. This is the default mode and comprises tests with little overlap in their characteristics and additional targeting testing of the lower bits of the output sequence. The limit for these results was 4 terabytes (4 TiB). A number in the table indicates the size in bytes of output where a failure occurred expressed as an exponent of 2; hence higher is better. A dash (-) indicates no failure and is best.

Spurious failures are a failure in a single run of the test suite. These are to be expected as the tests use probability thresholds to determine if the output is non-random. Systematic failures where the RNG fails the same test in every run indicate a problem with the RNG output. The count of systematic failures for Dieharder and TestU01 are shown in parentheses. The maximum output at which a failure always occurs for PractRand is shown in parentheses.

Any RNG with no systematic failures is highlighted in bold. Note that some RNGs fail PractRand on tests which target the lower bits. These are not suitable as all purpose generators but have utility in floating-point number generation where the lower bits are not used.

RNG identifier Dieharder TestU01 (BigCrush) PractRand
JDK 4, 4, 4, 4, 4 (4) 50, 51, 52, 49, 51 (48) 20, 20, 20 (1 MiB)
WELL_512_A 0, 0, 0, 0, 0 6, 7, 8, 6, 6 (6) 24, 24, 24 (16 MiB)
WELL_1024_A 0, 0, 0, 0, 0 5, 4, 5, 5, 4 (4) 27, 27, 27 (128 MiB)
WELL_19937_A 0, 1, 0, 0, 0 2, 2, 3, 3, 3 (2) 39, 39, 39 (512 GiB)
WELL_19937_C 0, 0, 0, 0, 0 4, 2, 2, 2, 2 (2) 39, 39, 39 (512 GiB)
WELL_44497_A 0, 0, 0, 0, 0 3, 2, 2, 2, 3 (2) 42, 42, 42 (4 TiB)
WELL_44497_B 0, 0, 0, 0, 0 2, 2, 2, 2, 2 (2) 42, 42, 42 (4 TiB)
MT 0, 0, 0, 0, 0 2, 3, 2, 2, 3 (2) 38, 38, 38 (256 GiB)
ISAAC 0, 0, 0, 0, 0 1, 1, 0, 0, 1 -, -, -
SPLIT_MIX_64 0, 0, 0, 0, 0 0, 0, 0, 1, 0 -, -, -
XOR_SHIFT_1024_S 0, 0, 0, 0, 0 1, 0, 0, 0, 2 31, 31, 31 (2 GiB)
TWO_CMRES 2, 2, 2, 2, 2 (2) 0, 1, 0, 0, 0 32, 32, 32 (4 GiB)
MT_64 0, 0, 0, 0, 0 2, 2, 2, 2, 2 (2) 39, 39, 39 (512 GiB)
MWC_256 0, 0, 0, 0, 0 0, 1, 1, 1, 0 -, -, -
KISS 0, 0, 0, 0, 0 1, 1, 0, 0, 0 -, -, -
XOR_SHIFT_1024_S_PHI 0, 0, 0, 0, 0 0, 2, 0, 0, 1 33, 33, 33 (8 GiB)
XO_RO_SHI_RO_64_S 0, 0, 0, 0, 0 1, 2, 3, 1, 1 (1) 21, 21, 21 (2 MiB)
XO_RO_SHI_RO_64_SS 0, 0, 0, 0, 0 0, 1, 0, 0, 0 -, -, -
XO_SHI_RO_128_PLUS 0, 0, 0, 0, 0 0, 0, 1, 0, 0 24, 24, 24 (16 MiB)
XO_SHI_RO_128_SS 0, 0, 0, 0, 0 1, 0, 1, 0, 0 -, -, -
XO_RO_SHI_RO_128_PLUS 0, 0, 0, 0, 0 1, 0, 0, 0, 0 25, 25, 25 (32 MiB)
XO_RO_SHI_RO_128_SS 0, 0, 0, 0, 0 1, 1, 1, 0, 0 -, -, -
XO_SHI_RO_256_PLUS 0, 0, 0, 0, 0 1, 0, 0, 0, 0 27, 27, 27 (128 MiB)
XO_SHI_RO_256_SS 0, 0, 0, 0, 0 0, 0, 0, 0, 1 -, -, -
XO_SHI_RO_512_PLUS 0, 0, 0, 0, 0 0, 2, 0, 0, 0 30, 30, 30 (1 GiB)
XO_SHI_RO_512_SS 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
PCG_XSH_RR_32 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
PCG_XSH_RS_32 0, 0, 0, 0, 0 0, 1, 2, 1, 0 41, -, -
PCG_RXS_M_XS_64 0, 0, 0, 0, 0 0, 1, 0, 0, 0 -, -, -
PCG_MCG_XSH_RR_32 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
PCG_MCG_XSH_RS_32 0, 0, 0, 0, 0 2, 1, 0, 1, 0 40, 41, 41 (2 TiB)
MSWS 0, 0, 0, 0, 0 0, 0, 0, 1, 2 -, -, -
SFC_32 0, 0, 0, 0, 0 0, 0, 0, 1, 1 -, -, -
SFC_64 0, 0, 0, 0, 0 0, 0, 0, 1, 2 -, -, -
JSF_32 0, 0, 0, 0, 0 0, 0, 0, 1, 2 -, -, -
JSF_64 0, 0, 0, 0, 0 0, 0, 2, 0, 0 -, -, -
XO_SHI_RO_128_PP 0, 0, 0, 0, 0 0, 0, 0, 1, 1 -, -, -
XO_RO_SHI_RO_128_PP 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
XO_SHI_RO_256_PP 0, 0, 0, 0, 0 0, 1, 1, 0, 1 -, -, -
XO_SHI_RO_512_PP 0, 0, 0, 0, 0 0, 0, 1, 0, 0 -, -, -
XO_RO_SHI_RO_1024_PP 0, 0, 0, 0, 0 0, 0, 3, 0, 1 -, -, -
XO_RO_SHI_RO_1024_S 0, 0, 0, 1, 0 0, 1, 0, 0, 0 33, 33, 33 (8 GiB)
XO_RO_SHI_RO_1024_SS 0, 0, 0, 0, 0 0, 1, 0, 0, 0 -, -, -
PCG_XSH_RR_32_OS 0, 0, 0, 0, 0 0, 0, 1, 0, 0 -, -, -
PCG_XSH_RS_32_OS 0, 0, 0, 0, 0 0, 0, 1, 0, 0 -, -, -
PCG_RXS_M_XS_64_OS 0, 0, 0, 0, 0 0, 0, 0, 1, 2 -, -, -
L64_X128_SS 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
L64_X128_MIX 0, 0, 0, 0, 0 0, 0, 1, 1, 0 -, -, -
L64_X256_MIX 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
L64_X1024_MIX 0, 0, 0, 0, 0 0, 1, 1, 1, 0 -, -, -
L128_X128_MIX 0, 0, 0, 0, 0 1, 0, 0, 0, 0 -, -, -
L128_X256_MIX 1, 0, 0, 0, 0 1, 0, 0, 0, 0 -, -, -
L128_X1024_MIX 0, 0, 0, 0, 0 0, 0, 0, 1, 0 -, -, -
L32_X64_MIX 0, 0, 0, 0, 0 0, 0, 0, 0, 0 -, -, -
PHILOX_4X32 0, 0, 0, 0, 0 0, 1, 0, 1, 0 -, -, -
PHILOX_4X64 0, 0, 0, 0, 0 0, 0, 0, 2, 0 -, -, -

6. Examples

The source distribution for Apache Commons RNG contains example applications to demonstrate functionality of the library. These are contained in the following modules:

Example Module Description
Stress Application for calling external tools that perform stringent uniformity tests. This application is used to generate results in the Quality section.
Sampling Application producing output from distribution samplers to create an approximate probability density function (PDF) as shown here.
Quadrature Application for computing numerical quadrature by Monte-Carlo (random) integration.
JMH Benchmarks that assess the performance of the generators using the Java Microbenchmark Harness. This application is used to generate results in the Performance section.
JPMS Example JPMS application using all the JPMS modules of Commons RNG (requires Java 11+).

The examples require Java 8+ unless specifed as requiring a higher version.

The examples can be built using profiles in the relevant module. For example to build the JMH benchmarks application and show the help information:

cd commons-rng-examples/examples-jmh
mvn package -P examples-jmh
java -jar target/examples-jmh.jar -h

Details of each example module is contained in a HOWTO.md document in the module directory.

7. Release compatibility

Apache Commons RNG will maintain binary compatibility within a major release number. However the output from a random generator may differ between releases. This is a functional compatibility change. The result is that when upgrading the library any code based on a random generator may produce different results. For example any unit test code written with a fixed seed to generate pseudo-random test data may fail after update as the test data has changed.

The library generators are algorithms that produce a stream of random bits using 32-bit or 64-bit integer output. The output from the primary type will maintain functional compatibility for the lifetime of the library. This output is tested to match a reference implementation of the algorithm and should be invariant. The only exception is to address bug fixes identified in the upstream implementation.

The primary output of the generator is used to produced derived types, for example floating point values, byte arrays and integers within a range. The output for derived types is not subject to functional compatibility constraints. Put simply the output from a generator may be different even when using the same starting seed due to updates to generation algorithms that use the underlying stream of random bits. Any library changes that result in a functional compatibility should be recorded in the release notes.

The library is provided as modules. It is recommended to explicitly include all required RNG modules in a project using the same version number. This will avoid version mismatch occurring between modules due to transitive dependencies; specifically this avoids using an alternative version number explicitly specified in the dependency tree. A Bill of Materials (BOM) is provided to ease dependency management.

8. Dependencies

Apache Commons RNG requires JDK 1.8+ and has no runtime dependencies.