Chapter Orientation
XRT's randomness capability splits into four layers, each with its own place: the Secure layer goes to the operating-system entropy source (xrtSecureRandom, exclusively for keys and tokens); the global Rand layer is the thread-local default choice; the Fast layer prioritizes speed, not security; the RNG instance layer seeds explicitly and is fully reproducible (xrng). This chapter makes the four-layer selection clear and pins one red line: PCG and the Fast series are not cryptographically secure — tokens, keys, and nonces must go through the Secure layer. Fixed-seed reproducibility is the other theme: test snapshots and replay systems both stand on "same seed, same sequence".
11.1引入
Two seemingly contradictory demands coexist. In games and simulation you want randomness reproducible — the same session, the same save loaded, the random event sequence must agree, or replay recordings and synchronous online play all fail; in security you want randomness unpredictable — a guessed session token equals a hijacked account. These two must be carried by different mechanisms: the former by an explicitly seeded pseudorandom generator (same seed, same sequence), the latter by the operating-system entropy source (xrtSecureRandom).
The common mistake is mixing the two: seeding a fast generator with the time and generating tokens — the time is guessable and the generator predictable, two layers of protection amounting to zero. XRT cuts off this error class with explicit naming: Rand/FastRand/Rng read as statistical at a glance, SecureRandom/SecureText read as security at a glance, and the module docs state plainly "security boundaries must use Secure". Naming is documentation — during code review you needn't chase into the implementation; the function name alone completes half the security audit.
11.2概念
The four-layer system and selection
Selection asks only two questions: do you need unpredictability? Yes → Secure, done. Do you need reproducibility? Yes → an RNG instance (explicit state, explicit seed). Neither → the global Rand for convenience, Fast for confirmed performance-critical hot paths. The four layers' function-name prefixes map one-to-one onto those answers — visible at a glance in code review.
Add a feel-list of "which layer for which scenario": generating session tokens → SecureText; game drop rolls → an RNG instance (replay requirement); load-testing traffic perturbation → the global Rand; graphics particle noise → Fast; temporary file names → SecureText or XID (Chapter 12); test fault-injection points → an RNG instance (the seed is the test case). The list grows per project, but every new entry should answer one of those two questions.
The RNG instance: explicit state and stream numbers
xrng is a state struct you can put anywhere — a stack variable, a struct member, a stretch of bytes in a save file. xrtRngSeed(&Rng, 种子, 流序号) seeds (arguments: seed, stream number — verbatim token below): same seed, different stream number yields mutually independent sequences. This second parameter solves the classic trap: two generators seeded identically produce fully correlated output — when a server hands each session an RNG, using the session number as the stream number guarantees non-correlation. The generation family covers integers (Rng32/Rng64), floats (RngReal, [0,1)), bounded integers (RngRangeClosed, the closed-closed interval — a die roll 1..6 including 6), byte filling (RngBytes), and in-place shuffling (RngShuffle, Fisher-Yates).
The global layer and threads
Global functions like xrtRand64 use thread-local state — multi-threaded concurrent calls need no locking and never interfere with each other's sequences. This layer suits "just give me a random number"; when a sequence must be consistent across calls (the same logic rerun wanting the same result), return to the RNG instance. The Fast series (xrtFastRandSeed to seed, xrtFastRand32 to draw, etc.) is a speed-first implementation without security guarantees, for confirmed extreme-performance non-security scenarios.
Random text and alphabets
RandText / RandStringFrom / SecureText generate random strings: you can specify the character set (say only the unambiguous 23456789abcdefghjkmnpqrstuvwxyz) to generate invitation codes, temporary directory names, and session tokens. The security counterparts are SecureText and SecureStringFrom — built atop xrtSecureRandom, the random text's unpredictability identical to the byte version's.
A custom alphabet is not just cosmetics: excluding lookalikes like 0O1lI keeps invitation codes from being miscopied by users; restricting to the URL-safe set lets tokens go straight into links and QR codes. The generation length is caller-specified, and failure (entropy source unavailable) is likewise declared by return value — failures on the security path are never silently swallowed.
Engineering uses of reproducibility
Reproducible randomness has three high-frequency landing points. Test snapshots: write the seed into the test fixture, assert the generated sequence's hash (Chapter 12) equals the recorded fingerprint — regression testing of random logic goes from "luck" to exact assertion. Replay recordings: store only the seed and the operation sequence; on replay, re-run the generation calls, random events frame-identical. Fault injection: use an RNG to decide injection points (combined with Chapter 6's FailAfter for mixed fault modes), the seed being the test-case number — a failing rerun doesn't change the result. The shared discipline of the three: the order of generation calls is also state — skip one Rng32 during reproduction and everything after shifts.
Reading the three assertion classes of random tests
Before running the examples, be clear how random code is tested — both examples' assertions come from these three classes. Reproducibility assertions: generate twice with the same seed, results item-by-item equal (or hash fingerprints equal). Bounded assertions: generated values fall in the declared interval, including both endpoints of the closed interval each hit at least once. Distribution assertions: over large samples the frequencies approach expectation (judged with Chapter 10's Near on relative deviation). Three classes each at their post: reproducibility guarantees determinism, bounds guarantee semantics, distribution guarantees quality — writing only one leaves the corresponding dimension's defects uncaught.
11.3示例
Complete program: the explicit RNG full family
From the repository example examples/math/random/main.c — fixed seed, no global state, fully reproducible output:
*/ #include <stdio.h> #include <xrt.h> int main(void) { xrng Rng; /* 状态在栈上:零全局依赖 */ int arrOrder[] = { 1, 2, 3, 4, 5, 6 }; uint8 arrBytes[8]; xrtRngSeed(&Rng, 2026, 7); /* 种子 2026,流 7 */ /* 整数与浮点:同一状态顺序推进(消耗即前进)。 */ printf("explicit: %u\n", (unsigned int)xrtRng32(&Rng)); printf("dice : %lld\n", (long long)xrtRngRangeClosed(&Rng, 1, 6)); printf("real : %.12f\n", xrtRngReal(&Rng)); /* 字节填充与洗牌:RngBytes 无失败路径(内部检查冗余但保留统一风格)。 */ if ( !xrtRngBytes(&Rng, arrBytes, sizeof(arrBytes)) || !xrtRngShuffle(&Rng, arrOrder, sizeof(arrOrder) / sizeof(arrOrder[0]), sizeof(arrOrder[0])) ) { return 1; } printf("bytes : %02x%02x\n", (unsigned int)arrBytes[0], (unsigned int)arrBytes[1]); printf("shuffle : %d %d %d\n", arrOrder[0], arrOrder[1], arrOrder[2]); return 0; }
What just happened. (1) xrng Rng is local state on the stack — whoever declares it holds it; putting it in a struct, one per session, or storing it in a replay save are all the same shape. (2) xrtRngSeed(&Rng, 2026, 7): seed 2026, stream number 7; these fifty-odd bytes of sequence are now fully deterministic — any machine at any time rerunning gives these five output lines — the cornerstone of test snapshots and replay recordings. (3) The five generation functions advance the same state in sequence: the integer drawn first influences the die rolled later — when reproducing a sequence, the call order must also agree. (4) RngRangeClosed(&Rng, 1, 6) is the closed-closed interval; a die roll no longer needs 1 + x % 6 (the double trap of a half-open interval plus modulo bias). (5) RngShuffle is an in-place shuffle — the array is still that array, its order uniformly permuted by Fisher-Yates; "uniform" is not rhetoric — every permutation equally probable, the object of the distribution check in this chapter's exercises.
Complete program: a full tour of the four-layer interface
From examples/math/random_tour/main.c, walking each of the four layers and asserting reproducibility and boundedness:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> #define SV(x) XRT_STR_LITERAL(x) /* 字母表断言:str 全部字符都在表内且长度正确。 */ static bool exampleInAlphabet(cstr sText, size_t iLength, xstrview Alphabet) { size_t i; size_t j; if ( strlen(sText) != iLength ) { return false; } for ( i = 0; i < iLength; ++i ) { bool bFound = false; for ( j = 0; j < Alphabet.Size; ++j ) { if ( sText[i] == Alphabet.Data[j] ) { bFound = true; break; } } if ( !bFound ) { return false; } } return true; } int main(void) { static const char sHex[] = "0123456789abcdef"; xrng RngA; xrng RngB; char Buffer[16]; char TextA[9]; char TextB[9]; uint8 BytesA[8]; uint8 BytesB[8]; str sGenerated; int i; int iResult = 1; /* ---- 全局线程层:有界性 + 洗牌保持多重集 ---- */ (void)xrtRand64(); if ( (xrtRandBelow(10u) >= 10u) || (xrtRandBelow(1u) != 0u) ) { goto Cleanup; } { int64 iMin = 100; int64 iMax; for ( i = 0; i < 64; ++i ) { iMax = xrtRandRange(-5, 5); if ( (iMax < -5) || (iMax > 5) || (iMax == iMin) ) { iMin = iMax; } } } if ( !xrtRandBytes(Buffer, sizeof(Buffer)) ) { goto Cleanup; } { /* 洗牌 0..5:结果必须是原多重集的排列。 */ int Numbers[6] = { 0, 1, 2, 3, 4, 5 }; bool bSeen[6] = { false }; bool bPermutation = true; if ( !xrtRandShuffle(Numbers, 6u, sizeof(int)) ) { goto Cleanup; } for ( i = 0; i < 6; ++i ) { if ( (Numbers[i] < 0) || (Numbers[i] > 5) || bSeen[Numbers[i]] ) { bPermutation = false; break; } bSeen[Numbers[i]] = true; } if ( !bPermutation ) { goto Cleanup; } } /* RandText:按字母表填充固定缓冲;RandStringFrom 分配。 */ if ( !xrtRandText(SV(sHex), TextA, sizeof(TextA), 8u) || !exampleInAlphabet(TextA, 8u, SV(sHex)) ) { goto Cleanup; } sGenerated = xrtRandStringFrom(SV(sHex), 16u); if ( (sGenerated == NULL) || !exampleInAlphabet(sGenerated, 16u, SV(sHex)) ) { goto Cleanup; } xrtFree(sGenerated); /* RandString:URL-safe 64 字符表(自定义表见 StringFrom)。 */ sGenerated = xrtRandString(12u); if ( (sGenerated == NULL) || (strlen(sGenerated) != 12u) ) { goto Cleanup; } xrtFree(sGenerated); printf("random: global rand64/below/range/bytes/shuffle ok\n"); /* ---- Fast 层:同种子可复现 ---- */ xrtFastRandSeed(12345u, 67890u); (void)xrtFastRand32(); (void)xrtFastRand64(); { /* 每个生成器只消费一次:回放序列必须完全对齐。 */ int64 iRange = xrtFastRandRange(-3, 3); if ( (xrtFastRandBelow(7u) >= 7u) || (iRange < -3) || (iRange > 3) || (xrtFastRandRangeClosed(1, 1) != 1) ) { goto Cleanup; } } { double dReal = xrtFastRandReal(); if ( (dReal < 0.0) || (dReal >= 1.0) ) { goto Cleanup; } } if ( !xrtFastRandBytes(BytesA, sizeof(BytesA)) ) { goto Cleanup; } { int Numbers[4] = { 0, 1, 2, 3 }; if ( !xrtFastRandShuffle(Numbers, 4u, sizeof(int)) ) { goto Cleanup; } } /* 重置同一种子:32/64/字节序列必须完全一致。 */ xrtFastRandSeed(12345u, 67890u); (void)xrtFastRand32(); (void)xrtFastRand64(); (void)xrtFastRandBelow(7u); (void)xrtFastRandRange(-3, 3); (void)xrtFastRandRangeClosed(1, 1); (void)xrtFastRandReal(); if ( !xrtFastRandBytes(BytesB, sizeof(BytesB)) || (memcmp(BytesA, BytesB, sizeof(BytesA)) != 0) ) { goto Cleanup; } printf("random: fast (seeded) 32/64/below/range/real/shuffle ok\n"); /* ---- RNG 实例层:Ready 门 + Below/Range/Text ---- */ xrtRngSeed(&RngA, 42u, 7u); xrtRngSeed(&RngB, 42u, 7u); if ( !xrtRngReady(&RngA) || (xrtRngReady(NULL)) ) { goto Cleanup; } /* 双实例同种子:Rng64/RngBelow/RngRange 逐个一致。 */ for ( i = 0; i < 8; ++i ) { if ( xrtRng64(&RngA) != xrtRng64(&RngB) ) { goto Cleanup; } } { /* 双实例逐调用同步消费,任何一次额外抽取都会失步。 */ uint32 uA = xrtRngBelow32(&RngA, 100u); uint32 uB = xrtRngBelow32(&RngB, 100u); if ( (uA != uB) || (uA >= 100u) ) { goto Cleanup; } uA = xrtRngBelow32(&RngA, 100u); uB = xrtRngBelow32(&RngB, 100u); if ( (uA != uB) || (uA >= 100u) ) { goto Cleanup; } { uint64 uA = xrtRngBelow64(&RngA, UINT64_C(1000000)); uint64 uB = xrtRngBelow64(&RngB, UINT64_C(1000000)); if ( (uA != uB) || (uA >= UINT64_C(1000000)) ) { goto Cleanup; } } } { int64 iA = xrtRngRange(&RngA, -100, 100); int64 iB = xrtRngRange(&RngB, -100, 100); if ( (iA != iB) || (iA < -100) || (iA > 100) ) { goto Cleanup; } } /* RngText 固定缓冲 + RngStringFrom 分配(同种子双实例一致)。 */ if ( !xrtRngText(&RngA, SV(sHex), TextA, sizeof(TextA), 8u) || !xrtRngText(&RngB, SV(sHex), TextB, sizeof(TextB), 8u) || (memcmp(TextA, TextB, 8u) != 0) || !exampleInAlphabet(TextA, 8u, SV(sHex)) ) { goto Cleanup; } sGenerated = xrtRngStringFrom(&RngA, SV(sHex), 12u); if ( (sGenerated == NULL) || !exampleInAlphabet(sGenerated, 12u, SV(sHex)) ) { goto Cleanup; } xrtFree(sGenerated); printf("random: rng instance ready=1 below32/64/range/text ok\n"); /* ---- Secure:字母表定制的文本生成 ---- */ sGenerated = xrtSecureStringFrom(SV(sHex), 8u); if ( (sGenerated == NULL) || !exampleInAlphabet(sGenerated, 8u, SV(sHex)) ) { goto Cleanup; } xrtFree(sGenerated); if ( !xrtSecureText(SV(sHex), TextA, sizeof(TextA), 8u) || !exampleInAlphabet(TextA, 8u, SV(sHex)) ) { goto Cleanup; } printf("random: secure text from alphabet len=8\n"); iResult = 0; Cleanup: return iResult; }
What just happened. (1) The global layer's five in a row: direct calls within a thread, no state declaration. (2) The Fast layer seeds with xrtFastRandSeed first, then draws — fast but non-secure, the header comment pinning the boundary. (3) The RNG-instance layer verifies RngReady and the bounded versions; (4) the Secure layer generates an 8-character token from a custom alphabet — the length assertion passing completes it (the content of course differs every time). The whole program verifies reproducibility with "same seed, two generations, identical results" and boundedness with range assertions — the two assertion classes your own random tests should copy.
11.4契约
- Security red line: keys, tokens, passwords, nonces, temporary resource names, and every security boundary must use
xrtSecureRandom/SecureText;Rng/Rand/FastRandare all statistical — declared doubly by documentation and naming. - Reproducibility contract: same seed (stream number included) and same call sequence → same output, stable across platforms and versions (the consistency of
xrtMathsupports it underneath). - Independent streams: multiple instances seeded with different stream numbers are mutually independent; same seed, same stream = a fully correlated copy — check the seeding lines before parallel simulations.
- Threading: the global Rand is thread-local, lock-free concurrency; an RNG instance belongs to the scope that declared it, and cross-thread sharing needs your own synchronization.
- Interval semantics:
RngRangeClosedis closed-closed (both endpoints included); a hand-writtenx % ncarries modulo bias — don't. - Order is state: when reproducing a sequence, the kinds and ordering of generation calls must exactly match the recording.
Common Mistakes
Symptoms: the security audit flags tokens as predictable; in principle an attacker observing a few outputs can reconstruct the internal state and compute every later token.
Cause: PCG and the Fast series are statistical — reproducibility and unpredictability are opposed by design; the former dooms the latter.
xrng Rng; xrtRngSeed(&Rng, (uint64)time(NULL), 1); /* the time is guessable */ xrtRngBytes(&Rng, Token, sizeof(Token)); /* PCG is predictable — the token is decoration */
uint8 Token[16]; if ( !xrtSecureRandom(Token, sizeof(Token)) ) { return false; /* entropy failure treated as failure */ } /* xrtSecureZero(Token, sizeof(Token)) when done (Chapter 5) */
Symptoms: multiple "independent" random streams highly correlated in a parallel simulation — identical deck orders, synchronized load patterns, distorted statistical conclusions.
Cause: same seed, same stream number produces exactly the same sequence; "multiple instances" does not imply "multiple sequences".
for ( i = 0; i < 4; ++i ) { xrtRngSeed(&Workers[i].Rng, 2026, 1); /* four workers, one sequence */ }
for ( i = 0; i < 4; ++i ) { xrtRngSeed(&Workers[i].Rng, 2026, (uint64)i + 1u); /* stream numbers split the flows */ }
Programming Exercises
Draw ten consecutive integers with a fixed seed and print them; rerun with a different stream number and compare — the two sequences differ; rerun with the original seed, asserting item-by-item equality with the first run. Finally, fingerprint the ten-element sequence with Chapter 12's Hash64 — future regression of this test compares a single 64-bit value.
Roll RngRangeClosed(&Rng, 1, 6) a hundred thousand times, tally the frequencies of 1–6, and use Chapter 10's Near to verify each face's relative deviation from expectation (a hundred thousand divided by six) is under one percent. Then a control: deliberately generate with 1 + (int)(xrtRng32(&Rng) % 6) and observe whether the deviation is still acceptable — appreciate whether "modulo bias" is actually a problem in small ranges, and write down your conclusion.
Design a minimal session replay: store the opening seed (stream number included) and the player's operation sequence in a save; on "replay", re-run the seed and the operations, asserting every random event (every drop, every roll) matches the live recording item by item. Acceptance: saves from two different seeds replay self-consistently and differently from each other; the same save replayed twice gives item-by-item identical results.
Summary
| Topic | Quick reference |
|---|---|
| Four layers | Secure (entropy source) / global Rand (thread-local) / Fast (speed) / RNG instance (reproducible) |
| Security red line | tokens, keys, nonces only via SecureRandom / SecureText |
| Seeding | RngSeed(&Rng, 种子, 流序号); stream numbers split the flows |
| Interval | RngRangeClosed closed-closed with endpoints; x % n rejected |
| Reproduction | same seed, same call order → same output; call order is also state |
| Text | SecureStringFrom generates tokens from a custom alphabet; excluding lookalikes prevents miscopying |
📖 API references in this chapter:random (41 entries)
Click for full signatures, parameter constraints, return values and error notes