/*
* ExperienceMod - Bukkit server plugin for modifying the experience system in Minecraft.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.xp;
import java.util.Random;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
public class SampleRange {
private double start;
private double end;
public static SampleRange DEFAULT = new SampleRange(0);
/**
* Constructs a range of the form [value, value].
* @param value The end value.
*/
public SampleRange(double value) {
this.start = value;
this.end = value;
}
public SampleRange(double start, double end) {
if (end < start)
throw new IllegalArgumentException("Illegal range. The first value must be less than the last.");
this.start = start;
this.end = end;
}
public double getStart() {
return start;
}
public double getEnd() {
return end;
}
public SampleRange multiply(double multiply) {
// Consider the average value of a uniform distribution
return new SampleRange(start * multiply, end * multiply);
}
public double sampleDouble(Random rnd) {
if (start == end)
return start;
else
return start + (end - start) * rnd.nextDouble();
}
public int getMinimum() {
return (int) Math.floor(start);
}
public int getMaximum() {
return (int) Math.ceil(end);
}
public int sampleInt(Random rnd) {
/*
* Imagine our range is 0.7 - 5.3:
*
* 0.7 1 2 3 4 5 5.3
*
* |---|----------|----------|----------|----------|---|
* | | | | | | |
* | | | | | | |
* |---|----------|----------|----------|----------|---|
*
* The integer part, 1 - 5, is easy. To get a random number between and
* including 1 and 5, we simply get a random number between 0 and 4
* and add one.
*
* The beginning, 0.7 - 1.0, covers 30% of an integer. One interpretation is
* that this indicates the probability of getting that integer.
*
* So, we end up with a 30% probability of getting 0 and 5.3 - 5 = 30%
* probability of getting 4.
*/
int value = 0;
// Convert the range to an integer equivalent.
// Notice that we round to shrink the range.
int a = (int) Math.ceil(start);
int b = (int) Math.floor(end);
// Special case
if ((int)start == (int)end) {
return sampleIntReduced(rnd);
}
// The decimal leftover
double dA = a - start;
double dB = end - b;
// Sample an integer from the range [a, b] (inclusive)
if (b > a) {
value = a + rnd.nextInt(b - a + 1); // Add one since nextInt is exclusive
}
// The remainder is the probability of choosing the previous value
if (dA > 0 && rnd.nextDouble() < dA)
value--;
// And here it is the probability of choosing the next value
if (dB > 0 && rnd.nextDouble() < dB)
value++;
return value;
}
private int sampleIntReduced(Random rnd) {
double value = sampleDouble(rnd);
// Probability of adding the fraction
double fraction = value - Math.floor(value);
double toAdd = rnd.nextDouble() < fraction ? Math.signum(value) : 0;
return (int)value + (int)toAdd;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31).
append(start).
append(end).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (obj.getClass() != getClass())
return false;
SampleRange other = (SampleRange) obj;
return new EqualsBuilder().
append(start, other.start).
append(end, other.end).
isEquals();
}
@Override
public String toString() {
if (start == end)
return "{" + start + "}";
else
return "{start: " + start + ", end: " + end + "}";
}
}