1 package net.sf.openrocket.util;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
9 import net.sf.openrocket.logging.LogHelper;
10 import net.sf.openrocket.startup.Application;
12 public class MathUtil {
13 private static final LogHelper log = Application.getLogger();
15 public static final double EPSILON = 0.00000001; // 10mm^3 in m^3
18 * The square of x (x^2). On Sun's JRE using this method is as fast as typing x*x.
22 public static double pow2(double x) {
27 * The cube of x (x^3).
31 public static double pow3(double x) {
35 public static double pow4(double x) {
36 return (x * x) * (x * x);
40 * Clamps the value x to the range min - max.
41 * @param x Original value.
42 * @param min Minimum value to return.
43 * @param max Maximum value to return.
44 * @return The clamped value.
46 public static double clamp(double x, double min, double max) {
54 public static float clamp(float x, float min, float max) {
62 public static int clamp(int x, int min, int max) {
72 * Maps a value from one value range to another.
74 * @param value the value to map.
75 * @param fromMin the minimum of the starting range.
76 * @param fromMax the maximum of the starting range.
77 * @param toMin the minimum of the destination range.
78 * @param toMax the maximum of the destination range.
79 * @return the mapped value.
80 * @throws IllegalArgumentException if fromMin == fromMax, but toMin != toMax.
82 public static double map(double value, double fromMin, double fromMax,
83 double toMin, double toMax) {
84 if (equals(toMin, toMax))
86 if (equals(fromMin, fromMax)) {
87 throw new IllegalArgumentException("from range is singular and to range is not: " +
88 "value=" + value + " fromMin=" + fromMin + " fromMax=" + fromMax +
89 "toMin=" + toMin + " toMax=" + toMax);
91 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin;
96 * Maps a coordinate from one value range to another.
98 * @param value the value to map.
99 * @param fromMin the minimum of the starting range.
100 * @param fromMax the maximum of the starting range.
101 * @param toMin the minimum coordinate of the destination;
102 * @param toMax the maximum coordinate of the destination;
103 * @return the mapped value.
104 * @throws IllegalArgumentException if fromMin == fromMax, but toMin != toMax.
106 public static Coordinate map(double value, double fromMin, double fromMax,
107 Coordinate toMin, Coordinate toMax) {
108 if (toMin.equals(toMax))
110 if (equals(fromMin, fromMax)) {
111 throw new IllegalArgumentException("from range is singular and to range is not: " +
112 "value=" + value + " fromMin=" + fromMin + " fromMax=" + fromMax +
113 "toMin=" + toMin + " toMax=" + toMax);
115 double a = (value - fromMin) / (fromMax - fromMin);
116 return toMax.multiply(a).add(toMin.multiply(1 - a));
121 * Compute the minimum of two values. This is performed by direct comparison.
122 * However, if one of the values is NaN and the other is not, the non-NaN value is
125 public static double min(double x, double y) {
128 return (x < y) ? x : y;
132 * Compute the maximum of two values. This is performed by direct comparison.
133 * However, if one of the values is NaN and the other is not, the non-NaN value is
136 public static double max(double x, double y) {
139 return (x < y) ? y : x;
143 * Compute the minimum of three values. This is performed by direct comparison.
144 * However, if one of the values is NaN and the other is not, the non-NaN value is
147 public static double min(double x, double y, double z) {
148 if (x < y || Double.isNaN(y)) {
158 * Compute the minimum of three values. This is performed by direct comparison.
159 * However, if one of the values is NaN and the other is not, the non-NaN value is
162 public static double min(double w, double x, double y, double z) {
163 return min(min(w, x), min(y, z));
168 * Compute the maximum of three values. This is performed by direct comparison.
169 * However, if one of the values is NaN and the other is not, the non-NaN value is
172 public static double max(double x, double y, double z) {
173 if (x > y || Double.isNaN(y)) {
181 * Calculates the hypotenuse <code>sqrt(x^2+y^2)</code>. This method is SIGNIFICANTLY
182 * faster than <code>Math.hypot(x,y)</code>.
184 public static double hypot(double x, double y) {
185 return Math.sqrt(x * x + y * y);
189 * Reduce the angle x to the range 0 - 2*PI.
190 * @param x Original angle.
191 * @return The equivalent angle in the range 0 ... 2*PI.
193 public static double reduce360(double x) {
194 double d = Math.floor(x / (2 * Math.PI));
195 return x - d * 2 * Math.PI;
199 * Reduce the angle x to the range -PI - PI.
201 * Either -PI and PI might be returned, depending on the rounding function.
203 * @param x Original angle.
204 * @return The equivalent angle in the range -PI ... PI.
206 public static double reduce180(double x) {
207 double d = Math.rint(x / (2 * Math.PI));
208 return x - d * 2 * Math.PI;
213 * Return the square root of a value. If the value is negative, zero is returned.
214 * This is safer in cases where rounding errors might make a value slightly negative.
216 * @param d the value of which the square root is to be taken.
217 * @return the square root of the value.
219 public static double safeSqrt(double d) {
222 log.warn(1, "Attempting to compute sqrt(" + d + ")");
231 public static boolean equals(double a, double b) {
232 double absb = Math.abs(b);
234 if (absb < EPSILON / 2) {
236 return Math.abs(a) < EPSILON / 2;
238 return Math.abs(a - b) < EPSILON * absb;
243 * Return the sign of the number. This corresponds to Math.signum, but ignores
244 * the special cases of zero and NaN. The value returned for those is arbitrary.
246 * This method is about 4 times faster than Math.signum().
248 * @param x the checked value.
249 * @return -1.0 if x<0; 1.0 if x>0; otherwise either -1.0 or 1.0.
251 public static double sign(double x) {
252 return (x < 0) ? -1.0 : 1.0;
255 /* Math.abs() is about 3x as fast as this:
257 public static double abs(double x) {
258 return (x<0) ? -x : x;
263 public static double average(Collection<? extends Number> values) {
264 if (values.isEmpty()) {
270 for (Number n : values) {
271 avg += n.doubleValue();
277 public static double stddev(Collection<? extends Number> values) {
278 if (values.size() < 2) {
282 double avg = average(values);
285 for (Number n : values) {
286 stddev += pow2(n.doubleValue() - avg);
289 stddev = Math.sqrt(stddev / (count - 1));
293 public static double median(Collection<? extends Number> values) {
294 if (values.isEmpty()) {
298 List<Number> sorted = new ArrayList<Number>(values);
299 Collections.sort(sorted, new Comparator<Number>() {
301 public int compare(Number o1, Number o2) {
302 return Double.compare(o1.doubleValue(), o2.doubleValue());
306 int n = sorted.size();
308 return (sorted.get(n / 2).doubleValue() + sorted.get(n / 2 - 1).doubleValue()) / 2;
310 return sorted.get(n / 2).doubleValue();
315 * Use interpolation to determine the value of the function at point t.
316 * Current implementation uses simple linear interpolation. The domain
317 * and range lists must include the same number of values, t must be within
318 * the domain, and the domain list must be sorted.
320 * @param domain list containing domain samples
321 * @param range list of corresponding range samples
322 * @param t domain value at which to interpolate
323 * @return returns Double.NaN if either list is null or empty or different size, or if t is outsize the domain.
325 public static double interpolate( List<Double> domain, List<Double> range, double t ) {
327 if ( domain == null || range == null || domain.size() != range.size() ) {
331 int length = domain.size();
332 if ( length <= 1 || t < domain.get(0) || t > domain.get( length-1 ) ) {
336 // Look for the index of the right end point.
338 while( t > domain.get(right) ) {
345 double deltax = domain.get(right) - domain.get(left);
346 double deltay = range.get(right) - range.get(left);
348 // For numerical stability, if deltax is small,
349 if ( Math.abs(deltax) < EPSILON ) {
350 if ( deltay < -1.0 * EPSILON ) {
351 // return neg infinity if deltay is negative
352 return Double.NEGATIVE_INFINITY;
354 else if ( deltay > EPSILON ) {
355 // return infinity if deltay is large
356 return Double.POSITIVE_INFINITY;
358 // otherwise return 0
363 return range.get(left) + ( t - domain.get(left) ) * deltay / deltax;