/* * */ package org.smartly.commons.util; import org.smartly.commons.logging.Logger; import org.smartly.commons.logging.util.LoggingUtils; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; /** * @author */ public abstract class MathUtils { /** * @param nums the numbers to be added * @return the sum of the numbers or * <code>null</code> if they're invalid */ public static Number add(Object... nums) { double value = 0; Number[] ns = new Number[nums.length]; for (Object num : nums) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } value += n.doubleValue(); } return matchType(value, ns); } /** * @param nums the numbers to be subtracted * @return the difference of the numbers (subtracted in order) or * <code>null</code> if they're invalid */ public static Number sub(final Object... nums) { double value = 0; final Number[] ns = new Number[nums.length]; for (int i = 0; i < nums.length; i++) { Number n = ConversionUtils.toNumber(nums[i]); if (n == null) { //return null; getLogger().warning("You are trying to subtract '{0}' to {1}", nums[i], value); continue; // does not return, but try to subtract next number } if (i == 0) { value = n.doubleValue(); } else { value -= n.doubleValue(); } } return matchType(value, ns); } /** * @param nums the numbers to be multiplied * @return the product of the numbers or * <code>null</code> if they're invalid */ public static Number mul(Object... nums) { double value = 1; Number[] ns = new Number[nums.length]; for (Object num : nums) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } value *= n.doubleValue(); } return matchType(value, ns); } /** * @param nums the numbers to be divided * @return the quotient of the numbers or * <code>null</code> if they're invalid * or if any denominator equals zero */ public static Number div(Object... nums) { double value = 0; Number[] ns = new Number[nums.length]; for (int i = 0; i < nums.length; i++) { Number n = ConversionUtils.toNumber(nums[i]); if (n == null) { return null; } if (i == 0) { value = n.doubleValue(); } else { double denominator = n.doubleValue(); if (denominator == 0.0) { return null; } value /= denominator; } } return matchType(value, ns); } /** * @param num1 the first number * @param num2 the second number * @return the first number raised to the power of the * second or <code>null</code> if they're invalid */ public static Number pow(Object num1, Object num2) { Number n1 = ConversionUtils.toNumber(num1); Number n2 = ConversionUtils.toNumber(num2); if (n1 == null || n2 == null) { return null; } double value = java.lang.Math.pow(n1.doubleValue(), n2.doubleValue()); return matchType(n1, n2, value); } /** * Does integer division on the int values of the specified numbers. * <p/> * <p>So, $math.idiv('5.1',3) will return '1', * and $math.idiv(6,'3.9') will return '2'.</p> * * @param num1 the first number * @param num2 the second number * @return the result of performing integer division * on the operands. */ public static Integer idiv(Object num1, Object num2) { Number n1 = ConversionUtils.toNumber(num1); Number n2 = ConversionUtils.toNumber(num2); if (n1 == null || n2 == null || n2.intValue() == 0) { return null; } int value = n1.intValue() / n2.intValue(); return Integer.valueOf(value); } /** * Does integer modulus on the int values of the specified numbers. * <p/> * <p>So, $math.mod('5.1',3) will return '2', * and $math.mod(6,'3.9') will return '0'.</p> * * @param num1 the first number * @param num2 the second number * @return the result of performing integer modulus * on the operands. */ public static Integer mod(Object num1, Object num2) { Number n1 = ConversionUtils.toNumber(num1); Number n2 = ConversionUtils.toNumber(num2); if (n1 == null || n2 == null || n2.intValue() == 0) { return null; } int value = n1.intValue() % n2.intValue(); return Integer.valueOf(value); } public static int max(final Integer... numbers) { int result = 0; if (null != numbers && numbers.length > 0) { for (final int number : numbers) { if (number > result) { result = number; } } } return result; } /** * @param nums the numbers to be searched * @return the largest of the numbers or * <code>null</code> if they're invalid */ public static Number max(final Object... nums) { double value = Double.MIN_VALUE; final Number[] ns = new Number[nums.length]; for (Object num : nums) { final Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } value = java.lang.Math.max(value, n.doubleValue()); } return matchType(value, ns); } /** * @param nums the numbers to be searched * @return the smallest of the numbers or * <code>null</code> if they're invalid */ public static Number min(Object... nums) { double value = Double.MAX_VALUE; Number[] ns = new Number[nums.length]; for (Object num : nums) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } value = java.lang.Math.min(value, n.doubleValue()); } return matchType(value, ns); } public static double matchLimit(final Object value, final Object min, final Object max) { final double nvalue = ConversionUtils.toDouble(value); final double nmin = ConversionUtils.toDouble(min); final double nmax = ConversionUtils.toDouble(max); if (nvalue >= nmin && nvalue <= nmax) { return nvalue; } else if (nvalue > nmax) { return nmax; } else { return nmin; } } /** * @param num the number * @return the absolute value of the number or * <code>null</code> if it's invalid */ public static Number abs(Object num) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } double value = java.lang.Math.abs(n.doubleValue()); return matchType(n, value); } // -------------------------------------------------------------------- // Aggregation methods // -------------------------------------------------------------------- /** * Get the sum of the values from a list * * @param collection A collection containing Java beans * @param field A Java Bean field for the objects in <i>collection</i> that * will return a number. * @return The sum of the values in <i>collection</i>. */ public static Number getTotal(Collection collection, String field) { if (collection == null || field == null) { return null; } double result = 0; // hold the first number and use it to match return type Number first = null; try { for (Iterator i = collection.iterator(); i.hasNext(); ) { Object property = BeanUtils.getValueIfAny(i.next(), field); Number value = ConversionUtils.toNumber(property); // skip over nulls (i.e. treat them as 0) if (value != null) { if (first == null) { first = value; } result += value.doubleValue(); } } return matchType(first, result); } catch (Exception e) { return null; } } /** * Get the average of the values from a list * * @param collection A collection containing Java beans * @param field A Java Bean field for the objects in <i>collection</i> that * will return a number. * @return The average of the values in <i>collection</i>. */ public static Number getAverage(Collection collection, String field) { Number result = getTotal(collection, field); if (result == null) { return null; } double avg = result.doubleValue() / collection.size(); return matchType(result, avg); } /** * Get the sum of the values from a list * * @param array An array containing Java beans * @param field A Java Bean field for the objects in <i>array</i> that * will return a number. * @return The sum of the values in <i>array</i>. */ public static Number getTotal(Object[] array, String field) { return getTotal(Arrays.asList(array), field); } /** * Get the sum of the values from a list * * @param array A collection containing Java beans * @param field A Java Bean field for the objects in <i>array</i> that * will return a number. * @return The sum of the values in <i>array</i>. */ public static Number getAverage(Object[] array, String field) { return getAverage(Arrays.asList(array), field); } /** * Get the sum of the values * * @param collection A collection containing numeric values * @return The sum of the values in <i>collection</i>. */ public static Number getTotal(Collection collection) { if (collection == null) { return null; } double result = 0; // grab the first number and use it to match return type Number first = null; for (Iterator i = collection.iterator(); i.hasNext(); ) { Number value = ConversionUtils.toNumber(i.next()); if (value == null) { //FIXME? or should we ignore this and keep adding? return null; } if (first == null) { first = value; } result += value.doubleValue(); } return matchType(first, result); } /** * Get the average of the values * * @param collection A collection containing number values * @return The average of the values in <i>collection</i>. */ public static Number getAverage(Collection collection) { Number result = getTotal(collection); if (result == null) { return null; } double avg = result.doubleValue() / collection.size(); return matchType(result, avg); } /** * Get the sum of the values * * @param array An array containing number values * @return The sum of the values in <i>array</i>. */ public static Number getTotal(Object... array) { return getTotal(Arrays.asList(array)); } /** * Get the average of the values * * @param array An array containing number values * @return The sum of the values in <i>array</i>. */ public static Number getAverage(Object... array) { return getAverage(Arrays.asList(array)); } /** * Get the sum of the values * * @param values The list of double values to add up. * @return The sum of the arrays */ public static Number getTotal(double... values) { if (values == null) { return null; } double result = 0; for (int i = 0; i < values.length; i++) { result += values[i]; } return new Double(result); } /** * Get the average of the values in an array of double values * * @param values The list of double values * @return The average of the array of values */ public static Number getAverage(double... values) { Number total = getTotal(values); if (total == null) { return null; } return new Double(total.doubleValue() / values.length); } /** * Get the sum of the values * * @param values The list of long values to add up. * @return The sum of the arrays */ public static Number getTotal(long... values) { if (values == null) { return null; } long result = 0; for (long value : values) { result += value; } return Long.valueOf(result); } /** * Get the average of the values in an array of long values * * @param values The list of long values * @return The average of the array of values */ public static Number getAverage(long... values) { Number total = getTotal(values); if (total == null) { return null; } double avg = total.doubleValue() / values.length; return matchType(total, avg); } // -------------------------------------------------------------------- // R O U N D // -------------------------------------------------------------------- /** * @param num the number * @return the smallest integer that is not * less than the given number */ public static Integer ceil(Object num) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } return Integer.valueOf((int) java.lang.Math.ceil(n.doubleValue())); } /** * Round a number mantainig desired number of decimal digits.<br> * i.e. round(1.234, 2) = 1.23<br> * i.e. round(1.236, 2) = 1.24 * * @param value * @param decimals * @return */ public static double round(double value, int decimals) { final double x = decimals == 0 ? 1 : Math.pow(10, decimals); // 1 or 10^decimals return Math.round(value * x) / x; } /** * @param num the number * @return the integer portion of the number */ public static Integer floor(Object num) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } return Integer.valueOf((int) java.lang.Math.floor(n.doubleValue())); } /** * Rounds a number to the nearest whole Integer * * @param num the number to round * @return the number rounded to the nearest whole Integer * or <code>null</code> if it's invalid * @see java.lang.Math#rint(double) */ public static Integer round(final Object num) { Number n = ConversionUtils.toNumber(num); if (n == null) { return null; } return Integer.valueOf((int) java.lang.Math.rint(n.doubleValue())); } /** * Rounds a number to the specified number of decimal places. * This is particulary useful for simple display formatting. * If you want to round an number to the nearest integer, it * is better to use {@link #round}, as that will return * an {@link Integer} rather than a {@link Double}. * * @param num the number to round * @param decimals the number of decimal places * @return the value rounded to the specified number of * decimal places or <code>null</code> if it's invalid */ public static Double roundTo(final Object num, final Object decimals) { final Number i = ConversionUtils.toNumber(decimals); final Number d = ConversionUtils.toNumber(num); if (i == null || d == null) { return null; } //ok, go ahead and do the rounding int places = i.intValue(); double value = d.doubleValue(); int delta = 10; for (int j = 1; j < places; j++) { delta *= 10; } return new Double((double) java.lang.Math.round(value * delta) / delta); } /** * Round ceil a number, mantaining desired decimals. * i.e. roundCeil(1.121, 2) = 1.13 * * @param value the value * @param decimals number of decimals digits * @return rounded value */ public static double roundCeil(double value, int decimals) { final double x = decimals == 0 ? 1 : Math.pow(10, decimals); // 1 or 10^decimals return Math.ceil(value * x) / x; } /** * Round floor a number, mantaining desired decimals. * i.e. roundFloor(1.129, 2) = 1.12 * * @param value the value * @param decimals number of decimals digits * @return rounded value */ public static double roundFloor(double value, int decimals) { final double x = decimals == 0 ? 1 : Math.pow(10, decimals); // 1 or 10^decimals return Math.floor(value * x) / x; } /** * Return an array of long values. Length of array is "mod" value. * * @param value * @param mod * @param div * @return */ public static long[] modArray(double value, int mod, int div) { final long[] result = new long[mod]; if (result.length > 0) { result[0] = 100; if (result.length > 1) { final long base = ((long) (value / (mod * div))) * div; final long last = (long) (value - base * (mod - 1)); for (int i = 0; i < result.length; i++) { if (i == result.length - 1) { result[i] = last; } else { result[i] = base; } } } } return result; } /** * Calculate number of pages to contain items.<br/> * i.e. * pageSize=10, items=9, result=1. * pageSize=10, items=11, result=2. * * @param pageSize Max number of items for each page. * @param items Number of items to store in pages. * @return Number of pages. i.e. pageSize=10, items=9, result=1. pageSize=10, items=11, result=2. */ public static int paging(final int pageSize, final int items) { int result = 1; if (pageSize > 0 && pageSize < items) { result = items / pageSize; if (items % pageSize > 0) { result++; } } return result; } public static double progress(final int count, final int length, final int decimals) { final double result = round(progress(count, length), decimals); return result; } public static double progress(final int count, final int length) { return (double) count / (double) length; } /** * @see #matchType(double, Number...) */ public static Number matchType(Number in, double out) { return matchType(out, new Number[]{in}); } /** * @see #matchType(double, Number...) */ public static Number matchType(Number in1, Number in2, double out) { return matchType(out, new Number[]{in1, in2}); } /** * Takes the original argument(s) and returns the resulting value as * an instance of the best matching type (Integer, Long, or Double). * If either an argument or the result is not an integer (i.e. has no * decimal when rendered) the result will be returned as a Double. * If not and the result is < -2147483648 or > 2147483647, then a * Long will be returned. Otherwise, an Integer will be returned. */ public static Number matchType(double out, Number... in) { //NOTE: if we just checked class types, we could miss custom // extensions of java.lang.Number, and if we only checked // the mathematical value, $math.div('3.0', 1) would render // as '3'. To get the expected result, we check what we're // concerned about: the rendered string. // first check if the result is even a whole number boolean isIntegral = (java.lang.Math.rint(out) == out); if (isIntegral) { for (Number n : in) { if (n == null) { break; } else if (hasFloatingPoint(n.toString())) { isIntegral = false; break; } } } if (!isIntegral) { return new Double(out); } else if (out > Integer.MAX_VALUE || out < Integer.MIN_VALUE) { return Long.valueOf((long) out); } else { return Integer.valueOf((int) out); } } public static boolean hasFloatingPoint(final String value) { return value.indexOf('.') >= 0; } // -------------------------------------------------------------------- // S T A T I C // -------------------------------------------------------------------- private static Logger getLogger() { return LoggingUtils.getLogger(MathUtils.class); } }