package net.sf.openrocket.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MathUtil {
private static final Logger log = LoggerFactory.getLogger(MathUtil.class);
public static final double EPSILON = 0.00000001; // 10mm^3 in m^3
/**
* The square of x (x^2). On Sun's JRE using this method is as fast as typing x*x.
* @param x x
* @return x^2
*/
public static double pow2(double x) {
return x * x;
}
/**
* The cube of x (x^3).
* @param x x
* @return x^3
*/
public static double pow3(double x) {
return x * x * x;
}
public static double pow4(double x) {
return (x * x) * (x * x);
}
/**
* Clamps the value x to the range min - max.
* @param x Original value.
* @param min Minimum value to return.
* @param max Maximum value to return.
* @return The clamped value.
*/
public static double clamp(double x, double min, double max) {
if (x < min)
return min;
if (x > max)
return max;
return x;
}
public static float clamp(float x, float min, float max) {
if (x < min)
return min;
if (x > max)
return max;
return x;
}
public static int clamp(int x, int min, int max) {
if (x < min)
return min;
if (x > max)
return max;
return x;
}
/**
* Maps a value from one value range to another.
*
* @param value the value to map.
* @param fromMin the minimum of the starting range.
* @param fromMax the maximum of the starting range.
* @param toMin the minimum of the destination range.
* @param toMax the maximum of the destination range.
* @return the mapped value.
* @throws IllegalArgumentException if fromMin == fromMax, but toMin != toMax.
*/
public static double map(double value, double fromMin, double fromMax,
double toMin, double toMax) {
if (equals(toMin, toMax))
return toMin;
if (equals(fromMin, fromMax)) {
throw new IllegalArgumentException("from range is singular and to range is not: " +
"value=" + value + " fromMin=" + fromMin + " fromMax=" + fromMax +
"toMin=" + toMin + " toMax=" + toMax);
}
return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin;
}
/**
* Maps a coordinate from one value range to another.
*
* @param value the value to map.
* @param fromMin the minimum of the starting range.
* @param fromMax the maximum of the starting range.
* @param toMin the minimum coordinate of the destination;
* @param toMax the maximum coordinate of the destination;
* @return the mapped value.
* @throws IllegalArgumentException if fromMin == fromMax, but toMin != toMax.
*/
public static Coordinate map(double value, double fromMin, double fromMax,
Coordinate toMin, Coordinate toMax) {
if (toMin.equals(toMax))
return toMin;
if (equals(fromMin, fromMax)) {
throw new IllegalArgumentException("from range is singular and to range is not: " +
"value=" + value + " fromMin=" + fromMin + " fromMax=" + fromMax +
"toMin=" + toMin + " toMax=" + toMax);
}
double a = (value - fromMin) / (fromMax - fromMin);
return toMax.multiply(a).add(toMin.multiply(1 - a));
}
/**
* Compute the minimum of two values. This is performed by direct comparison.
* However, if one of the values is NaN and the other is not, the non-NaN value is
* returned.
*/
public static double min(double x, double y) {
if (Double.isNaN(y))
return x;
return (x < y) ? x : y;
}
/**
* Compute the maximum of two values. This is performed by direct comparison.
* However, if one of the values is NaN and the other is not, the non-NaN value is
* returned.
*/
public static double max(double x, double y) {
if (Double.isNaN(x))
return y;
return (x < y) ? y : x;
}
/**
* Compute the minimum of three values. This is performed by direct comparison.
* However, if one of the values is NaN and the other is not, the non-NaN value is
* returned.
*/
public static double min(double x, double y, double z) {
if (x < y || Double.isNaN(y)) {
return min(x, z);
} else {
return min(y, z);
}
}
/**
* Compute the minimum of three values. This is performed by direct comparison.
* However, if one of the values is NaN and the other is not, the non-NaN value is
* returned.
*/
public static double min(double w, double x, double y, double z) {
return min(min(w, x), min(y, z));
}
/**
* Compute the maximum of three values. This is performed by direct comparison.
* However, if one of the values is NaN and the other is not, the non-NaN value is
* returned.
*/
public static double max(double x, double y, double z) {
if (x > y || Double.isNaN(y)) {
return max(x, z);
} else {
return max(y, z);
}
}
/**
* Calculates the hypotenuse <code>sqrt(x^2+y^2)</code>. This method is SIGNIFICANTLY
* faster than <code>Math.hypot(x,y)</code>.
*/
public static double hypot(double x, double y) {
return Math.sqrt(x * x + y * y);
}
/**
* Reduce the angle x to the range 0 - 2*PI.
* @param x Original angle.
* @return The equivalent angle in the range 0 ... 2*PI.
*/
public static double reduce360(double x) {
double d = Math.floor(x / (2 * Math.PI));
return x - d * 2 * Math.PI;
}
/**
* Reduce the angle x to the range -PI - PI.
*
* Either -PI and PI might be returned, depending on the rounding function.
*
* @param x Original angle.
* @return The equivalent angle in the range -PI ... PI.
*/
public static double reduce180(double x) {
double d = Math.rint(x / (2 * Math.PI));
return x - d * 2 * Math.PI;
}
/**
* Return the square root of a value. If the value is negative, zero is returned.
* This is safer in cases where rounding errors might make a value slightly negative.
*
* @param d the value of which the square root is to be taken.
* @return the square root of the value.
*/
public static double safeSqrt(double d) {
if (d < 0) {
if (d < 0.01) {
log.warn("Attempting to compute sqrt(" + d + ")");
}
return 0;
}
return Math.sqrt(d);
}
public static boolean equals(double a, double b, double epsilon) {
double absb = Math.abs(b);
if (absb < epsilon / 2) {
// Near zero
return Math.abs(a) < epsilon / 2;
}
return Math.abs(a - b) < epsilon * absb;
}
public static boolean equals(double a, double b) {
return equals(a, b, EPSILON);
}
/**
* Return the sign of the number. This corresponds to Math.signum, but ignores
* the special cases of zero and NaN. The value returned for those is arbitrary.
* <p>
* This method is about 4 times faster than Math.signum().
*
* @param x the checked value.
* @return -1.0 if x<0; 1.0 if x>0; otherwise either -1.0 or 1.0.
*/
public static double sign(double x) {
return (x < 0) ? -1.0 : 1.0;
}
/* Math.abs() is about 3x as fast as this:
public static double abs(double x) {
return (x<0) ? -x : x;
}
*/
public static double average(Collection<? extends Number> values) {
if (values.isEmpty()) {
return Double.NaN;
}
double avg = 0.0;
int count = 0;
for (Number n : values) {
avg += n.doubleValue();
count++;
}
return avg / count;
}
public static double stddev(Collection<? extends Number> values) {
if (values.size() < 2) {
return Double.NaN;
}
double avg = average(values);
double stddev = 0.0;
int count = 0;
for (Number n : values) {
stddev += pow2(n.doubleValue() - avg);
count++;
}
stddev = Math.sqrt(stddev / (count - 1));
return stddev;
}
public static double median(Collection<? extends Number> values) {
if (values.isEmpty()) {
return Double.NaN;
}
List<Number> sorted = new ArrayList<Number>(values);
Collections.sort(sorted, new Comparator<Number>() {
@Override
public int compare(Number o1, Number o2) {
return Double.compare(o1.doubleValue(), o2.doubleValue());
}
});
int n = sorted.size();
if (n % 2 == 0) {
return (sorted.get(n / 2).doubleValue() + sorted.get(n / 2 - 1).doubleValue()) / 2;
} else {
return sorted.get(n / 2).doubleValue();
}
}
/**
* Use interpolation to determine the value of the function at point t.
* Current implementation uses simple linear interpolation. The domain
* and range lists must include the same number of values, t must be within
* the domain, and the domain list must be sorted.
*
* @param domain list containing domain samples
* @param range list of corresponding range samples
* @param t domain value at which to interpolate
* @return returns Double.NaN if either list is null or empty or different size, or if t is outsize the domain.
*/
public static double interpolate(List<Double> domain, List<Double> range, double t) {
if (domain == null || range == null || domain.size() != range.size()) {
return Double.NaN;
}
int length = domain.size();
if (length <= 1 || t < domain.get(0) || t > domain.get(length - 1)) {
return Double.NaN;
}
// Look for the index of the right end point.
int right = 1;
while (t > domain.get(right)) {
right++;
}
int left = right - 1;
// Points are:
double deltax = domain.get(right) - domain.get(left);
double deltay = range.get(right) - range.get(left);
// For numerical stability, if deltax is small,
if (Math.abs(deltax) < EPSILON) {
if (deltay < -1.0 * EPSILON) {
// return neg infinity if deltay is negative
return Double.NEGATIVE_INFINITY;
}
else if (deltay > EPSILON) {
// return infinity if deltay is large
return Double.POSITIVE_INFINITY;
} else {
// otherwise return 0
return 0.0d;
}
}
return range.get(left) + (t - domain.get(left)) * deltay / deltax;
}
}