/* * The MIT License (MIT) * * Copyright (c) 2015 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.math; import com.wm.app.b2b.server.ServiceException; import permafrost.tundra.lang.ArrayHelper; import permafrost.tundra.lang.ExceptionHelper; import permafrost.tundra.lang.LocaleHelper; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; /** * A collection of convenience methods for working with decimals. */ public final class BigDecimalHelper { /** * Disallow instantiation of this class. */ private BigDecimalHelper() {} /** * Parses the given string and returns a decimal representation. * * @param decimalString A string to be parsed as a decimal. * @return A decimal representation of the given string. */ public static BigDecimal parse(String decimalString) { return parse(decimalString, (String)null); } /** * Parses the given string and returns a decimal representation. * * @param decimalString A string to be parsed as a decimal. * @param locale The locale to use if the string is only parseable in this localized format. * @return A decimal representation of the given string. */ public static BigDecimal parse(String decimalString, Locale locale) { return parse(decimalString, (String)null, locale); } /** * Parses the given string and returns a decimal representation. * * @param decimalString A string to be parsed as a decimal. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * string. * @return A decimal representation of the given string. */ public static BigDecimal parse(String decimalString, String decimalPattern) { return parse(decimalString, decimalPattern, null); } /** * Parses the given string and returns a decimal representation. * * @param decimalString A string to be parsed as a decimal. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * string. * @param locale The locale to use if the string is only parseable in this localized format. * @return A decimal representation of the given string. */ public static BigDecimal parse(String decimalString, String decimalPattern, Locale locale) { if (decimalString == null) return null; BigDecimal result; if (decimalPattern == null) { try { result = new BigDecimal(decimalString); } catch(NumberFormatException ex) { try { // try parsing with the number format for the default locale result = new BigDecimal(NumberFormat.getInstance(LocaleHelper.normalize(locale)).parse(decimalString).doubleValue()); } catch(ParseException pe) { throw new IllegalArgumentException(pe); } } } else { DecimalFormat parser = new DecimalFormat(decimalPattern); parser.setParseBigDecimal(true); try { result = (BigDecimal)parser.parse(decimalString); } catch (ParseException ex) { throw new IllegalArgumentException("Unparseable decimal: '" + decimalString + "' does not conform to pattern '" + decimalPattern + "'", ex); } } return result; } /** * Parses the given string and returns a decimal representation. * * @param decimalString A string to be parsed as a decimal. * @param decimalPatterns A list java.text.DecimalFormat pattern strings one of which describes the format of the * given decimal string. * @return A decimal representation of the given string. */ public static BigDecimal parse(String decimalString, String[] decimalPatterns) { return parse(decimalString, decimalPatterns, null); } /** * Parses the given string and returns a decimal representation. * * @param decimalString A string to be parsed as a decimal. * @param decimalPatterns A list java.text.DecimalFormat pattern strings one of which describes the format of the * given decimal string. * @param locale The locale to use if the string is only parseable in this localized format. * @return A decimal representation of the given string. */ public static BigDecimal parse(String decimalString, String[] decimalPatterns, Locale locale) { if (decimalString == null) return null; BigDecimal result = null; if (decimalPatterns == null || decimalPatterns.length == 0) { result = parse(decimalString, (String)null); } else { boolean parsed = false; for (String decimalPattern : decimalPatterns) { try { result = parse(decimalString, decimalPattern, locale); parsed = true; break; } catch (IllegalArgumentException ex) { // ignore } } if (!parsed) { throw new IllegalArgumentException("Unparseable decimal: '" + decimalString + "' does not conform to patterns [" + ArrayHelper.join(decimalPatterns, ", ") + "]"); } } return result; } /** * Parses the given strings and returns their decimal representations. * * @param decimals One or more strings to be parsed as a decimal. * @return A decimal representation of the given strings. */ public static BigDecimal[] parse(String[] decimals) { return parse(decimals, (String)null); } /** * Parses the given strings and returns their decimal representations. * * @param decimals One or more strings to be parsed as a decimal. * @param locale The locale to use if the string is only parseable in this localized format. * @return A decimal representation of the given strings. */ public static BigDecimal[] parse(String[] decimals, Locale locale) { return parse(decimals, (String)null, locale); } /** * Parses the given strings and returns their decimal representations. * * @param decimalStrings One or more strings to be parsed as a decimal. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * strings. * @return A decimal representation of the given strings. */ public static BigDecimal[] parse(String[] decimalStrings, String decimalPattern) { if (decimalStrings == null) return null; BigDecimal[] decimals = new BigDecimal[decimalStrings.length]; for (int i = 0; i < decimals.length; i++) { decimals[i] = parse(decimalStrings[i], decimalPattern); } return decimals; } /** * Parses the given strings and returns their decimal representations. * * @param decimalStrings One or more strings to be parsed as a decimal. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * strings. * @param locale The locale to use if the string is only parseable in this localized format. * @return A decimal representation of the given strings. */ public static BigDecimal[] parse(String[] decimalStrings, String decimalPattern, Locale locale) { if (decimalStrings == null) return null; BigDecimal[] decimals = new BigDecimal[decimalStrings.length]; for (int i = 0; i < decimals.length; i++) { decimals[i] = parse(decimalStrings[i], decimalPattern, locale); } return decimals; } /** * Parses the given strings and returns their decimal representations. * * @param decimalStrings One or more strings to be parsed as a decimal. * @param decimalPatterns A list of java.text.DecimalFormat pattern string one of which describes the format of the * given decimal strings. * @return A decimal representation of the given strings. */ public static BigDecimal[] parse(String[] decimalStrings, String[] decimalPatterns) { return parse(decimalStrings, decimalPatterns, null); } /** * Parses the given strings and returns their decimal representations. * * @param decimalStrings One or more strings to be parsed as a decimal. * @param decimalPatterns A list of java.text.DecimalFormat pattern string one of which describes the format of the * given decimal strings. * @param locale The locale to use if the string is only parseable in this localized format. * @return A decimal representation of the given strings. */ public static BigDecimal[] parse(String[] decimalStrings, String[] decimalPatterns, Locale locale) { if (decimalStrings == null) return null; BigDecimal[] decimals = new BigDecimal[decimalStrings.length]; for (int i = 0; i < decimals.length; i++) { decimals[i] = parse(decimalStrings[i], decimalPatterns, locale); } return decimals; } /** * Returns a string representation of the given decimal. * * @param decimal The decimal to convert to a string representation. * @return The string representation of the given decimal. */ public static String emit(BigDecimal decimal) { return emit(decimal, (String)null); } /** * Returns a string representation of the given decimal. * * @param decimal The decimal to convert to a string representation. * @param locale The locale to use for emitting a localized number format. * @return The string representation of the given decimal. */ public static String emit(BigDecimal decimal, Locale locale) { return emit(decimal, null, locale); } /** * Returns a string representation of the given decimal. * * @param decimal The decimal to convert to a string representation. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * strings. * @return The string representation of the given decimal. */ public static String emit(BigDecimal decimal, String decimalPattern) { return emit(decimal, decimalPattern, null); } /** * Returns a string representation of the given decimal. * * @param decimal The decimal to convert to a string representation. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * strings. * @param locale The locale to use for emitting a localized number format, if no pattern is specified. * @return The string representation of the given decimal. */ public static String emit(BigDecimal decimal, String decimalPattern, Locale locale) { if (decimal == null) return null; String output; if (decimalPattern == null) { if (locale == null) { output = decimal.toString(); } else { output = NumberFormat.getInstance(locale).format(decimal); } } else { output = new DecimalFormat(decimalPattern).format(decimal); } return output; } /** * Returns a string representation of the given list of decimals. * * @param decimals The list of decimals to convert to string representations. * @return The string representations of the given list of decimals. */ public static String[] emit(BigDecimal[] decimals) { return emit(decimals, (String)null); } /** * Returns a string representation of the given list of decimals. * * @param decimals The list of decimals to convert to string representations. * @param locale The locale to use for emitting a localized number format, if no pattern is specified. * @return The string representations of the given list of decimals. */ public static String[] emit(BigDecimal[] decimals, Locale locale) { return emit(decimals, null, locale); } /** * Returns a string representation of the given list of decimals. * * @param decimals The list of decimals to convert to string representations. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * strings. * @return The string representations of the given list of decimals. */ public static String[] emit(BigDecimal[] decimals, String decimalPattern) { return emit(decimals, decimalPattern, null); } /** * Returns a string representation of the given list of decimals. * * @param decimals The list of decimals to convert to string representations. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal * strings. * @param locale The locale to use for emitting a localized number format, if no pattern is specified. * @return The string representations of the given list of decimals. */ public static String[] emit(BigDecimal[] decimals, String decimalPattern, Locale locale) { if (decimals == null) return null; String[] strings = new String[decimals.length]; for (int i = 0; i < decimals.length; i++) { strings[i] = emit(decimals[i], decimalPattern, locale); } return strings; } /** * Formats the given decimal string according to the given pattern. * * @param input The decimal string * @param inputPattern The pattern the input string adheres to. * @param outputPattern The pattern the input string is reformatted to. * @return The given input string reformatted to the desired pattern. */ public static String format(String input, String inputPattern, String outputPattern) { return emit(parse(input, inputPattern), outputPattern); } /** * Formats the given decimal string according to the given pattern. * * @param input The decimal string * @param inputPatterns A list of patterns one of which the input string adheres to. * @param outputPattern The pattern the input string is reformatted to. * @return The given input string reformatted to the desired pattern. */ public static String format(String input, String[] inputPatterns, String outputPattern) { return emit(parse(input, inputPatterns), outputPattern); } /** * Returns a BigDecimal representation of the given object, if it an instance of java.lang.Number or a * java.lang.String which can be parsed as a decimal number. * * @param object An object to be converted to a BigDecimal. * @return A BigDecimal representation of the given object. */ public static BigDecimal normalize(Object object) { BigDecimal decimal = null; if (object instanceof BigDecimal) { decimal = (BigDecimal)object; } else if (object instanceof BigInteger) { decimal = new BigDecimal((BigInteger)object); } else if (object instanceof Number) { decimal = new BigDecimal(((Number)object).doubleValue()); } else if (object instanceof String) { decimal = parse((String)object); } return decimal; } /** * Returns BigDecimal representations of the given list of Objects. * * @param values The objects to convert to BigDecimal representations. * @return BigDecimal representations of the given objects. */ public static BigDecimal[] normalize(Object[] values) { if (values == null) return null; BigDecimal[] decimals = new BigDecimal[values.length]; for (int i = 0; i < values.length; i++) { decimals[i] = normalize(values[i]); } return decimals; } /** * Returns the absolute value of the given decimal number. * * @param decimal A decimal number. * @return The absolute value of the given decimal number. */ public static BigDecimal absolute(BigDecimal decimal) { return decimal == null ? null : decimal.abs(); } /** * Returns the absolute values of the given decimal numbers. * * @param decimals A list of decimal numbers. * @return The absolute values of the given list of decimal numbers. */ public static BigDecimal[] absolute(BigDecimal[] decimals) { if (decimals == null) return null; BigDecimal[] results = new BigDecimal[decimals.length]; for (int i = 0; i < decimals.length; i++) { results[i] = absolute(decimals[i]); } return results; } /** * Returns the negated value of the given decimal number. * * @param decimal A decimal number. * @return The negated value of the given decimal number. */ public static BigDecimal negate(BigDecimal decimal) { return decimal == null ? null : decimal.negate(); } /** * Returns the negated values of the given decimal numbers. * * @param decimals A list of decimal numbers. * @return The negated values of the given list of decimal numbers. */ public static BigDecimal[] negate(BigDecimal[] decimals) { if (decimals == null) return null; BigDecimal[] results = new BigDecimal[decimals.length]; for (int i = 0; i < decimals.length; i++) { results[i] = negate(decimals[i]); } return results; } /** * Returns the sum of all the given decimals. * * @param decimals The decimal numbers to be summed. * @return The sum of all the given decimal numbers. */ public static BigDecimal add(BigDecimal... decimals) { BigDecimal result = null; if (decimals != null) { for (BigDecimal operand : decimals) { if (operand != null) { if (result == null) { result = operand; } else { result = result.add(operand); } } } } return result; } /** * Subtracts one decimal from another returning the result. * * @param minuend The decimal to be subtracted from. * @param subtrahend The decimal to be subtracted. * @return The result of subtracting the subtrahend from the minuend. */ public static BigDecimal subtract(BigDecimal minuend, BigDecimal subtrahend) { BigDecimal result = null; if (minuend != null && subtrahend != null) { result = minuend.subtract(subtrahend); } return result; } /** * Returns the multiplication of all the given decimals. * * @param decimals The decimal numbers to be summed. * @return The multiplication of all the given decimal numbers. */ public static BigDecimal multiply(BigDecimal... decimals) { BigDecimal result = null; if (decimals != null) { for (BigDecimal operand : decimals) { if (operand != null) { if (result == null) { result = operand; } else { result = result.multiply(operand); } } } } return result; } /** * Divides the given dividend by the divisor. * * @param dividend The decimal to be divided. * @param divisor The decimal to divide by. * @param precision The number of decimal places preserved in the result. * @param roundingMode The rounding algorithm to use when rounding the result. * @return The result of dividing the dividend by the divisor. */ public static BigDecimal divide(BigDecimal dividend, BigDecimal divisor, int precision, RoundingMode roundingMode) { BigDecimal result = null; if (dividend != null && divisor != null) { result = dividend.divide(divisor, PrecisionHelper.normalize(precision, dividend, divisor), RoundingModeHelper.normalize(roundingMode)); } return result; } /** * Returns the exponentiation of the given base raised to power of the given exponent. * * @param base The decimal base to be raised to the power of the given exponent. * @param exponent The exponent to raise the given base to. * @return The result of raising the given base to the power of the given exponent. */ public static BigDecimal power(BigDecimal base, int exponent) { return base == null ? null : base.pow(exponent); } /** * Rounds the given decimal to the given precision with the given rounding algorithm. * * @param decimal A decimal to be rounded. * @param precision The number of decimal places to round to. * @param roundingMode The rounding algorithm to be used. * @return The given decimal rounded to the given precision using the given algorithm. */ public static BigDecimal round(BigDecimal decimal, int precision, RoundingMode roundingMode) { if (precision < 0 || decimal == null) return decimal; return decimal.setScale(PrecisionHelper.normalize(precision), RoundingModeHelper.normalize(roundingMode)); } /** * Rounds the given list of decimals to the given precision with the given rounding algorithm. * * @param decimals A list of decimals to be rounded. * @param precision The number of decimal places to round to. * @param roundingMode The rounding algorithm to be used. * @return The given list of decimals rounded to the given precision using the given algorithm. */ public static BigDecimal[] round(BigDecimal[] decimals, int precision, RoundingMode roundingMode) { if (decimals == null) return null; BigDecimal[] output = new BigDecimal[decimals.length]; for (int i = 0; i < decimals.length; i++) { output[i] = round(decimals[i], precision, roundingMode); } return output; } /** * Returns the largest of the given list of decimal numbers. * * @param decimals A list of decimal numbers. * @return The largest of the given numbers. */ public static BigDecimal maximum(BigDecimal... decimals) { BigDecimal result = null; if (decimals != null) { for (BigDecimal decimal : decimals) { if (decimal != null) { if (result == null) { result = decimal; } else { result = result.max(decimal); } } } } return result; } /** * Returns the smallest of the given list of decimal numbers. * * @param decimals A list of decimal numbers. * @return The smallest of the given numbers. */ public static BigDecimal minimum(BigDecimal... decimals) { BigDecimal result = null; if (decimals != null) { for (BigDecimal decimal : decimals) { if (decimal != null) { if (result == null) { result = decimal; } else { result = result.min(decimal); } } } } return result; } /** * Returns the average or mean from the given list of decimal numbers. * * @param precision The number of decimal places to round to. * @param roundingMode The rounding algorithm to be used. * @param decimals A list of decimal numbers. * @return The average or mean value of the given list of values. */ public static BigDecimal average(int precision, RoundingMode roundingMode, BigDecimal... decimals) { BigDecimal result = null; if (decimals != null) { int length = 0; for (BigDecimal decimal : decimals) { if (decimal != null) { if (result == null) { result = decimal; } else { result = result.add(decimal); } length++; } } if (result != null) { result = divide(result, new BigDecimal(length), precision, roundingMode); } } return result; } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @return True if the string can be parsed as a decimal number, otherwise false. */ public static boolean validate(String decimal) { return validate(decimal, null, null); } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal string. * @return True if the string can be parsed as a decimal number, otherwise false. */ public static boolean validate(String decimal, String decimalPattern) { return validate(decimal, decimalPattern, null); } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param locale The locale to use if the string is only parseable in this localized format. * @return True if the string can be parsed as a decimal number, otherwise false. */ public static boolean validate(String decimal, Locale locale) { return validate(decimal, null, locale); } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal string. * @param locale The locale to use if the string is only parseable in this localized format. * @return True if the string can be parsed as a decimal number, otherwise false. */ public static boolean validate(String decimal, String decimalPattern, Locale locale) { boolean result = false; try { result = validate(decimal, decimalPattern, locale, false); } catch (ServiceException ex) { // suppress the exception, which will never be thrown anyway } return result; } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param raise True if an exception should be thrown if the string is not a valid decimal number. * @return True if the string can be parsed as a decimal number, otherwise false. * @throws ServiceException If raise is true and the given string is not a valid decimal number. */ public static boolean validate(String decimal, boolean raise) throws ServiceException { return validate(decimal, null, null, raise); } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal string. * @param raise True if an exception should be thrown if the string is not a valid decimal number. * @return True if the string can be parsed as a decimal number, otherwise false. * @throws ServiceException If raise is true and the given string is not a valid decimal number. */ public static boolean validate(String decimal, String decimalPattern, boolean raise) throws ServiceException { return validate(decimal, decimalPattern, null, raise); } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param raise True if an exception should be thrown if the string is not a valid decimal number. * @param locale The locale to use if the string is only parseable in this localized format. * @return True if the string can be parsed as a decimal number, otherwise false. * @throws ServiceException If raise is true and the given string is not a valid decimal number. */ public static boolean validate(String decimal, Locale locale, boolean raise) throws ServiceException { return validate(decimal, null, locale, raise); } /** * Returns true if the given string can be parsed as a decimal number. * * @param decimal The string to validate. * @param decimalPattern A java.text.DecimalFormat pattern string describing the format of the given decimal string. * @param locale The locale to use if the string is only parseable in this localized format. * @param raise True if an exception should be thrown if the string is not a valid decimal number. * @return True if the string can be parsed as a decimal number, otherwise false. * @throws ServiceException If raise is true and the given string is not a valid decimal number. */ public static boolean validate(String decimal, String decimalPattern, Locale locale, boolean raise) throws ServiceException { boolean valid = false; try { if (decimal != null) { parse(decimal, decimalPattern, locale); valid = true; } } catch(Exception ex) { if (raise) ExceptionHelper.raise(ex); } return valid; } }