如何基于自制Linear Congruential Generator实现均匀与指数分布?
Great question! Let's walk through building these distributions using your existing Linear Congruential Generator (LCG) — first, let's recap your LCG's behavior since that's our foundation:
You're using the classic MINSTD_RAND parameters (multiplier 16807, modulus (2^{31}-1), which is 2147483647). This generator outputs values from 1 to 2147483646 (note: never initialize RN to 0, or it'll get stuck outputting 0 forever!).
1. Uniform Distribution Implementation
Uniform distributions come in two flavors: discrete (integer outputs) and continuous (floating-point). Since you mentioned wanting a long Module::... function, we'll start with discrete uniform, then add a continuous version for completeness.
Discrete Uniform (Integer Range [a, b])
This distribution gives every integer between a and b (inclusive) an equal chance of being selected. The key here is avoiding modulo bias — if we just take LCG() % range, some values will be slightly more likely if the LCG's output range isn't a perfect multiple of our target range.
Here's the implementation:
long Module::uniform_int(long a, long b) { const long MODULUS = 2147483647; const long MAX_LCG_OUTPUT = MODULUS - 1; // LCG outputs 1 to this value const long RANGE = b - a + 1; // Calculate upper limit to avoid bias: discard values above this const long LIMIT = MODULUS - (MODULUS % RANGE); long rn; do { rn = LCG(); // Get next LCG value } while (rn >= LIMIT); // Discard biased values return a + (rn % RANGE); }
- Parameters:
a(lower bound) andb(upper bound), wherea <= b - Mean: ((a + b) / 2) (for integers, this will be a floor/ceil value if the sum is odd)
- Standard Deviation: (\sqrt{(RANGE^2 - 1)/12}) (the formula for discrete uniform variance)
Continuous Uniform (Floating-Point Range [low, high])
If you need a continuous uniform distribution (common for statistical simulations), we can normalize the LCG output to the [0,1) range first, then map it to your target interval:
double Module::uniform_double(double low, double high) { const long MODULUS = 2147483647; long rn = LCG(); // Normalize LCG output to [1/MODULUS, 1) (no zero, which avoids edge cases later) double normalized = static_cast<double>(rn) / MODULUS; return low + normalized * (high - low); }
- Mean: ((low + high) / 2)
- Standard Deviation: ((high - low)/\sqrt{12})
2. Exponential Distribution Implementation
Exponential distributions model waiting times (e.g., time between events) and use the inverse transform sampling method, which relies on a [0,1) uniform distribution. The probability density function is (f(x) = \lambda e^{-\lambda x}), where (\lambda) is the rate parameter (mean = (1/\lambda), standard deviation = (1/\lambda)).
Continuous Exponential Distribution
double Module::exponential(double lambda) { // Lambda must be positive! Add a check if you want to enforce this const long MODULUS = 2147483647; long rn = LCG(); double u = static_cast<double>(rn) / MODULUS; // [1/MODULUS, 1) // Inverse CDF: -ln(u)/lambda (we can use u instead of 1-u since they're identical in distribution) return -log(u) / lambda; }
If you need a long return type (e.g., integer waiting times), just floor the continuous result:
long Module::exponential_int(double lambda) { double exp_val = exponential(lambda); return static_cast<long>(floor(exp_val)); }
Critical Notes
- Seed Initialization: Make sure you initialize
RNto a non-zero value (e.g.,RN = static_cast<long>(time(nullptr)) % 2147483646 + 1;— this ensures it's between 1 and 2147483646). - LCG Limitations: Your MINSTD_RAND has a period of (2^{31}-2), which is more than enough for most non-cryptographic use cases. If you need better randomness later, you could combine multiple LCGs, but this setup works perfectly for your current goals.
内容的提问来源于stack exchange,提问作者user9158336




