Concepts / Setting Seeds for Reproducible Randomness

Setting Seeds for Reproducible Randomness

random.random() generates a pseudorandom float in the range [0.0, 1.0), inclusive of 0.0 but exclusive of 1.0

  • Programming

Why Random Output Can Repeat

A value produced by random.random() looks unpredictable, but it is not produced without a process. Each call returns the next value in a deterministic sequence based on the random module's internal state. Different runs generally produce different sequences unless you set a seed with random.seed() before calling random.random().

Random-looking does not mean unreproducible. Setting the same seed before the same calls produces identical sequences.

setscalls producesetscalls produceSeed valuesame valueSeed valuesame valueInternal statestate after seedingInternal statestate after seedingFloat sequencevalue 1, value 2, value 3Float sequencevalue 1, value 2, value 3
How does setting the same seed cause random.random() to produce the same sequence across separate runs?

The Value Each Call Returns

random.random() generates a pseudorandom float in the range [0.0, 1.0). The word pseudorandom describes a value that appears random while belonging to a deterministic sequence controlled by the random module's internal state.

The notation [0.0, 1.0) means that 0.0 is included and 1.0 is excluded. A call can return exactly 0.0, but it will never return exactly 1.0.

throughapproaches but excludes0.0includedValues betweenpossible floats1.0excluded
Where can each generated float appear on a number line, and why is 0.0 possible while 1.0 is excluded?

The range can be scaled. Multiplying a generated value by 100 gives a float in [0.0, 100.0). Multiplying by 6 and converting to an integer gives one of 0, 1, 2, 3, 4, or 5, which can be useful for simulating a die roll.

Following the Generator Through a Loop

A loop is a straightforward way to observe random.random(). If the loop calls the function 10 times, it prints 10 random floats, one per iteration. Every value is in [0.0, 1.0), but the exact values depend on the random module's internal state and can differ from values seen in another run.

returnsinternal state advancesreturnsinternal state advancesreturnsIteration 1random.random()Float 1[0.0, 1.0)Iteration 2random.random()Float 2[0.0, 1.0)Iteration 3random.random()Float 3[0.0, 1.0)
What changes after each loop iteration, and which output value comes next?

Tracing Three Calls

Imagine a loop that calls random.random() three times.

First iteration: The first call returns the next float determined by the random module's current internal state.

Second iteration: The second call returns a new float. The internal state has changed because the first call occurred.

Third iteration: The third call returns the next float in the sequence after the second call, and the internal state advances again.

The loop produces three floats. Each call is a separate event, and the next value depends on the state left by the preceding call.

What do you think happens?

If the same code is run twice without resetting the seed, should you expect the two runs to produce identical sequences?

  • Yes, because the loop is the same
  • No, because the random module's state is not reset
  • Yes, because every call returns the same value
Reveal answer

Answer: No, because the random module's state is not reset.

The source material states that running the same code twice without resetting the seed produces different sequences. Setting the same seed before both runs produces identical sequences.

Reproducing a Sequence

To reproduce an exact sequence, set the seed with random.seed() before the sequence of random.random() calls begins. When the same seed is set before the same sequence of calls, the random module starts from the corresponding same state and produces identical values in the same order.

SetupExpected sequence behavior
Same seed before the same callsIdentical sequences
No seed reset between runsDifferent sequences can occur
Different seedThe sequence can differ
Different number of earlier random callsLater output can differ because the internal state differs

How setup affects reproducibility

Finding the Divergence Point

When actual output differs from an expected sequence, locate the first random.random() call that returned a different value. That is the divergence point. The difference comes from the random module having a different internal state, which can happen when the seed was not set, a different seed was used, or a different number of random calls occurred before the code ran.

next callnext callCall 1expected and actual matchCall 2expected and actual differCall 3later values follow thechanged state
At which call do two random-number sequences diverge when their seeds, call counts, or generator states differ?

Comparing Expected and Actual Values

Suppose five expected values are [0.1, 0.2, 0.3, 0.4, 0.5], while the observed values are [0.7, 0.8, 0.9, 0.6, 0.5]. Where does the sequence first diverge?

Compare the first call: The expected first value is 0.1, but the observed first value is 0.7.

Mark the divergence: The first call to random.random() is the divergence point because it produced a different value immediately.

Interpret the cause: The random module's internal state was different from the state that produced the expected sequence.

The divergence occurred at the first random.random() call. Reproducing the expected sequence requires setting the same seed that produced it previously.

  • Assuming a random-looking value is unrelated to previous calls.

    The random module maintains internal state, and each call returns the next value in a deterministic sequence based on that state.

    Fix: Trace each call in order and remember that every call advances the internal state.

  • Expecting identical output from repeated runs without resetting the seed.

    The source states that runs without resetting the seed produce different sequences.

    Fix: Set the same seed before both sequences when reproducible output is needed.

  • Treating 1.0 as a possible result.

    The interval uses a parenthesis at 1.0, so the upper bound is exclusive.

    Fix: Read [0.0, 1.0) as including 0.0 and excluding 1.0.

  • Searching for the divergence somewhere after the generated value.

    The divergence occurs at the call where the pseudorandom value is generated.

    Fix: Compare expected and actual values in order and identify the first differing random.random() call.

Check Your Understanding

MEDIUM

A loop calls random.random() three times. Explain what happens to the random module's internal state after each call, state the valid range for each returned value, and describe what you would change to make a second run reproduce the first run's sequence.

Hints
  • Each call returns the next value in the sequence.
  • The range includes 0.0 and excludes 1.0.
  • Use random.seed() before the loop, using the same seed before both runs.

Key Takeaways

  1. random.random() returns a pseudorandom float in [0.0, 1.0).
  2. The lower bound 0.0 is included, while the upper bound 1.0 is excluded.
  3. Every call returns the next value and advances the random module's internal state.
  4. The same seed before the same calls produces the same sequence.
  5. When output differs from expectation, find the first random.random() call where the values diverge.

Key Takeaways

  • random.random() generates pseudorandom floats from 0.0 inclusive up to, but not including, 1.0.
  • Repeated calls move through a deterministic sequence controlled by the random module's internal state.
  • Setting the same seed before repeated runs makes their sequences reproducible.
  • The first differing random.random() call marks the divergence point during debugging.