blob: 28766ac9e205a4b159e2f409f90d11e443a4c96e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.rng.sampling.distribution;
import org.apache.commons.rng.UniformRandomProvider;
/**
* Discrete uniform distribution sampler generating values of type {@code long}.
*
* <p>Sampling uses {@link UniformRandomProvider#nextLong}.</p>
*
* <p>When the range is a power of two the number of calls is 1 per sample.
* Otherwise a rejection algorithm is used to ensure uniformity. In the worst
* case scenario where the range spans half the range of a {@code long}
* (2<sup>63</sup> + 1) the expected number of calls is 2 per sample.</p>
*
* @since 1.4
*/
public abstract class UniformLongSampler implements SharedStateLongSampler {
/** Underlying source of randomness. */
protected final UniformRandomProvider rng;
/**
* Discrete uniform distribution sampler when the sample value is fixed.
*/
private static class FixedUniformLongSampler extends UniformLongSampler {
/** The value. */
private final long value;
/**
* @param value The value.
*/
FixedUniformLongSampler(long value) {
// No requirement for the RNG
super(null);
this.value = value;
}
@Override
public long sample() {
return value;
}
@Override
public String toString() {
// No RNG to include in the string
return "Uniform deviate [X=" + value + "]";
}
@Override
public UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
// No requirement for the RNG
return this;
}
}
/**
* Discrete uniform distribution sampler when the range is a power of 2 and greater than 1.
* This sampler assumes the lower bound of the range is 0.
*
* <p>Note: This cannot be used when the range is 1 (2^0) as the shift would be 64-bits
* which is ignored by the shift operator.</p>
*/
private static class PowerOf2RangeUniformLongSampler extends UniformLongSampler {
/** Bit shift to apply to the long sample. */
private final int shift;
/**
* @param rng Generator of uniformly distributed random numbers.
* @param range Maximum range of the sample (exclusive).
* Must be a power of 2 greater than 2^0.
*/
PowerOf2RangeUniformLongSampler(UniformRandomProvider rng,
long range) {
super(rng);
this.shift = Long.numberOfLeadingZeros(range) + 1;
}
/**
* @param rng Generator of uniformly distributed random numbers.
* @param source Source to copy.
*/
PowerOf2RangeUniformLongSampler(UniformRandomProvider rng,
PowerOf2RangeUniformLongSampler source) {
super(rng);
this.shift = source.shift;
}
@Override
public long sample() {
// Use a bit shift to favour the most significant bits.
return rng.nextLong() >>> shift;
}
@Override
public UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
return new PowerOf2RangeUniformLongSampler(rng, this);
}
}
/**
* Discrete uniform distribution sampler when the range is small
* enough to fit in a positive long.
* This sampler assumes the lower bound of the range is 0.
*/
private static class SmallRangeUniformLongSampler extends UniformLongSampler {
/** Maximum range of the sample (exclusive). */
private final long n;
/**
* @param rng Generator of uniformly distributed random numbers.
* @param range Maximum range of the sample (exclusive).
*/
SmallRangeUniformLongSampler(UniformRandomProvider rng,
long range) {
super(rng);
this.n = range;
}
@Override
public long sample() {
// Rejection algorithm copied from o.a.c.rng.core.BaseProvider
// to avoid the (n <= 0) conditional.
// See the JDK javadoc for java.util.Random.nextInt(int) for
// a description of the algorithm.
long bits;
long val;
do {
bits = rng.nextLong() >>> 1;
val = bits % n;
} while (bits - val + (n - 1) < 0);
return val;
}
@Override
public UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
return new SmallRangeUniformLongSampler(rng, n);
}
}
/**
* Discrete uniform distribution sampler when the range between lower and upper is too large
* to fit in a positive long.
*/
private static class LargeRangeUniformLongSampler extends UniformLongSampler {
/** Lower bound. */
private final long lower;
/** Upper bound. */
private final long upper;
/**
* @param rng Generator of uniformly distributed random numbers.
* @param lower Lower bound (inclusive) of the distribution.
* @param upper Upper bound (inclusive) of the distribution.
*/
LargeRangeUniformLongSampler(UniformRandomProvider rng,
long lower,
long upper) {
super(rng);
this.lower = lower;
this.upper = upper;
}
@Override
public long sample() {
// Use a simple rejection method.
// This is used when (upper-lower) >= Long.MAX_VALUE.
// This will loop on average 2 times in the worst case scenario
// when (upper-lower) == Long.MAX_VALUE.
while (true) {
final long r = rng.nextLong();
if (r >= lower &&
r <= upper) {
return r;
}
}
}
@Override
public UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
return new LargeRangeUniformLongSampler(rng, lower, upper);
}
}
/**
* Adds an offset to an underlying discrete sampler.
*/
private static class OffsetUniformLongSampler extends UniformLongSampler {
/** The offset. */
private final long offset;
/** The long sampler. */
private final UniformLongSampler sampler;
/**
* @param offset The offset for the sample.
* @param sampler The discrete sampler.
*/
OffsetUniformLongSampler(long offset,
UniformLongSampler sampler) {
// No requirement for the RNG
super(null);
this.offset = offset;
this.sampler = sampler;
}
@Override
public long sample() {
return offset + sampler.sample();
}
@Override
public String toString() {
return sampler.toString();
}
@Override
public UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
return new OffsetUniformLongSampler(offset, sampler.withUniformRandomProvider(rng));
}
}
/**
* @param rng Generator of uniformly distributed random numbers.
*/
UniformLongSampler(UniformRandomProvider rng) {
this.rng = rng;
}
/** {@inheritDoc} */
@Override
public String toString() {
return "Uniform deviate [" + rng.toString() + "]";
}
/** {@inheritDoc} */
// Redeclare the signature to return a UniformLongSampler not a SharedStateLongSampler
@Override
public abstract UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng);
/**
* Creates a new discrete uniform distribution sampler.
*
* @param rng Generator of uniformly distributed random numbers.
* @param lower Lower bound (inclusive) of the distribution.
* @param upper Upper bound (inclusive) of the distribution.
* @return the sampler
* @throws IllegalArgumentException if {@code lower > upper}.
*/
public static UniformLongSampler of(UniformRandomProvider rng,
long lower,
long upper) {
if (lower > upper) {
throw new IllegalArgumentException(lower + " > " + upper);
}
// Choose the algorithm depending on the range
// Edge case for no range.
// This must be done first as the methods to handle lower == 0
// do not handle upper == 0.
if (upper == lower) {
return new FixedUniformLongSampler(lower);
}
// Algorithms to ignore the lower bound if it is zero.
if (lower == 0) {
return createZeroBoundedSampler(rng, upper);
}
final long range = (upper - lower) + 1;
// Check power of 2 first to handle range == 2^63.
if (isPowerOf2(range)) {
return new OffsetUniformLongSampler(lower,
new PowerOf2RangeUniformLongSampler(rng, range));
}
if (range <= 0) {
// The range is too wide to fit in a positive long (larger
// than 2^63); use a simple rejection method.
// Note: if range == 0 then the input is [Long.MIN_VALUE, Long.MAX_VALUE].
// No specialisation exists for this and it is handled as a large range.
return new LargeRangeUniformLongSampler(rng, lower, upper);
}
// Use a sample from the range added to the lower bound.
return new OffsetUniformLongSampler(lower,
new SmallRangeUniformLongSampler(rng, range));
}
/**
* Create a new sampler for the range {@code 0} inclusive to {@code upper} inclusive.
*
* <p>This can handle any positive {@code upper}.
*
* @param rng Generator of uniformly distributed random numbers.
* @param upper Upper bound (inclusive) of the distribution. Must be positive.
* @return the sampler
*/
private static UniformLongSampler createZeroBoundedSampler(UniformRandomProvider rng,
long upper) {
// Note: Handle any range up to 2^63 (which is negative as a signed
// 64-bit long but handled as a power of 2)
final long range = upper + 1;
return isPowerOf2(range) ?
new PowerOf2RangeUniformLongSampler(rng, range) :
new SmallRangeUniformLongSampler(rng, range);
}
/**
* Checks if the value is a power of 2.
*
* <p>This returns {@code true} for the value {@code Long.MIN_VALUE} which can be
* handled as an unsigned long of 2^63.</p>
*
* @param value Value.
* @return {@code true} if a power of 2
*/
private static boolean isPowerOf2(final long value) {
return value != 0 && (value & (value - 1)) == 0;
}
}