package org.magenta.random; import java.util.Random; import com.google.common.collect.BoundType; import com.google.common.collect.Range; /** * Helper class that generates randomly selected double. The algorithm is weak when generating high values, as they tend to the * upper limit, that is why the default "all" range is limited between -1.0E7 and 1.0E7. * * @author ngagnon * */ public class RandomDouble { public static final int DEFAULT_NUMBER_OF_DECIMAL_PLACES = 10; private static final double SCALE = 10D; private Random random; private double scale; private double lowestIncrement; private int numberOfDecimalPlaces; private Range<Double> constraint; private static final Range<Double> ALL = Range.all(); private static final Range<Double> EVERY_POSITIVE_BUT_ZERO = Range.greaterThan(0D); private static final Range<Double> EVERY_POSITIVE_INCLUDING_ZERO = Range.atLeast(0D); private static final Range<Double> EVERY_NEGATIVE_BUT_ZERO = Range.lessThan(0D); private static final Range<Double> EVERY_NEGATIVE_INCLUDING_ZERO = Range.atMost(0D); /** * Default constructor. * * @param random * a random */ public RandomDouble(Random random) { this(random, DEFAULT_NUMBER_OF_DECIMAL_PLACES, ALL); } /** * @param random * a random * @param numberOfDecimalPlaces * the desired number of decimal places in the generated doubles */ public RandomDouble(Random random, int numberOfDecimalPlaces) { this(random, numberOfDecimalPlaces, ALL); } /** * @param random * a random * @param numberOfDecimalPlaces * the desired number of decimal places in the generated doubles * @param constraint * a range of double from which to randomly select */ public RandomDouble(Random random, int numberOfDecimalPlaces, Range<Double> constraint) { this.numberOfDecimalPlaces = numberOfDecimalPlaces; this.random = random; this.constraint = constraint; this.scale = (Math.pow(SCALE, numberOfDecimalPlaces)); this.lowestIncrement = 1.0D / scale; } /** * Return any double in the intersection of the specified range and this class * own defined range. * * @param range * the desired range * @return a random double */ public double any(final Range<Double> range) { Range<Double> r = range.intersection(constraint); if (r.isEmpty()) { throw new IllegalStateException(String.format( "The intersection of the passed in range %s and this class constrained range %s result in a empty range", range, constraint)); } double upperBound = r.hasUpperBound() ? r.upperEndpoint() : 1.0E7D; double lowerBound = r.hasLowerBound() ? r.lowerEndpoint() : -1.0E7D; if (r.hasUpperBound() && BoundType.CLOSED == r.upperBoundType()) { upperBound = upperBound + lowestIncrement; } if (r.hasLowerBound() && BoundType.OPEN == r.lowerBoundType()) { lowerBound = lowerBound + lowestIncrement; } if(Double.isInfinite(lowerBound * scale)){ lowerBound = lowerBound / scale; } if(Double.isInfinite(upperBound * scale)){ upperBound = upperBound / scale; } double anyDoubleRange; if (lowerBound < 0 && upperBound > 0) { // special case double lowerRandomDouble = (Math.floor(this.random.nextDouble() * (-lowerBound) * scale) / scale) + lowerBound; double upperRandomDouble = (Math.floor(this.random.nextDouble() * upperBound * scale) / scale); anyDoubleRange = lowerRandomDouble + upperRandomDouble; } else { double delta = Math.abs(upperBound - lowerBound); anyDoubleRange = (Math.floor(this.random.nextDouble() * delta * scale) / scale) + lowerBound; } return anyDoubleRange; } /** * Return any double within this class defined double range. * * @return a random double */ public double any() { return any(ALL); } /** * Return any positive double within this class defined double range. * * @return a random double */ public double anyPositive() { return any(EVERY_POSITIVE_INCLUDING_ZERO); } /** * Return any positive double, except zero, within this class defined double * range. * * @return a random double */ public double anyPositiveButZero() { return any(EVERY_POSITIVE_BUT_ZERO); } /** * Return any positive double within this class defined double range and * having a max value of <code>max</code>. * * @param max * the maximum * @return a random double */ public double anyPositive(double max) { return any(Range.closedOpen(0D, max)); } /** * Return any positive double, except zero, within this class defined double * range and having a max value of <code>max</code>. * * @param max * the maximum * @return a random double */ public double anyPositiveButZero(double max) { return any(Range.open(0D, max)); } /** * Return any negative double within this class defined double range. * * @return a random double */ public double anyNegative() { return any(EVERY_NEGATIVE_INCLUDING_ZERO); } /** * Return any negative double, except zero, within this class defined double * range. * * @return a random double */ public double anyNegativeButZero() { return any(EVERY_NEGATIVE_BUT_ZERO); } /** * Return any negative double within this class defined double range and * having a minimum value of <code>min</code>. * * @param min * the minimum * @return a random double */ public double anyNegative(double min) { return any(Range.openClosed(min, 0D)); } /** * Return any negative double, except zero, within this class defined double * range and having a minimum value of <code>min</code>. * * @param min * the minimum * @return a random double */ public double anyNegativeButZero(double min) { return any(Range.open(min, 0D)); } /** * @param numberOfDecimalPlaces the number of decimals * @return a new instance */ public RandomDouble numberOfDecimalPlaces(int numberOfDecimalPlaces) { return new RandomDouble(random, numberOfDecimalPlaces, constraint); } /** * Constraint using the intersection of the specified range with this class own range. * * @param constraint the desired range. * @return a new instance */ public RandomDouble constraint(Range<Double> constraint) { return new RandomDouble(random, numberOfDecimalPlaces, this.constraint.intersection(constraint)); } }