/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 1999-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.math;
import org.geotoolkit.lang.Static;
import org.apache.sis.util.Numbers;
import org.geotoolkit.resources.Errors;
/**
* Simple mathematical functions in addition to the ones provided in {@link Math}.
*
* @author Martin Desruisseaux (MPO, IRD, Geomatys)
* @author Thomas Rouby (Geomatys)
* @version 3.20
*
* @since 1.0
* @module
*/
public final class XMath extends Static {
/**
* Do not allow instantiation of this class.
*/
private XMath() {
}
/**
* Returns the number adjacent to the given value, as one of the nearest representable numbers
* of the given type. First this method selects the nearest adjacent value in the direction of
* positive infinity if {@code amount} is positive, or in the direction of negative infinity if
* {@code amount} is negative. Then this operation is repeated as many time as the absolute value
* of {@code amount}. More specifically:
*
* <ul>
* <li><p>If {@code type} is an integer type ({@link Integer}, {@link Short}, <i>etc.</i>),
* then this method returns {@code value + amount}. If {@code value} had a fractional part,
* then this part is truncated before the addition is performed.</p></li>
*
* <li><p>If {@code type} is {@link Double}, then this method is equivalent to invoking
* <code>{@linkplain Math#nextUp(double) Math.nextUp}(value)</code> if {@code amount}
* is positive, or {@code -Math.nextUp(-value)} if {@code amount} is negative, and to
* repeat this operation {@code abs(amount)} times.</p></li>
*
* <li><p>If {@code type} is {@link Float}, then this method is equivalent to invoking
* <code>{@linkplain Math#nextUp(float) Math.nextUp}((float) value)</code> if {@code amount}
* is positive, or {@code -Math.nextUp((float) -value)} if {@code amount} is negative,
* and to repeat this operation {@code abs(amount)} times.</p></li>
* </ul>
*
* @param type The type. Should be the class of {@link Double}, {@link Float},
* {@link Long}, {@link Integer}, {@link Short} or {@link Byte}.
* @param value The number for which to find an adjacent number.
* @param amount -1 to return the previous representable number,
* +1 to return the next representable number,
* or a multiple of the above.
* @return One of previous or next representable number as a {@code double}.
* @throws IllegalArgumentException if {@code type} is not one of supported types.
*/
public static double adjacentForType(final Class<? extends Number> type, double value, int amount)
throws IllegalArgumentException
{
if (Numbers.isInteger(type)) {
if (amount == 0) {
return Math.rint(value);
} else if (amount > 0) {
value = Math.floor(value);
} else {
value = Math.ceil(value);
}
return value + amount;
}
final boolean down = amount < 0;
if (down) {
amount = -amount;
value = -value;
}
if (type == Double.class) {
while (--amount >= 0) {
value = Math.nextUp(value);
}
} else if (type == Float.class) {
float vf = (float) value;
while (--amount >= 0) {
vf = Math.nextUp(vf);
}
value = vf;
} else {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedDataType_1, type));
}
if (down) {
value = -value;
}
return value;
}
/**
* Ensures that the specified value stay within the [-<var>bound</var> … <var>bound</var>] range.
* This method is typically invoked before to project geographic coordinates.
* It may add or subtract some amount of 2×<var>bound</var> from <var>x</var>.
*
* <p>The <var>bound</var> value is typically 180 if the longitude is express in degrees,
* or {@link Math#PI} it the longitude is express in radians. But it can also be some other value
* if the longitude has already been multiplied by a scale factor before this method is invoked.</p>
*
* @param x The longitude.
* @param bound The absolute value of the minimal and maximal allowed value, or
* {@link Double#POSITIVE_INFINITY} if no rolling should be applied.
* @return The longitude between ±<var>bound</var>.
*/
public static double roll(double x, final double bound) {
/*
* Note: we could do the same than the code below with this single line
* (assuming bound == PI):
*
* return x - (2*PI) * floor(x / (2*PI) + 0.5);
*
* However the code below tries to reduce the amount of floating point operations: only
* a division followed by a cast to (long) in the majority of cases. The multiplication
* happen only if there is effectively a rolling to apply. All the remaining operations
* are using integer arithmetic, so it should be fast.
*
* Note: usage of long instead of int is necessary, otherwise overflows do occur.
*/
long n = (long) (x / bound); // Really want rounding toward zero.
if (n != 0) {
if (n < 0) {
if ((n &= ~1) == -2) { // If odd number, decrement to the previous even number.
if (x == -bound) { // Special case for this one: don't rool to +180°.
return x;
}
}
} else if ((n & 1) != 0) {
n++; // If odd number, increment to the next even number.
}
x -= n * bound;
}
return x;
}
/**
* Clamps a value between min value and max value.
*
* @param val the value to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static int clamp(int val, int min, int max) {
return Math.min(Math.max(val, min), max);
}
/**
* Clamps each value of an array between min value and max value.
*
* @param val the array of values to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static int[] clamp(int[] val, int min, int max) {
final int[] ret = new int[val.length];
for (int i=0; i<val.length; i++) {
ret[i] = clamp(val[i], min, max);
}
return ret;
}
/**
* Clamps a value between min value and max value.
*
* @param val the value to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static long clamp(long val, long min, long max) {
return Math.min(Math.max(val, min), max);
}
/**
* Clamps each value of an array between min value and max value.
*
* @param val the array of values to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static long[] clamp(long[] val, long min, long max) {
final long[] ret = new long[val.length];
for (int i=0; i<val.length; i++) {
ret[i] = clamp(val[i], min, max);
}
return ret;
}
/**
* Clamps a value between min value and max value.
*
* @param val the value to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static float clamp(float val, float min, float max) {
return Math.min(Math.max(val, min), max);
}
/**
* Clamps each value of an array between min value and max value.
*
* @param val the array of values to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static float[] clamp(float[] val, float min, float max) {
final float[] ret = new float[val.length];
for (int i=0; i<val.length; i++) {
ret[i] = clamp(val[i], min, max);
}
return ret;
}
/**
* Clamps a value between min value and max value.
*
* @param val the value to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static double clamp(double val, double min, double max) {
return Math.min(Math.max(val, min), max);
}
/**
* Clamps each value of an array between min value and max value.
*
* @param val the array of values to clamp
* @param min the minimum value
* @param max the maximum value
* @return val clamped between min and max
*/
public static double[] clamp(double[] val, double min, double max) {
final double[] ret = new double[val.length];
for (int i=0; i<val.length; i++) {
ret[i] = clamp(val[i], min, max);
}
return ret;
}
}