UniformLongSampler.java
/*
* 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 final 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 final 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 and the range is
* non-zero.
*/
private static final class SmallRangeUniformLongSampler extends UniformLongSampler {
/** Maximum range of the sample (exclusive). */
private final long n;
/** Limit of the uniform range (inclusive) to sample a positive long.
* This is the largest positive multiple of {@code n} minus 1:
* {@code floor(2^63 / n) * n - 1}.
* The -1 changes the limit to an inclusive bound and allows support
* for a power of 2 range. */
private final long limit;
/**
* @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;
// Set the upper limit for the positive long.
// The sample must be selected from the largest multiple
// of 'range' that fits within a positive value:
// limit = floor(2^63 / range) * range
// = 2^63 - (2^63 % range)
// Sample:
// X in [0, limit) or X in [0, limit - 1]
// return X % range
// This is a one-off computation cost.
// The divide will truncate towards zero (do not use Math.floorDiv).
// Note: This is invalid if range is not strictly positive.
limit = (Long.MIN_VALUE / range) * -range - 1;
}
/**
* @param rng Generator of uniformly distributed random numbers.
* @param source Source to copy.
*/
SmallRangeUniformLongSampler(UniformRandomProvider rng,
SmallRangeUniformLongSampler source) {
super(rng);
this.n = source.n;
this.limit = source.limit;
}
@Override
public long sample() {
// Note:
// This will have the same output as the rejection algorithm
// from o.a.c.rng.core.BaseProvider. The limit for the uniform
// positive value can be pre-computed. This ensures exactly
// 1 modulus operation per call.
for (;;) {
// bits in [0, limit]
final long bits = rng.nextLong() >>> 1;
if (bits <= limit) {
return bits % n;
}
}
}
@Override
public UniformLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
return new SmallRangeUniformLongSampler(rng, this);
}
}
/**
* Discrete uniform distribution sampler when the range between lower and upper is too large
* to fit in a positive long.
*/
private static final 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 final 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;
}
}