UniformDouble.java
package org.loudouncodes.randkit.continuous;
import java.util.random.RandomGenerator;
import org.loudouncodes.randkit.api.ContinuousDistribution;
import org.loudouncodes.randkit.api.DistributionSupport;
import org.loudouncodes.randkit.util.Randoms;
/**
* Continuous Uniform(a, b) on the half-open interval {@code [a, b)} with {@code a < b}.
*
* <p>PDF: {@code f(x) = 1 / (b - a)} for {@code x ∈ [a, b)}, else 0.<br>
* CDF: {@code F(x) = 0} for {@code x ≤ a}; {@code (x - a)/(b - a)} for {@code a < x < b}; and
* {@code 1} for {@code x ≥ b}.
*/
public final class UniformDouble implements ContinuousDistribution {
private final RandomGenerator rng;
private final double a;
private final double b;
private final double width;
/**
* Constructs a UniformDouble(a, b) using a platform-provided, high-quality default RNG.
*
* @param a inclusive lower bound
* @param b exclusive upper bound; must satisfy {@code a < b}
* @throws IllegalArgumentException if {@code a >= b} or any bound is not finite
*/
public UniformDouble(double a, double b) {
this(Randoms.defaultGenerator(), a, b);
}
/**
* Constructs a UniformDouble(a, b) with a deterministic seed.
*
* @param seed random seed for reproducibility
* @param a inclusive lower bound
* @param b exclusive upper bound; must satisfy {@code a < b}
* @throws IllegalArgumentException if {@code a >= b} or any bound is not finite
*/
public UniformDouble(long seed, double a, double b) {
this(Randoms.seeded(seed), a, b);
}
/**
* Constructs a UniformDouble(a, b) using the provided generator.
*
* @param rng non-null random generator supplying independent U(0,1) variates
* @param a inclusive lower bound
* @param b exclusive upper bound; must satisfy {@code a < b}
* @throws NullPointerException if {@code rng} is null
* @throws IllegalArgumentException if {@code a >= b} or any bound is not finite
*/
public UniformDouble(RandomGenerator rng, double a, double b) {
if (rng == null) throw new NullPointerException("rng must not be null");
if (!Double.isFinite(a) || !Double.isFinite(b)) {
throw new IllegalArgumentException("Bounds must be finite");
}
if (!(a < b)) {
throw new IllegalArgumentException("Require a < b (got a=" + a + ", b=" + b + ")");
}
this.rng = rng;
this.a = a;
this.b = b;
this.width = b - a;
}
/** {@inheritDoc} */
@Override
public double sample() {
return a + rng.nextDouble() * width; // maps U[0,1) -> [a,b)
}
/** {@inheritDoc} */
@Override
public double pdf(double x) {
return (x >= a && x < b) ? (1.0 / width) : 0.0;
}
/** {@inheritDoc} */
@Override
public double cdf(double x) {
if (x <= a) return 0.0;
if (x >= b) return 1.0;
return (x - a) / width;
}
/** {@inheritDoc} */
@Override
public double quantile(double p) {
if (!(p >= 0.0 && p <= 1.0)) {
throw new IllegalArgumentException("p must be in [0,1], got " + p);
}
if (p == 0.0) return a;
if (p == 1.0) return b; // for [a,b) we return b at p=1 by convention
return a + p * width;
}
/** {@inheritDoc} */
@Override
public double mean() {
return 0.5 * (a + b);
}
/** {@inheritDoc} */
@Override
public double variance() {
return (width * width) / 12.0;
}
/**
* Returns the mathematical support of this distribution: the half-open interval [a, b).
*
* @return a {@link DistributionSupport} describing {@code [a, b)}
*/
public DistributionSupport support() {
return DistributionSupport.continuous(a, /* lowerClosed= */ true, b, /* upperClosed= */ false);
}
/**
* The inclusive lower bound {@code a}.
*
* @return the lower bound value {@code a}
*/
public double lowerBound() {
return a;
}
/**
* The exclusive upper bound {@code b}.
*
* @return the upper bound value {@code b}
*/
public double upperBound() {
return b;
}
}