c88e85853562bc5907f64bfb4654d329b2140128
[debian/openrocket] / core / src / net / sf / openrocket / util / MathUtil.java
1 package net.sf.openrocket.util;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.List;
8
9 import net.sf.openrocket.logging.LogHelper;
10 import net.sf.openrocket.startup.Application;
11
12 public class MathUtil {
13         private static final LogHelper log = Application.getLogger();
14
15         public static final double EPSILON = 0.00000001; // 10mm^3 in m^3
16
17         /**
18          * The square of x (x^2).  On Sun's JRE using this method is as fast as typing x*x. 
19          * @param x  x
20          * @return   x^2
21          */
22         public static double pow2(double x) {
23                 return x * x;
24         }
25
26         /**
27          * The cube of x (x^3).
28          * @param x  x
29          * @return   x^3
30          */
31         public static double pow3(double x) {
32                 return x * x * x;
33         }
34
35         public static double pow4(double x) {
36                 return (x * x) * (x * x);
37         }
38
39         /**
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.
45          */
46         public static double clamp(double x, double min, double max) {
47                 if (x < min)
48                         return min;
49                 if (x > max)
50                         return max;
51                 return x;
52         }
53
54         public static float clamp(float x, float min, float max) {
55                 if (x < min)
56                         return min;
57                 if (x > max)
58                         return max;
59                 return x;
60         }
61
62         public static int clamp(int x, int min, int max) {
63                 if (x < min)
64                         return min;
65                 if (x > max)
66                         return max;
67                 return x;
68         }
69
70
71         /**
72          * Maps a value from one value range to another.
73          * 
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.
81          */
82         public static double map(double value, double fromMin, double fromMax,
83                         double toMin, double toMax) {
84                 if (equals(toMin, toMax))
85                         return toMin;
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);
90                 }
91                 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin;
92         }
93
94
95         /**
96          * Maps a coordinate from one value range to another.
97          * 
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.
105          */
106         public static Coordinate map(double value, double fromMin, double fromMax,
107                         Coordinate toMin, Coordinate toMax) {
108                 if (toMin.equals(toMax))
109                         return toMin;
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);
114                 }
115                 double a = (value - fromMin) / (fromMax - fromMin);
116                 return toMax.multiply(a).add(toMin.multiply(1 - a));
117         }
118
119
120         /**
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
123          * returned.
124          */
125         public static double min(double x, double y) {
126                 if (Double.isNaN(y))
127                         return x;
128                 return (x < y) ? x : y;
129         }
130
131         /**
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
134          * returned.
135          */
136         public static double max(double x, double y) {
137                 if (Double.isNaN(x))
138                         return y;
139                 return (x < y) ? y : x;
140         }
141
142         /**
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
145          * returned.
146          */
147         public static double min(double x, double y, double z) {
148                 if (x < y || Double.isNaN(y)) {
149                         return min(x, z);
150                 } else {
151                         return min(y, z);
152                 }
153         }
154
155
156
157         /**
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
160          * returned.
161          */
162         public static double min(double w, double x, double y, double z) {
163                 return min(min(w, x), min(y, z));
164         }
165
166
167         /**
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
170          * returned.
171          */
172         public static double max(double x, double y, double z) {
173                 if (x > y || Double.isNaN(y)) {
174                         return max(x, z);
175                 } else {
176                         return max(y, z);
177                 }
178         }
179
180         /**
181          * Calculates the hypotenuse <code>sqrt(x^2+y^2)</code>.  This method is SIGNIFICANTLY
182          * faster than <code>Math.hypot(x,y)</code>.
183          */
184         public static double hypot(double x, double y) {
185                 return Math.sqrt(x * x + y * y);
186         }
187
188         /**
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.
192          */
193         public static double reduce360(double x) {
194                 double d = Math.floor(x / (2 * Math.PI));
195                 return x - d * 2 * Math.PI;
196         }
197
198         /**
199          * Reduce the angle x to the range -PI - PI.
200          * 
201          * Either -PI and PI might be returned, depending on the rounding function. 
202          * 
203          * @param x  Original angle.
204          * @return   The equivalent angle in the range -PI ... PI.
205          */
206         public static double reduce180(double x) {
207                 double d = Math.rint(x / (2 * Math.PI));
208                 return x - d * 2 * Math.PI;
209         }
210
211
212         /**
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.
215          * 
216          * @param d             the value of which the square root is to be taken.
217          * @return              the square root of the value.
218          */
219         public static double safeSqrt(double d) {
220                 if (d < 0) {
221                         if (d < 0.01) {
222                                 log.warn(1, "Attempting to compute sqrt(" + d + ")");
223                         }
224                         return 0;
225                 }
226                 return Math.sqrt(d);
227         }
228
229
230
231         public static boolean equals(double a, double b) {
232                 double absb = Math.abs(b);
233
234                 if (absb < EPSILON / 2) {
235                         // Near zero
236                         return Math.abs(a) < EPSILON / 2;
237                 }
238                 return Math.abs(a - b) < EPSILON * absb;
239         }
240
241
242         /**
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.
245          * <p>
246          * This method is about 4 times faster than Math.signum().
247          * 
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.
250          */
251         public static double sign(double x) {
252                 return (x < 0) ? -1.0 : 1.0;
253         }
254
255         /* Math.abs() is about 3x as fast as this:
256
257         public static double abs(double x) {
258                 return (x<0) ? -x : x;
259         }
260          */
261
262
263         public static double average(Collection<? extends Number> values) {
264                 if (values.isEmpty()) {
265                         return Double.NaN;
266                 }
267
268                 double avg = 0.0;
269                 int count = 0;
270                 for (Number n : values) {
271                         avg += n.doubleValue();
272                         count++;
273                 }
274                 return avg / count;
275         }
276
277         public static double stddev(Collection<? extends Number> values) {
278                 if (values.size() < 2) {
279                         return Double.NaN;
280                 }
281
282                 double avg = average(values);
283                 double stddev = 0.0;
284                 int count = 0;
285                 for (Number n : values) {
286                         stddev += pow2(n.doubleValue() - avg);
287                         count++;
288                 }
289                 stddev = Math.sqrt(stddev / (count - 1));
290                 return stddev;
291         }
292
293         public static double median(Collection<? extends Number> values) {
294                 if (values.isEmpty()) {
295                         return Double.NaN;
296                 }
297
298                 List<Number> sorted = new ArrayList<Number>(values);
299                 Collections.sort(sorted, new Comparator<Number>() {
300                         @Override
301                         public int compare(Number o1, Number o2) {
302                                 return Double.compare(o1.doubleValue(), o2.doubleValue());
303                         }
304                 });
305
306                 int n = sorted.size();
307                 if (n % 2 == 0) {
308                         return (sorted.get(n / 2).doubleValue() + sorted.get(n / 2 - 1).doubleValue()) / 2;
309                 } else {
310                         return sorted.get(n / 2).doubleValue();
311                 }
312         }
313
314         /**
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.
319          * 
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.
324          */
325         public static double interpolate( List<Double> domain, List<Double> range, double t ) {
326
327                 if ( domain == null || range == null || domain.size() != range.size() ) {
328                         return Double.NaN;
329                 }
330
331                 int length = domain.size();
332                 if ( length <= 1 || t < domain.get(0) || t > domain.get( length-1 ) ) {
333                         return Double.NaN;
334                 }
335
336                 // Look for the index of the right end point.
337                 int right = 1;
338                 while( t > domain.get(right) ) {
339                         right ++;
340                 }
341                 int left = right -1;
342
343                 // Points are:
344                 
345                 double deltax = domain.get(right) - domain.get(left);
346                 double deltay = range.get(right) - range.get(left);
347
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;
353                         }
354                         else if ( deltay > EPSILON ) {
355                                 // return infinity if deltay is large
356                                 return Double.POSITIVE_INFINITY;
357                         } else {
358                                 // otherwise return 0
359                                 return 0.0d;
360                         }
361                 }
362                 
363                 return  range.get(left) + ( t - domain.get(left) ) * deltay / deltax;
364
365         }
366
367 }