jaitools.numeric.NumberOperations Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt-all Show documentation
Show all versions of jt-all Show documentation
Provides a single jar containing all JAI-tools modules which you can
use instead of including individual modules in your project. Note:
It does not include the Jiffle scripting language or Jiffle image
operator.
The newest version!
/*
* Copyright 2009-2011 Michael Bedward
*
* This file is part of jai-tools.
*
* jai-tools 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, either version 3 of the
* License, or (at your option) any later version.
*
* jai-tools 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with jai-tools. If not, see .
*
*/
package jaitools.numeric;
import java.util.HashMap;
import java.util.Map;
/**
* Provides static methods to work directly with {@code Number} objects
* without having to cast them to narrower types.
*
* @author Michael Bedward
* @since 1.1
* @version $Id: NumberOperations.java 1504 2011-03-05 10:56:10Z michael.bedward $
*/
public class NumberOperations {
/** Default tolerance for float comparisons. */
public static final float DEFAULT_FLOAT_TOL = 1.0e-4f;
private static float floatTol = DEFAULT_FLOAT_TOL;
/* Default tolerance for double comparisons */
public static final double DEFAULT_DOUBLE_TOL = 1.0e-8f;
private static double doubleTol = DEFAULT_DOUBLE_TOL;
/**
* Information about the {@code Number} classes supported including
* their rank in terms of numeric precision.
*/
public enum ClassInfo {
/** Byte: rank 0, integral type */
BYTE(0, Byte.class, true),
/** Short: rank 1, integral type */
SHORT(1, Short.class, true),
/** Integer: rank 2, integral type */
INTEGER(2, Integer.class, true),
/** Long: rank 3, integral type */
LONG(3, Long.class, true),
/** Float: rank 4, non-integral type */
FLOAT(4, Float.class, false),
/** Double: rank 5, non-integral type */
DOUBLE(5, Double.class, false);
/** Map for reverse lookup by class */
private static final Map, ClassInfo> lookup;
static {
lookup = new HashMap, ClassInfo>();
for (ClassInfo cr : ClassInfo.values()) {
lookup.put(cr.clazz, cr);
}
}
private int rank;
private Class extends Number> clazz;
private boolean isIntegral;
/** Private constructor */
private ClassInfo(int rank, Class extends Number> clazz, boolean isIntegral) {
this.rank = rank;
this.clazz = clazz;
this.isIntegral = isIntegral;
}
/**
* Gets the relative rank of this type in terms of numeric precision.
*
* @return int value for rank
*/
public int getRank() {
return rank;
}
/**
* Gets the class that this type relates to
*
* @return the class
*/
public Class extends Number> getNumberClass() {
return clazz;
}
/**
* Tests if this is an integral type
*
* @return true if integral; false otherwise
*/
public boolean isIntegral() {
return isIntegral;
}
/**
* Gets a String representation of this type
*
* @return a String of the form "{@code ClassInfo}"
*/
@Override
public String toString() {
return String.format("ClassInfo<%s>", clazz.getSimpleName());
}
/**
* Gets the ClassInfo type for the given class
*
* @param clazz a Number class
*
* @return the ClassInfo type or {@code null} if the class is not
* recognized
*/
public static ClassInfo get(Class extends Number> clazz) {
return lookup.get(clazz);
}
}
/** Supported operations. */
private enum OpType {
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE,
COMPARE;
}
/**
* Adds two {@code Number} objects. The return value will be an instance
* of the highest ranking class of the two arguments (e.g. Byte + Integer
* will give an Integer result)
*
* @param n1 first value
* @param n2 second value
*
* @return the result as a new instance of the highest ranking argument class
*/
public static Number add(Number n1, Number n2) {
return calculate(OpType.ADD, n1, n2);
}
/**
* Calculates {@code n2 - n1}. The return value will be an instance
* of the highest ranking class of the two arguments (e.g. Byte - Integer
* will give an Integer result)
*
* @param n1 first value
* @param n2 second value
*
* @return the result as a new instance of the highest ranking argument class
*/
public static Number subtract(Number n1, Number n2) {
return calculate(OpType.SUBTRACT, n1, n2);
}
/**
* Calculates {@code n2 * n1}. The return value will be an instance
* of the highest ranking class of the two arguments (e.g. Byte * Integer
* will give an Integer result)
*
* @param n1 first value
* @param n2 second value
*
* @return the result as a new instance of the highest ranking argument class
*/
public static Number multiply(Number n1, Number n2) {
return calculate(OpType.MULTIPLY, n1, n2);
}
/**
* Calculates {@code n2 / n1}. The return value will be an instance
* of the highest ranking class of the two arguments (e.g. Byte / Integer
* will give an Integer result)
*
* @param n1 first value
* @param n2 second value
*
* @return the result as a new instance of the highest ranking argument class
*/
public static Number divide(Number n1, Number n2) {
return calculate(OpType.DIVIDE, n1, n2);
}
/**
* Compares value {@code n1} to value {@code n2}. If one or both of the values are
* Float or Double the comparison is done within the currently set float or double
* tolerance.
*
* @param n1 the first value
* @param n2 the second value
*
* @return -1 if the first value is less than the second; 1 if the first value
* is greater than the second; 0 if the two values are equal.
*
* @see #getFloatTolerance()
* @see #setFloatTolerance(float)
*/
public static int compare(Number n1, Number n2) {
return (int)Math.round(calculate(OpType.COMPARE, n1, n2).doubleValue());
}
/**
* Gets the current tolerance used for Float comparisons.
*
* @return the current tolerance
*/
public static float getFloatTolerance() {
return floatTol;
}
/**
* Sets the tolerance used for Float comparisons.
*
* @param tol a small positive value
*/
public static void setFloatTolerance(float tol) {
floatTol = Math.abs(tol);
}
/**
* Gets the current tolerance used for Double comparisons.
*
* @return the current tolerance
*/
public static double getDoubleTolerance() {
return doubleTol;
}
/**
* Sets the tolerance used for Double comparisons.
*
* @param tol a small positive value
*/
public static void setFloatTolerance(double tol) {
doubleTol = Math.abs(tol);
}
/**
* Helper method for the individual operations methods: add, subtract etc.
*
* @param type calculation type
* @param n1 the first value
* @param n2 the second value
*
* @return the result as a new instance of the highest ranking argument class
*/
private static Number calculate(OpType type, Number n1, Number n2) {
Number result = null;
ClassInfo ciIn = ClassInfo.get(highestClass(n1, n2));
ClassInfo ciOut = null;
if (type == OpType.COMPARE) {
ciOut = ClassInfo.INTEGER;
} else {
ciOut = ciIn;
}
switch (ciIn) {
case BYTE:
case SHORT:
case INTEGER:
case LONG:
result = integralCalculation(type, n1, n2);
break;
case FLOAT:
result = floatCalculation(type, n1, n2);
break;
case DOUBLE:
result = doubleCalculation(type, n1, n2);
break;
default:
throw new UnsupportedOperationException("Unrecognized number class");
}
return newInstance(result, ciOut);
}
/**
* Safely convert the argument to an {@code int} value. The following conventions apply:
*
* - Byte values are handled as unsigned (e.g. range 0 - 255)
*
- Float or Double NaN arguments are converted to 0
*
- Float or Double POSITIVE_INFINITY is converted to Integer.MAX_VALUE
*
- Float or Double NEGATIVE_INFINITY is converted to Integer.MIN_VALUE
*
*
* @param number the object to convert
*
* @return the object value as an {@code int}
*/
public static int intValue(Number number) {
ClassInfo ci = ClassInfo.get(number.getClass());
int value = 0;
switch (ci) {
case BYTE:
value = number.intValue() & 0xff;
break;
case SHORT:
case INTEGER:
value = number.intValue();
break;
case LONG:
value = (int) Math.min(Math.max(number.longValue(), Integer.MIN_VALUE), Integer.MAX_VALUE);
break;
case FLOAT:
Float f = (Float) number;
if (f.isNaN()) {
value = 0;
} else if (f.isInfinite()) {
value = f.equals(Float.NEGATIVE_INFINITY) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
} else {
value = (int) Math.max( Math.min(number.floatValue(), Integer.MAX_VALUE), Integer.MIN_VALUE );
}
case DOUBLE:
Double d = (Double) number;
if (d.isNaN()) {
value = 0;
} else if (d.isInfinite()) {
value = d.equals(Double.NEGATIVE_INFINITY) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
} else {
value = (int) Math.max( Math.min(number.doubleValue(), Integer.MAX_VALUE), Integer.MIN_VALUE );
}
default:
throw new IllegalStateException("Unrecognized number class: " + number.getClass().getName());
}
return value;
}
/**
* Safely convert the argument to a {@code long} value. The following conventions apply:
*
* - Byte values are handled as unsigned (e.g. range 0 - 255)
*
- Float or Double NaN arguments are converted to 0
*
- Float or Double POSITIVE_INFINITY is converted to Long.MAX_VALUE
*
- Float or Double NEGATIVE_INFINITY is converted to Long.MIN_VALUE
*
*
* @param number the object to convert
*
* @return the object value as a {@code long}
*/
public static long longValue(Number number) {
ClassInfo ci = ClassInfo.get(number.getClass());
long value = 0;
switch (ci) {
case BYTE:
value = number.intValue() & 0xff;
break;
case SHORT:
case INTEGER:
case LONG:
value = number.longValue();
break;
case FLOAT:
Float f = (Float) number;
if (f.isNaN()) {
value = 0;
} else if (f.isInfinite()) {
value = f.equals(Float.NEGATIVE_INFINITY) ? Long.MIN_VALUE : Long.MAX_VALUE;
} else {
value = (long) Math.max( Math.min(number.floatValue(), Long.MAX_VALUE), Long.MIN_VALUE );
}
break;
case DOUBLE:
Double d = (Double) number;
if (d.isNaN()) {
value = 0;
} else if (d.isInfinite()) {
value = d.equals(Double.NEGATIVE_INFINITY) ? Long.MIN_VALUE : Long.MAX_VALUE;
} else {
value = (long) Math.max( Math.min(number.doubleValue(), Long.MAX_VALUE), Long.MIN_VALUE );
}
break;
default:
throw new IllegalStateException("Unrecognized number class: " + number.getClass().getName());
}
return value;
}
/**
* Safely convert the argument to a {@code float} value. The following conventions apply:
*
* - Byte values are handled as unsigned (e.g. range 0 - 255)
*
- Double.NaN is converted to Float.NaN
*
- Double POSITIVE_INFINITY is converted to Float.POSITIVE_INFINITY
*
- Double NEGATIVE_INFINITY is converted to Float.NEGATIVE_INFINITY
*
*
* @param number the object to convert
*
* @return the object value as a {@code float}
*/
public static float floatValue(Number number) {
ClassInfo ci = ClassInfo.get(number.getClass());
float value = 0.0F;
switch (ci) {
case BYTE:
value = number.intValue() & 0xff;
break;
case SHORT:
case INTEGER:
case LONG:
case FLOAT:
value = number.floatValue();
break;
case DOUBLE:
Double d = (Double) number;
if (d.isNaN()) {
value = Float.NaN;
} else if (d.isInfinite()) {
value = d.equals(Double.NEGATIVE_INFINITY) ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
} else {
value = (float) Math.max( Math.min(number.doubleValue(), Float.POSITIVE_INFINITY), Float.NEGATIVE_INFINITY );
}
default:
throw new IllegalStateException("Unrecognized number class: " + number.getClass().getName());
}
return value;
}
/**
* Safely convert the argument to a {@code double} value. The only difference
* between this method and {@code Number.doubleValue()} is that Byte values
* are handled as unsigned (e.g. range 0 - 255).
*
* @param number the object to convert
*
* @return the object value as a {@code double}
*/
public static double doubleValue(Number number) {
ClassInfo ci = ClassInfo.get(number.getClass());
switch (ci) {
case BYTE:
return (double) (number.intValue() & 0xff);
default:
return number.doubleValue();
}
}
/**
* Return a new instance of class {@code clazz} taking its value from {@code number}
*
* @param number the number object whose value will be copied
* @param clazz the class of the new instance
*
* @return a new instance of the request class with value {@code number}
*/
public static Number newInstance(Number number, Class extends Number> clazz) {
ClassInfo ci = ClassInfo.get(clazz);
return newInstance(number, ci);
}
/**
* Return a new instance of the class described by {@code ci} taking its value
* from {@code number}
*
* @param number the number object whose value will be copied
* @param ci a ClassInfo object
*
* @return a new instance of the request class with value {@code number}
*/
private static Number newInstance(Number number, ClassInfo ci) {
switch (ci) {
case BYTE:
return newByte(number);
case SHORT:
return newShort(number);
case INTEGER:
return newInteger(number);
case LONG:
return newLong(number);
case FLOAT:
return newFloat(number);
case DOUBLE:
return newDouble(number);
default:
throw new IllegalArgumentException("Unrecognized ClassInfo arg: " + ci);
}
}
/**
* Determine the highest ranking class (in terms of numeric precision)
* among the arguments
*
* @param numbers {@code Number} objects to compare
*
* @return the highest ranking class, or {@code null} if no arguments were supplied
*/
public static Class extends Number> highestClass(Number ...numbers) {
int maxRank = -1;
Class extends Number> result = null;
for (Number n : numbers) {
int rank = ClassInfo.get(n.getClass()).getRank();
if (rank > maxRank) {
maxRank = rank;
result = n.getClass();
}
}
return result;
}
/**
* Determine the lowest ranking class (in terms of numeric precision)
* among the arguments
*
* @param numbers {@code Number} objects to compare
*
* @return the lowest ranking class, or {@code null} if no arguments were supplied
*/
public static Class extends Number> lowestClass(Number ...numbers) {
int minRank = ClassInfo.DOUBLE.getRank() + 1;
Class extends Number> result = null;
for (Number n : numbers) {
int rank = ClassInfo.get(n.getClass()).getRank();
if (rank < minRank) {
minRank = rank;
result = n.getClass();
}
}
return result;
}
/**
* Perform a calculation on two integral values. The calculations are done
* with {@code long} values.
*
* @param type the type of calculation
* @param n1 first value
* @param n2 second value
*
* @return result as a new instance of {@code Long}
*/
private static Long integralCalculation(OpType type, Number n1, Number n2) {
long result = 0;
long val1 = longValue(n1);
long val2 = longValue(n2);
switch (type) {
case ADD:
result = val1 + val2;
break;
case SUBTRACT:
result = val1 - val2;
break;
case MULTIPLY:
result = val1 * val2;
break;
case DIVIDE:
result = val1 / val2;
break;
case COMPARE:
result = (val1 < val2 ? -1 : (val1 > val2 ? 1 : 0));
break;
default:
throw new IllegalArgumentException("Invalid OpType: " + type);
}
return Long.valueOf(result);
}
/**
* Perform a calculation on two values using {@code float} precision.
*
* @param type the type of calculation
* @param n1 first value
* @param n2 second value
*
* @return result as a new instance of {@code Float}
*/
private static Float floatCalculation(OpType opType, Number n1, Number n2) {
float result = 0;
if (opType == OpType.COMPARE) {
return (float) floatComparison(n1, n2);
}
if (n1 instanceof Float) {
Float fn1 = (Float) n1;
if (fn1.isInfinite() || fn1.isNaN()) {
return Float.valueOf(Float.NaN);
}
}
if (n2 instanceof Float) {
Float fn2 = (Float) n1;
if (fn2.isInfinite() || fn2.isNaN()) {
return Float.valueOf(Float.NaN);
}
}
float val1 = floatValue(n1);
float val2 = floatValue(n2);
switch (opType) {
case ADD:
result = val1 + val2;
break;
case SUBTRACT:
result = val1 - val2;
break;
case MULTIPLY:
result = val1 * val2;
break;
case DIVIDE:
result = val1 / val2;
break;
}
return Float.valueOf(result);
}
/**
* Compare two values as floats using the currently set tolerance
*
* @param n1 first value
* @param n2 second value
*
* @return -1 if the first value is less than the second; 1 if the
* first value is greater than the second; 0 if the two values
* are equal
*/
private static int floatComparison(Number n1, Number n2) {
float val1 = floatValue(n1);
float val2 = floatValue(n2);
if (Float.isInfinite(val1) || Float.isNaN(val1) ||
Float.isInfinite(val2) || Float.isNaN(val2)) {
return Float.compare(val1, val2);
}
int result;
if (Math.abs(val1 - val2) < floatTol) {
result = 0;
} else {
result = (val1 < val2 ? -1 : 1);
}
return result;
}
/**
* Perform a calculation on two values using double precision.
*
* @param type the type of calculation
* @param n1 first value
* @param n2 second value
*
* @return result as a new instance of {@code Double}
*/
private static Double doubleCalculation(OpType opType, Number n1, Number n2) {
double result = 0;
if (opType == OpType.COMPARE) {
return (double) doubleComparison(n1, n2);
}
if (n1 instanceof Double) {
Double dn1 = (Double) n1;
if (dn1.isInfinite() || dn1.isNaN()) {
return Double.valueOf(Double.NaN);
}
}
if (n2 instanceof Double) {
Double dn2 = (Double) n1;
if (dn2.isInfinite() || dn2.isNaN()) {
return Double.valueOf(Double.NaN);
}
}
double val1 = doubleValue(n1);
double val2 = doubleValue(n2);
switch (opType) {
case ADD:
result = val1 + val2;
break;
case SUBTRACT:
result = val1 - val2;
break;
case MULTIPLY:
result = val1 * val2;
break;
case DIVIDE:
result = val1 / val2;
break;
}
return Double.valueOf(result);
}
/**
* Compare two values as floats using the currently set tolerance
*
* @param n1 first value
* @param n2 second value
*
* @return -1 if the first value is less than the second; 1 if the
* first value is greater than the second; 0 if the two values
* are equal
*/
private static int doubleComparison(Number n1, Number n2) {
double val1 = doubleValue(n1);
double val2 = doubleValue(n2);
if (Double.isInfinite(val1) || Double.isNaN(val1) ||
Double.isInfinite(val2) || Double.isNaN(val2)) {
return Double.compare(val1, val2);
}
int result;
if (Math.abs(val1 - val2) < doubleTol) {
result = 0;
} else {
result = (val1 < val2 ? -1 : 1);
}
return result;
}
/**
* Creates a new Byte taking its value (unsigned and possibly clamped)
* from {@code source}
*
* @param source the value to copy
*
* @return a new Byte object
*/
private static Byte newByte(Number source) {
long value = longValue(source);
if (value < 0) {
value = 0;
} else if (value > 255) {
value = 255;
}
return Byte.valueOf((byte) value);
}
/**
* Creates a new Short taking its value (possibly clamped)
* from {@code source}
*
* @param source the value to copy
*
* @return a new Short object
*/
private static Short newShort(Number source) {
long value = longValue(source);
if (value < Short.MIN_VALUE) {
value = Short.MIN_VALUE;
} else if (value > Short.MAX_VALUE) {
value = Short.MAX_VALUE;
}
return Short.valueOf((short) value);
}
/**
* Creates a new Integer taking its value (possibly clamped)
* from {@code source}
*
* @param source the value to copy
*
* @return a new Integer object
*/
private static Integer newInteger(Number source) {
long value = longValue(source);
if (value < Integer.MIN_VALUE) {
value = Integer.MIN_VALUE;
} else if (value > Integer.MAX_VALUE) {
value = Integer.MAX_VALUE;
}
return Integer.valueOf((int) value);
}
/**
* Creates a new Long taking its value (possibly clamped)
* from {@code source}
*
* @param source the value to copy
*
* @return a new Long object
*/
private static Long newLong(Number source) {
long value = longValue(source);
return Long.valueOf((int) value);
}
/**
* Creates a new Float taking its value (possibly clamped)
* from {@code source}
*
* @param source the value to copy
*
* @return a new Float object
*/
private static Float newFloat(Number source) {
float value = floatValue(source);
return Float.valueOf(value);
}
/**
* Creates a new Double taking its value (possibly clamped)
* from {@code source}
*
* @param source the value to copy
*
* @return a new Double object
*/
private static Double newDouble(Number source) {
double value = doubleValue(source);
return Double.valueOf(value);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy