blob: b7981030d981996c89482b401573c980e564f83e [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;
/**
* Support for {@link UniformRandomProvider} default methods.
*
* @since 1.5
*/
final class UniformRandomProviderSupport {
/** Message for an invalid stream size. */
private static final String INVALID_STREAM_SIZE = "Invalid stream size: ";
/** Message for an invalid upper bound (must be positive, finite and above zero). */
private static final String INVALID_UPPER_BOUND = "Upper bound must be above zero: ";
/** Message format for an invalid range for lower inclusive and upper exclusive. */
private static final String INVALID_RANGE = "Invalid range: [%s, %s)";
/** 2^32. */
private static final long POW_32 = 1L << 32;
/** No instances. */
private UniformRandomProviderSupport() {}
/**
* Validate the stream size.
*
* @param size Stream size.
* @throws IllegalArgumentException if {@code size} is negative.
*/
static void validateStreamSize(long size) {
if (size < 0) {
throw new IllegalArgumentException(INVALID_STREAM_SIZE + size);
}
}
/**
* Validate the upper bound.
*
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code bound} is equal to or less than zero.
*/
static void validateUpperBound(int bound) {
if (bound <= 0) {
throw new IllegalArgumentException(INVALID_UPPER_BOUND + bound);
}
}
/**
* Validate the upper bound.
*
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code bound} is equal to or less than zero.
*/
static void validateUpperBound(long bound) {
if (bound <= 0) {
throw new IllegalArgumentException(INVALID_UPPER_BOUND + bound);
}
}
/**
* Validate the upper bound.
*
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code bound} is equal to or less than zero, or
* is not finite
*/
static void validateUpperBound(float bound) {
// Negation of logic will detect NaN
if (!(bound > 0 && bound <= Float.MAX_VALUE)) {
throw new IllegalArgumentException(INVALID_UPPER_BOUND + bound);
}
}
/**
* Validate the upper bound.
*
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code bound} is equal to or less than zero, or
* is not finite
*/
static void validateUpperBound(double bound) {
// Negation of logic will detect NaN
if (!(bound > 0 && bound <= Double.MAX_VALUE)) {
throw new IllegalArgumentException(INVALID_UPPER_BOUND + bound);
}
}
/**
* Validate the range between the specified {@code origin} (inclusive) and the
* specified {@code bound} (exclusive).
*
* @param origin Lower bound on the random number to be returned.
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code origin} is greater than or equal to
* {@code bound}.
*/
static void validateRange(int origin, int bound) {
if (origin >= bound) {
throw new IllegalArgumentException(String.format(INVALID_RANGE, origin, bound));
}
}
/**
* Validate the range between the specified {@code origin} (inclusive) and the
* specified {@code bound} (exclusive).
*
* @param origin Lower bound on the random number to be returned.
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code origin} is greater than or equal to
* {@code bound}.
*/
static void validateRange(long origin, long bound) {
if (origin >= bound) {
throw new IllegalArgumentException(String.format(INVALID_RANGE, origin, bound));
}
}
/**
* Validate the range between the specified {@code origin} (inclusive) and the
* specified {@code bound} (exclusive).
*
* @param origin Lower bound on the random number to be returned.
* @param bound Upper bound (exclusive) on the random number to be returned.
* @throws IllegalArgumentException if {@code origin} is not finite, or {@code bound}
* is not finite, or {@code origin} is greater than or equal to {@code bound}.
*/
static void validateRange(double origin, double bound) {
if (origin >= bound || !Double.isFinite(origin) || !Double.isFinite(bound)) {
throw new IllegalArgumentException(String.format(INVALID_RANGE, origin, bound));
}
}
/**
* Checks if the sub-range from fromIndex (inclusive) to fromIndex + size (exclusive) is
* within the bounds of range from 0 (inclusive) to length (exclusive).
*
* <p>This function provides the functionality of
* {@code java.utils.Objects.checkFromIndexSize} introduced in JDK 9. The
* <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Objects.html#checkFromIndexSize(int,int,int)">Objects</a>
* javadoc has been reproduced for reference.
*
* <p>The sub-range is defined to be out of bounds if any of the following inequalities
* is true:
* <ul>
* <li>{@code fromIndex < 0}
* <li>{@code size < 0}
* <li>{@code fromIndex + size > length}, taking into account integer overflow
* <li>{@code length < 0}, which is implied from the former inequalities
* </ul>
*
* <p>Note: This is not an exact implementation of the functionality of
* {@code Objects.checkFromIndexSize}. The following changes have been made:
* <ul>
* <li>The method signature has been changed to avoid the return of {@code fromIndex};
* this value is not used within this package.
* <li>No checks are made for {@code length < 0} as this is assumed to be derived from
* an array length.
* </ul>
*
* @param fromIndex the lower-bound (inclusive) of the sub-interval
* @param size the size of the sub-range
* @param length the upper-bound (exclusive) of the range
* @throws IndexOutOfBoundsException if the sub-range is out of bounds
*/
static void validateFromIndexSize(int fromIndex, int size, int length) {
// check for any negatives (assume 'length' is positive array length),
// or overflow safe length check given the values are all positive
// remaining = length - fromIndex
if ((fromIndex | size) < 0 || size > length - fromIndex) {
throw new IndexOutOfBoundsException(
// Note: %<d is 'relative indexing' to re-use the last argument
String.format("Range [%d, %<d + %d) out of bounds for length %d",
fromIndex, size, length));
}
}
/**
* Generates random bytes and places them into a user-supplied array.
*
* <p>The array is filled with bytes extracted from random {@code long} values. This
* implies that the number of random bytes generated may be larger than the length of
* the byte array.
*
* @param source Source of randomness.
* @param bytes Array in which to put the generated bytes. Cannot be null.
* @param start Index at which to start inserting the generated bytes.
* @param len Number of bytes to insert.
*/
static void nextBytes(UniformRandomProvider source,
byte[] bytes, int start, int len) {
// Index of first insertion plus multiple of 8 part of length
// (i.e. length with 3 least significant bits unset).
final int indexLoopLimit = start + (len & 0x7ffffff8);
// Start filling in the byte array, 8 bytes at a time.
int index = start;
while (index < indexLoopLimit) {
final long random = source.nextLong();
bytes[index++] = (byte) random;
bytes[index++] = (byte) (random >>> 8);
bytes[index++] = (byte) (random >>> 16);
bytes[index++] = (byte) (random >>> 24);
bytes[index++] = (byte) (random >>> 32);
bytes[index++] = (byte) (random >>> 40);
bytes[index++] = (byte) (random >>> 48);
bytes[index++] = (byte) (random >>> 56);
}
// Index of last insertion + 1
final int indexLimit = start + len;
// Fill in the remaining bytes.
if (index < indexLimit) {
long random = source.nextLong();
for (;;) {
bytes[index++] = (byte) random;
if (index == indexLimit) {
break;
}
random >>>= 8;
}
}
}
/**
* Generates an {@code int} value between 0 (inclusive) and the specified value
* (exclusive).
*
* @param source Source of randomness.
* @param n Bound on the random number to be returned. Must be strictly positive.
* @return a random {@code int} value between 0 (inclusive) and {@code n} (exclusive).
*/
static int nextInt(UniformRandomProvider source,
int n) {
// Lemire (2019): Fast Random Integer Generation in an Interval
// https://arxiv.org/abs/1805.10941
long m = (source.nextInt() & 0xffffffffL) * n;
long l = m & 0xffffffffL;
if (l < n) {
// 2^32 % n
final long t = POW_32 % n;
while (l < t) {
m = (source.nextInt() & 0xffffffffL) * n;
l = m & 0xffffffffL;
}
}
return (int) (m >>> 32);
}
/**
* Generates an {@code int} value between the specified {@code origin} (inclusive) and
* the specified {@code bound} (exclusive).
*
* @param source Source of randomness.
* @param origin Lower bound on the random number to be returned.
* @param bound Upper bound (exclusive) on the random number to be returned. Must be
* above {@code origin}.
* @return a random {@code int} value between {@code origin} (inclusive) and
* {@code bound} (exclusive).
*/
static int nextInt(UniformRandomProvider source,
int origin, int bound) {
final int n = bound - origin;
if (n > 0) {
return nextInt(source, n) + origin;
}
// Range too large to fit in a positive integer.
// Use simple rejection.
int v = source.nextInt();
while (v < origin || v >= bound) {
v = source.nextInt();
}
return v;
}
/**
* Generates an {@code long} value between 0 (inclusive) and the specified value
* (exclusive).
*
* @param source Source of randomness.
* @param n Bound on the random number to be returned. Must be strictly positive.
* @return a random {@code long} value between 0 (inclusive) and {@code n}
* (exclusive).
*/
static long nextLong(UniformRandomProvider source,
long n) {
long bits;
long val;
do {
bits = source.nextLong() >>> 1;
val = bits % n;
} while (bits - val + (n - 1) < 0);
return val;
}
/**
* Generates a {@code long} value between the specified {@code origin} (inclusive) and
* the specified {@code bound} (exclusive).
*
* @param source Source of randomness.
* @param origin Lower bound on the random number to be returned.
* @param bound Upper bound (exclusive) on the random number to be returned. Must be
* above {@code origin}.
* @return a random {@code long} value between {@code origin} (inclusive) and
* {@code bound} (exclusive).
*/
static long nextLong(UniformRandomProvider source,
long origin, long bound) {
final long n = bound - origin;
if (n > 0) {
return nextLong(source, n) + origin;
}
// Range too large to fit in a positive integer.
// Use simple rejection.
long v = source.nextLong();
while (v < origin || v >= bound) {
v = source.nextLong();
}
return v;
}
/**
* Generates a {@code float} value between 0 (inclusive) and the specified value
* (exclusive).
*
* @param source Source of randomness.
* @param bound Bound on the random number to be returned. Must be strictly positive.
* @return a random {@code float} value between 0 (inclusive) and {@code bound}
* (exclusive).
*/
static float nextFloat(UniformRandomProvider source,
float bound) {
float v = source.nextFloat() * bound;
if (v >= bound) {
// Correct rounding
v = Math.nextDown(bound);
}
return v;
}
/**
* Generates a {@code float} value between the specified {@code origin} (inclusive)
* and the specified {@code bound} (exclusive).
*
* @param source Source of randomness.
* @param origin Lower bound on the random number to be returned. Must be finite.
* @param bound Upper bound (exclusive) on the random number to be returned. Must be
* above {@code origin} and finite.
* @return a random {@code float} value between {@code origin} (inclusive) and
* {@code bound} (exclusive).
*/
static float nextFloat(UniformRandomProvider source,
float origin, float bound) {
float v = source.nextFloat();
// This expression allows (bound - origin) to be infinite
// origin + (bound - origin) * v
// == origin - origin * v + bound * v
v = (1f - v) * origin + v * bound;
if (v >= bound) {
// Correct rounding
v = Math.nextDown(bound);
}
return v;
}
/**
* Generates a {@code double} value between 0 (inclusive) and the specified value
* (exclusive).
*
* @param source Source of randomness.
* @param bound Bound on the random number to be returned. Must be strictly positive.
* @return a random {@code double} value between 0 (inclusive) and {@code bound}
* (exclusive).
*/
static double nextDouble(UniformRandomProvider source,
double bound) {
double v = source.nextDouble() * bound;
if (v >= bound) {
// Correct rounding
v = Math.nextDown(bound);
}
return v;
}
/**
* Generates a {@code double} value between the specified {@code origin} (inclusive)
* and the specified {@code bound} (exclusive).
*
* @param source Source of randomness.
* @param origin Lower bound on the random number to be returned. Must be finite.
* @param bound Upper bound (exclusive) on the random number to be returned. Must be
* above {@code origin} and finite.
* @return a random {@code double} value between {@code origin} (inclusive) and
* {@code bound} (exclusive).
*/
static double nextDouble(UniformRandomProvider source,
double origin, double bound) {
double v = source.nextDouble();
// This expression allows (bound - origin) to be infinite
// origin + (bound - origin) * v
// == origin - origin * v + bound * v
v = (1f - v) * origin + v * bound;
if (v >= bound) {
// Correct rounding
v = Math.nextDown(bound);
}
return v;
}
}