/*
* Strongback
* Copyright 2015, Strongback and individual contributors by the @authors tag.
* See the COPYRIGHT.txt in the distribution for a full listing of individual
* contributors.
*
* Licensed under the MIT License; you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.strongback.util;
import org.strongback.function.DoubleToDoubleFunction;
/**
* Utility class for working with values.
*
* @author Zach Anderson
*/
public final class Values {
public static final int DEFAULT_NUMBER_OF_BITS = 12;
/**
* Compares two floating point numbers with the {@link #DEFAULT_NUMBER_OF_BITS default tolerance}.
*
* @param first the first value
* @param second the second value
* @return {@code 0} if both values are within a tolerance of each other; {@code 1} if {@code a} is greater than {@code b};
* {@code -1} if {@code b} is greater than {@code a}
*/
public static int fuzzyCompare(double first, double second) {
return fuzzyCompare(first, second, DEFAULT_NUMBER_OF_BITS);
}
/**
* Compares two floating point numbers with a tolerance dictated by the number of bits precision used for the fractional
* part of the values.
*
* @param a the first value
* @param b the second value
* @param bits the number of bits of precision
* @return {@code 0} if both values are within {@code tolerance} of each other; {@code 1} if {@code a} is greater than
* {@code b}; {@code -1} if {@code b} is greater than {@code a}
*/
public static int fuzzyCompare(double a, double b, int bits) {
return fuzzyCompare(a, b, calcTolerance(bits));
}
/**
* Calculate the tolerance for the given number of bits of precision. The tolerance is calculated as {@code 1/(2^n)}, where
* {@code n} is the number of bits. For example, a precision of 4 bits results in a tolerance of 0.0625.
*
* @param bits
* @return
*/
private static double calcTolerance(int bits) {
return 1.0 / (1 << bits);
}
/**
* Compares two floating point numbers with a tolerance.
*
* @param a the first value
* @param b the second value
* @param tolerance the smallest delta that is still considered equal
* @return {@code 0} if both values are within {@code tolerance} of each other; {@code 1} if {@code a} is greater than
* {@code b}; {@code -1} if {@code b} is greater than {@code a}
* @throws IllegalArgumentException if the tolerance is negative
*/
public static int fuzzyCompare(double a, double b, double tolerance) {
if (tolerance < 0.0) throw new IllegalArgumentException("The tolerance may not be negative");
double difference = a - b;
return (Math.abs(difference) <= tolerance ? 0
: // the two values are within the tolerance of each other
(difference > 0 ? 1
: // the first is greater than the second
-1)); // the first is less than the second
}
/**
* Limit values to the band between {@code [minimum,maximum]} (inclusive).
*
* @param minimum the minimum value below which 0.0 is used; must be less than or equal to {@code maximum}
* @param num the input value; may be any value
* @param maximum the maximum allowed value; must be greater than or equal to {@code minimum}
* @return the limited output value
* @throws IllegalArgumentException if the minimum value is greater than the maximum value
*/
public static double limit(double minimum, double num, double maximum) {
if (maximum < minimum) throw new IllegalArgumentException(
"The minimum value must be less than or equal to the maximum value");
if (num > maximum) {
return maximum;
}
if (num < minimum) {
return minimum;
}
return num;
}
/**
* Create a {@link DoubleToDoubleFunction function} that limits the input value to to the band between
* {@code [minimum,maximum]} (inclusive).
*
* @param minimum the minimum value below which 0.0 is used; must be less than or equal to {@code maximum}
* @param maximum the maximum allowed value; must be greater than or equal to {@code minimum}
* @return the function that limits to the maximum and minimum values; never null
* @throws IllegalArgumentException if the minimum value is greater than the maximum value
*/
public static DoubleToDoubleFunction limiter(double minimum, double maximum) {
if (maximum < minimum) throw new IllegalArgumentException(
"The minimum value must be less than or equal to the maximum value");
return new DoubleToDoubleFunction() {
@Override
public double applyAsDouble(double value) {
if (value > maximum) {
return maximum;
}
if (value < minimum) {
return minimum;
}
return value;
}
};
}
/**
* Limit values to the band between {@code [-maximum,-minimum]} and {@code [+minimum,+maximum]} (inclusive).
*
* @param minimum the minimum value below which 0.0 is used; must be positive or equal to zero, but less than or equal to
* maximum
* @param num the input value; may be any value
* @param maximum the maximum allowed value; must be positive or equal to zero
* @return the limited output value
* @throws IllegalArgumentException if the minimum and maximum values are invalid
*/
public static double symmetricLimit(double minimum, double num, double maximum) {
if (minimum < 0) throw new IllegalArgumentException("The minimum value may not be negative");
if (maximum < 0) throw new IllegalArgumentException("The maximum value may not be negative");
if (maximum < minimum) throw new IllegalArgumentException(
"The minimum value must be less than or equal to the maximum value");
if (num > maximum) {
return maximum;
}
double positiveNum = Math.abs(num);
if (positiveNum > maximum) {
return -maximum;
}
return positiveNum > minimum ? num : 0.0;
}
/**
* Create a {@link DoubleToDoubleFunction function} that limits the input value to the band between
* {@code [-maximum,-minimum]} and {@code [+minimum,+maximum]} (inclusive).
*
* @param minimum the minimum value below which 0.0 is used; must be less than or equal to {@code maximum}
* @param maximum the maximum allowed value; must be greater than or equal to {@code minimum}
* @return the function that limits to the maximum and minimum values; never null
*/
public static DoubleToDoubleFunction symmetricLimiter(double minimum, double maximum) {
if (minimum < 0) throw new IllegalArgumentException("The minimum value may not be negative");
if (maximum < 0) throw new IllegalArgumentException("The maximum value may not be negative");
if (maximum < minimum) throw new IllegalArgumentException(
"The minimum value must be less than or equal to the maximum value");
return new DoubleToDoubleFunction() {
@Override
public double applyAsDouble(double num) {
if (num > maximum) {
return maximum;
}
double positiveNum = Math.abs(num);
if (positiveNum > maximum) {
return -maximum;
}
return positiveNum > minimum ? num : 0.0;
}
};
}
public static interface RangeMaker {
DoubleToDoubleFunction toRange(double minOutputValue, double maxOutputValue);
}
/**
* Begin to create a function that maps from the specified range of input values. To obtain a mapping function, call the
* {@link RangeMaker#toRange(double, double)} method on the resulting {@link RangeMaker} instance.
* <p>
* This is equivalent to calling {@link #mapRange(double, double, double, double)} with the input and output range limits.
* For example:
*
* <pre>
* Values.mapRange(-1.0,1.0).toRange(0.0,1.0);
* </pre>
*
* is equivalent to:
*
* <pre>
* Values.mapRange(-1.0,1.0,0.0,1.0);
* </pre>
*
* @param minInputValue the minimum value of the range of input values to the function
* @param maxInputValue the maximum value of the range of input values to the function
* @return the range maker that should be used to complete the function; never null
* @see #mapRange(double, double, double, double)
*/
public static RangeMaker mapRange(double minInputValue, double maxInputValue) {
return (minOutput, maxOutput) -> {
return mapRange(minInputValue, maxInputValue, minOutput, maxOutput);
};
}
/**
* Create a function that maps from the specified range of input values
*
* @param minInputValue the minimum value of the range of input values to the function
* @param maxInputValue the maximum value of the range of input values to the function
* @param minOutputValue the minimum value for the output of the function
* @param maxOutputValue the maximum value for the output of the function
* @return the mapping function; never null
* @see #mapRange(double, double)
*/
public static DoubleToDoubleFunction mapRange(double minInputValue, double maxInputValue, double minOutputValue,
double maxOutputValue) {
double factor = (maxOutputValue - minOutputValue) / (maxInputValue - minInputValue);
return new DoubleToDoubleFunction() {
@Override
public double applyAsDouble(double num) {
if (num <= minInputValue) return minOutputValue;
if (num >= maxInputValue) return maxOutputValue;
double output = minOutputValue + ((num - minInputValue) * factor);
if (output < minOutputValue)
output = minOutputValue;
else if (output > maxOutputValue) output = maxOutputValue;
return output;
}
};
}
}