/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.cinchapi.concourse.util;
import java.math.BigDecimal;
import com.cinchapi.concourse.Link;
import com.google.common.primitives.UnsignedLongs;
import static com.google.common.base.Preconditions.*;
/**
* This class contains utility methods that provide interoperability for the
* various {@link Number} types.
*
* @author Jeff Nelson
* @author Raghav Babu
*/
public abstract class Numbers {
/**
* Return the sum of two numbers.
*
* @param a the first {@link Number}
* @param b the second {@link Number}
* @return the sum of {@code a} and {@code b}.
*/
public static Number add(Number a, Number b) {
if(Numbers.isFloatingPoint(a) || Numbers.isFloatingPoint(b)) {
BigDecimal a0 = Numbers.toBigDecimal(a);
BigDecimal b0 = Numbers.toBigDecimal(b);
return a0.add(b0);
}
else {
try {
return Math.addExact(a.intValue(), b.intValue());
}
catch (ArithmeticException e) {
return Math.addExact(a.longValue(), b.longValue());
}
}
}
/**
* Compare {@code a} to {@code b}.
*
* @param a
* @param b
* @return -1, 0, or 1 as {@code a} is numerically less than, equal to, or
* greater than {@code b}.
*/
public static int compare(Number a, Number b) {
Class<?> aClass = a.getClass();
Class<?> bClass = b.getClass();
if((aClass == int.class || aClass == Integer.class)
&& (bClass == int.class || bClass == Integer.class)) {
return Integer.compare(a.intValue(), b.intValue());
}
else if((aClass == long.class || aClass == Long.class)
&& (bClass == long.class || bClass == Long.class)) {
return Long.compare(a.longValue(), b.longValue());
}
else if((aClass == float.class || aClass == Float.class)
&& (bClass == float.class || bClass == Float.class)) {
return Float.compare(a.floatValue(), b.floatValue());
}
else if((aClass == double.class || aClass == Double.class)
&& (bClass == double.class || bClass == Double.class)) {
return Double.compare(a.doubleValue(), b.doubleValue());
}
else if((aClass == short.class || aClass == Short.class)
&& (bClass == short.class || bClass == Short.class)) {
return Short.compare(a.shortValue(), b.shortValue());
}
else if((aClass == byte.class || aClass == Byte.class)
&& (bClass == byte.class || bClass == Byte.class)) {
return Byte.compare(a.byteValue(), b.byteValue());
}
else {
// TODO review
String fa = aClass == Link.class
? UnsignedLongs.toString(a.longValue()) : a.toString();
String sb = bClass == Link.class
? UnsignedLongs.toString(b.longValue()) : b.toString();
BigDecimal first = new BigDecimal(fa);
BigDecimal second = new BigDecimal(sb);
return first.compareTo(second);
}
}
/**
* Return the division of two numbers.
*
* @param a the first {@link Number}
* @param b the second {@link Number}
* @return the division result of {@code a} by {@code b}.
*/
public static Number divide(Number a, Number b) {
if(Numbers.isFloatingPoint(a) || Numbers.isFloatingPoint(b)) {
BigDecimal a0 = Numbers.toBigDecimal(a);
BigDecimal b0 = Numbers.toBigDecimal(b);
return a0.divide(b0);
}
else {
try {
return Math.floorDiv(a.intValue(), b.intValue());
}
catch (ArithmeticException e) {
return Math.floorDiv(a.longValue(), b.longValue());
}
}
}
/**
* Compute the incremental average from the current {@code running} of the
* same, given the latest {@code number} and the total {@code count} of
* items (including the specified {@code number}).
*
* @param running the running average
* @param number the next number to include in the average
* @param count the total number of items, including the specified
* {@code number} that contribute to the average
* @return the new incremental average
*/
public static Number incrementalAverage(Number running, Number number,
int count) {
Number dividend = Numbers.add(number, Numbers.multiply(-1, running));
Number addend = Numbers.divide(dividend, count);
return Numbers.add(running, addend);
}
/**
* Return {@code true} if {@code a} is mathematically equal to {@code b}.
*
* @param a
* @param b
* @return {@code true} if {@code a} == {@code b}
*/
public static boolean isEqualTo(Number a, Number b) {
return compare(a, b) == 0;
}
/**
* Perform a cast safe equality check for two objects.
* <p>
* If both objects are instances of the {@link Number} class, this method
* will behave the same was as {@link #isEqualTo(Number, Number)}.
* Otherwise, this method returns {@code false}.
* </p>
*
* @param a the first, possibly {@link Number numeric}, object
* @param b the second, possibly {@link Number numeric}, object
* @return {@code true} if both objects are numbers and are mathematically
* equal
*/
public static boolean isEqualToCastSafe(Object a, Object b) {
if(a instanceof Number && b instanceof Number) {
return isEqualTo((Number) a, (Number) b);
}
else {
return false;
}
}
/**
* Return {@code true} if {@code number} is evenly divisible by two.
*
* @param number
* @return {@code true} if {@code number} is even.
*/
public static boolean isEven(Number number) {
return number.intValue() % 2 == 0;
}
/**
* Return {@code true} if the {@code number} is a floating point type.
*
* @param number the {@link Number} to check
* @return {@code true} if the {@link Number} is floating point
*/
public static boolean isFloatingPoint(Number number) {
return number instanceof Float || number instanceof Double
|| number instanceof BigDecimal;
}
/**
* Return {@code true} if {@code a} is mathematically greater than {@code b}
*
* @param a
* @param b
* @return {@code true} if {@code a} > {@code b}
*/
public static boolean isGreaterThan(Number a, Number b) {
return compare(a, b) > 0;
}
/**
* Return {@code true} if {@code a} is mathematically greater than or equal
* to {@code b}.
*
* @param a
* @param b
* @return {@code true} if {@code a} >= {@code b}
*/
public static boolean isGreaterThanOrEqualTo(Number a, Number b) {
return compare(a, b) >= 0;
}
/**
* Return {@code true} if {@code a} is mathematically less than {@code b}.
*
* @param a
* @param b
* @return {@code true} if {@code a} < {@code b}
*/
public static boolean isLessThan(Number a, Number b) {
return compare(a, b) < 0;
}
/**
* Return {@code true} if {@code a} is mathematically less than or equal to
* {@code b}.
*
* @param a
* @param b
* @return {@code true} if {@code a} <= {@code b}
*/
public static boolean isLessThanOrEqualTo(Number a, Number b) {
return compare(a, b) <= 0;
}
/**
* Return {@code true} if {@code number} is not evenly divisible by two.
*
* @param number
* @return {@code true} if {@code number} is odd.
*/
public static boolean isOdd(Number number) {
return !isEven(number);
}
/**
* Return the max from a list of {@code numbers}.
*
* @param numbers
* @return the largest number
*/
public static Number max(Number... numbers) {
Number max = numbers[0];
for (Number number : numbers) {
max = isGreaterThan(max, number) ? max : number;
}
return max;
}
/**
* Return the min from a list of {@code numbers}.
*
* @param numbers
* @return the smallest number
*/
public static Number min(Number... numbers) {
Number min = numbers[0];
for (Number number : numbers) {
min = isLessThan(min, number) ? min : number;
}
return min;
}
/**
* Return the product of two numbers.
*
* @param a the first {@link Number}
* @param b the second {@link Number}
* @return the product of {@code a} and {@code b}.
*/
public static Number multiply(Number a, Number b) {
if(Numbers.isFloatingPoint(a) || Numbers.isFloatingPoint(b)) {
BigDecimal a0 = Numbers.toBigDecimal(a);
BigDecimal b0 = Numbers.toBigDecimal(b);
return a0.multiply(b0);
}
else {
try {
return Math.multiplyExact(a.intValue(), b.intValue());
}
catch (ArithmeticException e) {
return Math.multiplyExact(a.longValue(), b.longValue());
}
}
}
/**
* Return numerator/denominator as a percent.
*
* @param numerator
* @param denominator
* @return the percent
*/
public static double percent(Number numerator, Number denominator) {
return numerator.doubleValue() * 100.0 / denominator.doubleValue();
}
/**
* Scale {@code number}, which is between {@code rawMin} and {@code rawMax}
* to a value between {@code scaledMin} and {@code scaleMax}.
*
* @param number
* @param rawMin
* @param rawMax
* @param scaledMin
* @param scaledMax
* @return the scaled value
*/
public static Number scale(Number number, Number rawMin, Number rawMax,
Number scaledMin, Number scaledMax) {
checkArgument(isGreaterThanOrEqualTo(number, rawMin)
&& isLessThanOrEqualTo(number, rawMax));
double x = number.doubleValue();
double min = rawMin.doubleValue();
double max = rawMax.doubleValue();
double a = scaledMin.doubleValue();
double b = scaledMax.doubleValue();
return (((b - a) * (x - min)) / (max - min)) + a;
}
/**
* Checks the instance type of input {@link Number} and returns a
* corresponding {@link BigDecimal}.
*
* @param number
* @return {@link BigDecimal}
*/
public static BigDecimal toBigDecimal(Number number) {
// TODO check for primitive classes...
if(number == null) {
return null;
}
if(number instanceof Integer) {
return new BigDecimal(number.intValue());
}
else if(number instanceof Double) {
return new BigDecimal(number.doubleValue());
}
else if(number instanceof Float) {
return new BigDecimal(number.floatValue());
}
else if(number instanceof Long) {
return new BigDecimal(number.longValue());
}
else if(number instanceof Byte) {
return new BigDecimal(number.byteValue());
}
else if(number instanceof Short) {
return new BigDecimal(number.shortValue());
}
else if(number instanceof BigDecimal) {
return (BigDecimal) number;
}
return null;
}
}