How Lodash _.random Ensures Uniform Distribution
The _.random function in Lodash provides uniform
pseudo-random number generation by combining modern JavaScript engine
PRNGs with precise interval scaling and boundary-safe transformations.
Rather than implementing its own proprietary generator from scratch,
Lodash delegates entropy generation to JavaScript's native
Math.random() and applies mathematical mappings that
prevent boundary skew and modulo bias. This article examines the
internal mechanisms Lodash uses to ensure both integer and
floating-point distributions remain strictly uniform across arbitrary
ranges.
Reliance on Modern Engine PRNGs
Lodash relies on JavaScript's native Math.random() as
its entropy source. Modern JavaScript engines, such as V8 (Node.js,
Chrome) and SpiderMonkey (Firefox), implement high-quality pseudo-random
number generators (PRNGs), typically variants of the
xorshift128+ algorithm.
These algorithms generate double-precision floating-point numbers uniformly distributed in the half-open interval \([0, 1)\), meaning:
\[0 \le x < 1\]
Because the underlying engine guarantees that every sub-interval
within \([0, 1)\) has an equal
likelihood of being chosen, Lodash’s primary responsibility is to
preserve this property when transforming the unit interval to the
caller's target range [lower, upper].
Eliminating Modulo Bias for Integers
A common mistake in pseudo-random mapping is using the modulo
operator (e.g., randomInt % range), which causes modulo
bias when the generator's state space does not divide evenly into the
target range. Lodash eliminates modulo bias entirely by using interval
scaling and floor discretization:
lower + nativeFloor(nativeRandom() * (upper - lower + 1))This mathematical approach guarantees uniformity through three distinct steps:
- Interval Sizing (
upper - lower + 1): When generating inclusive integers, the total number of distinct outcomes is \(N = upper - lower + 1\). Multiplying the \([0, 1)\) interval by \(N\) expands the continuous range to \([0, N)\). - Equidistant Partitioning: The interval \([0, N)\) is partitioned into \(N\) equal-width bins of length 1: \([0, 1), [1, 2), \dots, [N - 1, N)\). Since the underlying PRNG is uniform, the probability of a value falling into any single bin is precisely:
\[P(\text{bin}_k) = \frac{1}{N}\]
- Floor Discretization (
nativeFloor): ApplyingMath.floor()maps each continuous bin directly to its corresponding integer index without compressing or stretching the endpoints. Addinglowershifts the zero-indexed bin into the requested output domain.
Floating-Point Normalization and Inclusive Bounds
When floating-point values are requested (either via the
floating flag or passing float arguments), Lodash generates
a continuous real number:
var rand = nativeRandom();
var randLength = rand.toString().length - 1;
return Math.min(lower + (rand * (upper - lower + freeParseFloat('1e-' + randLength))), upper);Because native Math.random() produces values in the
half-open range \([0, 1)\), the upper
bound is technically unreachable under direct multiplication. To make
the range inclusive for floating-point values without distorting the
linear distribution, Lodash applies an epsilon-scaled offset based on
the string precision of the generated float and clamps the result to
upper using Math.min(). This ensures that
intermediate numbers scale linearly while safely accounting for edge
inclusiveness.
Argument Normalization and Deterministic Boundaries
To prevent statistical distortions caused by invalid parameters or undefined state, Lodash normalizes inputs before executing the mathematical transformation:
- Automatic Boundary Reversal: If
loweris greater thanupper, Lodash swaps the values rather than returning an invalid result or throwing an error. - Implicit Floor Detection: If neither bound is
explicitly designated as a float, but either parameter contains a
fractional component (e.g.,
lower % 1 !== 0), Lodash automatically switches to floating-point mode. This prevents unexpected truncation that would otherwise skew boundary probabilities. - Single-Argument Handling: Passing a single value \(N\) defaults the lower bound to \(0\), ensuring the interval remains calibrated to \([0, N]\) without reducing bucket sizes.
By enforcing linear scaling over raw modulo arithmetic and relying on native double-precision PRNG implementations, Lodash guarantees that each value within the user-specified interval maintains an identical probability of selection.