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;
- }
- }