Random Numbers and Reproducibility

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. The sessions in the "Octave 11.3.0 output" blocks were run in GNU Octave 11.3.0. Octave does not implement rng or RandStream, and its random generators are not MATLAB’s, so the numbers that Octave printed are never MATLAB’s numbers. For that reason the Octave runs below only print properties that do not depend on the generator (equality of repeated sequences, ranges, counts, classes). Every example that calls rng or RandStream is checked line by line against the R2026b documentation and labelled "not run here". Numeric values quoted from the documentation are labelled as such.

A computer cannot flip a coin; MATLAB uses pseudorandom number generators. The documentation describes them as algorithms that produce deterministic sequences of numbers that appear random: they are predictable if the algorithm, the seed and the internal state are known, yet they pass statistical tests of randomness and satisfy the independent and identically distributed (i.i.d.) condition. Because the sequence is deterministic, you can control it, which is the point of this page: to repeat a calculation exactly (a test, a bug report, a figure in a paper) or to make sessions differ on purpose. Statistics of the results are on Descriptive Statistics and Preprocessing; distribution-specific generators (normrnd, random) belong to the Statistics and Machine Learning Toolbox, see Toolboxes and Add-On Products.

The four generating functions

rand, randn, randi and randperm all draw from one shared generator, the global stream. They differ in what they make of it.

Function Result Common syntaxes

rand

Uniform numbers in the open interval (0,1).

X = rand, X = rand(n) (n-by-n), X = rand(sz1,...,szN), X = rand(sz) (size vector), X = rand(___,typename) with "single" or "double", X = rand(___,like=p), X = rand(s,___) (a stream s).

randn

Standard normal numbers (mean 0, standard deviation 1).

The same family of syntaxes as rand.

randi

Uniform integers from a discrete uniform distribution.

X = randi(imax,___) for 1 to imax; X = randi([imin,imax],___); typename may also be "int8", "uint8", "int16", "uint16", "int32", "uint32" or "logical".

randperm

A random permutation of the integers 1 to n, without repeats.

p = randperm(n), p = randperm(n,k) for k distinct integers chosen from 1 to n, p = randperm(s,___).

You can give typename or like, not both. To match the size of an existing array A, use X = rand(size(A)).

To draw from another interval or distribution, transform the uniform numbers. The documentation gives the formula for the interval (a,b) as r = a + (b-a).*rand(n,1). For a normal distribution with mean mu and standard deviation sigma, scale and shift: mu + sigma*randn(n,1).

a = -5;  b = 5;  n = 10;
r = a + (b-a).*rand(n,1);        % uniform in (-5,5)
k = randi([10 50],1,5);          % five integers from 10 to 50
p = randperm(8,4);               % four distinct integers from 1 to 8
z = 20 + 0.3*randn(1000,1);      % normal, mean 20, standard deviation 0.3

Octave 11.3.0 accepts all of these forms. Its random values are not MATLAB’s, so only invariants were checked:

r = randi([10 50],1,5); disp(all(r>=10 & r<=50))
p = randperm(8,4); disp(numel(unique(p)))
disp(class(rand(1,4,'single')))
z = randn(1,100000); fprintf('%d\n', abs(mean(z))<0.02 && abs(std(z)-1)<0.02);

Octave 11.3.0 output:

1
4
single
1

Note that randi is the function for integers. The documentation example randi([10 50],1,5) shows the pattern; do not round rand results by hand, because round(10*rand) gives the end values half the probability of the others.

Controlling the global stream with rng

The rng function controls the global stream, so it controls rand, randi, randn, randperm and the other functions built on them (the documentation names sprand among them).

Syntax Effect

rng("default")

Initialises the generator with the default algorithm and seed. The factory default is the Mersenne Twister, "twister", with seed 0.

rng(seed)

Seeds the current algorithm. seed is a nonnegative integer below 232, or "shuffle" to seed from the current time.

rng(seed,generator)

Sets both. For example rng(2,"philox").

rng(generator)

Algorithm only, with seed 0; equivalent to rng(0,generator) (R2023b).

s = rng

Returns the current settings as a structure with the fields Type, Seed and State.

rng(sprev)

Restores settings saved in a structure returned by an earlier rng.

s = rng(___)

Applies the arguments and returns the previous settings.

The documented generator names for rng are "twister" (default), "combRecursive", "multFibonacci", "philox", "simdTwister" and "threefry", plus "pcg" and "xoshiro" (both since R2026b). The legacy "v4", "v5uniform" and "v5normal" exist only to reproduce old results.

Repeating at startup, and different sessions

MATLAB initialises the generator the same way at every startup, so a fresh session returns the same sequence each time. The documentation shows that after rng("default") the command rand(2) returns

    0.8147    0.1270
    0.9058    0.9134

(Values quoted from the documentation page, not run here.) To get different sequences in different sessions, call rng("shuffle") once, before the first random call, or put it in a startup file (see Search Path, Startup and Files). The documentation is explicit that reseeding does not make numbers "more random": reseeding more often than at startup or before a big calculation can even harm the statistical properties.

rng("default")        % same numbers as a fresh session
A = rand(2,2);

rng("shuffle")        % seed from the clock: differs between runs
s = rng;              % s.Seed shows which seed was chosen

(Not run here.) Saving s.Seed after "shuffle" lets you rerun a surprising result exactly with rng(s.Seed,s.Type).

Seeding for repeatability

For a program whose results must survive a MATLAB upgrade or a change of the Settings window, set seed and algorithm together at the start.

rng(1,"twister")
r = randn(1,5);
rng(1,"twister")
r2 = randn(1,5);        % identical to r
isequal(r,r2)

(Not run here.) The documentation shows, for the Mersenne Twister with seed 1, randn(1,5) equal to -0.6490 1.1812 -0.7585 -1.1096 -0.8456, the same both times. Different seeds give different sequences, but separately seeded sequences are not guaranteed to be statistically independent of each other; for guaranteed independence use streams (below).

rng("default") repeats results within a session, but it uses the default algorithm and seed taken from the MATLAB settings. Since R2023b you can change those under Home > Settings > MATLAB > General > Random Number Generation, so the documentation recommends rng(seed,generator) or rng(generator) for results that must be identical across releases and settings.

Saving and restoring the state

The first call to a random function changes the generator state, so a second identical call gives different numbers. Reseeding restarts the sequence; saving the structure lets you return to any point. This is the tool for debugging a loop iteration that fails: store s = rng at the top of each iteration, and restore it after the failure.

rng(1,"twister")
A = randi(10,3,3);
s = rng;                 % save settings after the first draw
B = randi(10,3,3);
rng(s)                   % go back
C = randi(10,3,3);       % C equals B

(Not run here.) The documentation example with this sequence reports B as the 3-by-3 matrix with rows 6 3 7, 5 9 5 and 7 1 6, and the same matrix again after rng(s). The state field is a column of 625 uint32 values for the Mersenne Twister.

The same save and restore also works for a single call site without disturbing the rest of the program:

function x = noisyOnce(n)
    previous = rng(7,"twister");   % set seed, keep the caller's settings
    cleanup = onCleanup(@() rng(previous));
    x = randn(n,1);
end

(Not run here.) Because s = rng(___) returns the previous settings, noisyOnce leaves the caller’s stream exactly where it was.

Independent streams with RandStream

rng is the simple interface to one stream. The RandStream class (introduced in R2008b) creates streams that are separate from the global stream and from each other. Reasons to use one, from the documentation: generate values without disturbing the global stream, keep different sources of randomness apart in a simulation, and get more control than rng offers (different normal transformations, antithetic values, substreams).

myStream = RandStream("multFibonacci");
r = rand(myStream,1,5);                     % draws only from myStream

s = RandStream("mt19937ar",Seed=15,NormalTransform="Polar");
x = randn(s,3,1);

RandStream.setGlobalStream(myStream)         % make it the global stream
g = RandStream.getGlobalStream;

(Not run here.) Pass the stream as the first argument of rand, randn, randi or randperm; without it they use the global stream. The creation syntaxes are s = RandStream(generator), s = RandStream(generator,Name=Value) and, since R2026b, s = RandStream(existingStream) to copy a stream and its state. The name-value arguments are Seed (default 0, or "shuffle"), NormalTransform ("Ziggurat", "Polar" or "Inversion"), and, since R2026b, Antithetic and FullPrecision.

RandStream uses the longer algorithm names ("mt19937ar" is "twister", "dsfmt19937" is "simdTwister", "mrg32k3a" is "combRecursive", "mlfg6331_64" is "multFibonacci", "philox4x32_10" is "philox", "threefry4x64_20" is "threefry"), and accepts the short names too. Only some generators support multiple streams and substreams: "mlfg6331_64", "mrg32k3a", "philox4x32_10", "threefry4x64_20" and, since R2026b, "pcg64dxsm" and "xoshiro256pp". The Mersenne Twister and the SIMD Mersenne Twister are meant for sequential use. For guaranteed non-overlapping streams use RandStream.create with a multi-stream algorithm, or select a Substream index; RandStream.list returns the available algorithms.

The Parallel Computing Toolbox changes the defaults on workers: the client uses the Mersenne Twister with seed 0, workers use Threefry 4x64 with 20 rounds, and rng("shuffle") on workers is discouraged because simultaneous workers can get the same seed. Parallel computing is outside base MATLAB; see Toolboxes and Add-On Products.

The legacy seed and state syntaxes

Older books (the bibliography lists them, see Bibliography) show rand('seed',n), randn('state',n), rand('twister',n) and RandStream.getDefaultStream. The documentation now calls the 'seed', 'state' and 'twister' inputs discouraged: the words are misleading ('seed' names the MATLAB 4 generator, not a seed value; 'state' names the MATLAB 5 generators), all the generators except the twister are flawed, and rand and randn used unnecessarily different generators. They still exist, so old code runs, but it should be migrated to rng.

Legacy generator name rng generator name

'seed'

"v4"

'state'

"v5uniform" for rand, "v5normal" for randn

'twister'

"twister" (recommended)

For a seeded sequence the typical replacement is rand("twister",sd) becoming rng(sd,"twister"). Use the v4 and v5 generators only to reproduce numbers from very old releases. To see which old statements your code runs, the documentation suggests enabling two warnings at the start of a session:

warning("on","MATLAB:RandStream:ActivatingLegacyGenerators")
warning("on","MATLAB:RandStream:ReadingInactiveLegacyGeneratorState")

(Not run here.) Octave 11.3.0 still accepts the legacy forms and its output shows the old forms behave as seed-and-restore pairs, which is what migration to rng preserves:

rand('twister',1); a = rand(1,3); rand('twister',1); b = rand(1,3);
disp(isequal(a,b))
rand('state',1); s = rand('state'); x = rand(1,3); rand('state',s); y = rand(1,3);
disp(isequal(x,y))

Octave 11.3.0 output:

1
1

Example: a reproducible noisy thermocouple

The heatflow scenario (see The heatflow scenario) logs thermocouple readings in data/sensors.csv with the columns Time, SensorID and Temperature. To test the cleaning code without the real log, simulate readings: a smooth true temperature plus measurement noise, plus a few dropped samples. Seeding makes the test data identical on every run, and a separate stream keeps the simulation from disturbing other random code.

function T = simulateSensor(seed)
%SIMULATESENSOR Noisy thermocouple readings with dropouts, repeatable by seed.
    arguments
        seed (1,1) double {mustBeNonnegative, mustBeInteger} = 0
    end
    stream = RandStream("mt19937ar",Seed=seed);
    time = seconds(0:30:600)';
    trueTemp = 20 + 5*(1 - exp(-seconds(time)/240));
    noise = 0.2*randn(stream,size(trueTemp));
    temperature = trueTemp + noise;
    dropped = rand(stream,size(trueTemp)) < 0.1;
    temperature(dropped) = NaN;
    T = timetable(time,temperature);
end

(Not run here: needs RandStream and timetable.) Calling simulateSensor(3) twice gives identical tables, while simulateSensor(4) differs. The NaN values then go through fillmissing or rmmissing, see Missing Data and Timetables. Octave 11.3.0 lacks RandStream, so the same logic, seeded with rng on the global generator instead of a private stream, was run for invariants only:

rng(42);
t = (0:0.5:10)';
trueT = 20 + 5*(1-exp(-t/4));
noisy = trueT + 0.2*randn(size(t));
rng(42); n2 = trueT + 0.2*randn(size(t));
fprintf('%d\n', isequal(noisy,n2));
dropMask = rand(size(t)) < 0.1; noisy(dropMask) = NaN;
fprintf('%d\n', sum(isnan(noisy)) <= numel(t));

Octave 11.3.0 output:

1
1

Much older MATLAB code, and Octave code, seeds with rand('twister',42) and randn('twister',42); those are the legacy forms of The legacy seed and state syntaxes, kept for reading old code, not for new code.

For repeatable tests, put rng(1,"twister") at the top of the test (or in a test fixture’s setup) rather than relying on the default.

Common mistakes

  • Expecting a new sequence in every session. The default is the same at each startup, so a script that "uses random numbers" returns the same results unless you call rng("shuffle").

  • Reseeding inside a loop. rng("shuffle") or rng(1) in every iteration repeats values (same seed) or harms the statistical properties; seed once.

  • Seeding with rng("shuffle") in a parfor loop. Workers may get the same seed; use the default behaviour or substreams.

  • Assuming rng("default") is permanent. Since R2023b it follows the Settings window; set seed and algorithm explicitly for results that must be portable.

  • Comparing results across releases or machines without fixing the algorithm. The same seed with a different algorithm is a different sequence.

  • Using rand for integers. Use randi or randperm.

  • Putting the stream in the wrong place. The stream is the first argument: rand(myStream,n). Without it the call draws from the global stream and myStream is left untouched.

  • rng and tall arrays or GPUs. rng controls in-memory arrays only; tall arrays use tallrng, and GPU arrays use gpurng (Parallel Computing Toolbox).

Where the books are outdated

The older books describe rand('seed',n), randn('seed',n), rand('state',n) and RandStream.getDefaultStream (the current documentation uses RandStream.getGlobalStream for the same purpose). Current MATLAB uses rng and RandStream.getGlobalStream; the old syntaxes are discouraged and rng replaces them. They also describe rand and randn as separate generators, whereas all the random functions now share one global stream, which also supplies randi and randperm. The books predate the Settings window default (R2023b), the rng(generator) syntax (R2023b), and the "pcg" and "xoshiro" generators (R2026b). The statement that the Mersenne Twister is the startup generator is unchanged.