/* * ObjectLab, http://www.objectlab.co.uk/open is sponsoring the ObjectLab Kit. * * Based in London, we are world leaders in the design and development * of bespoke applications for the securities financing markets. * * <a href="http://www.objectlab.co.uk/open">Click here to learn more</a> * ___ _ _ _ _ _ * / _ \| |__ (_) ___ ___| |_| | __ _| |__ * | | | | '_ \| |/ _ \/ __| __| | / _` | '_ \ * | |_| | |_) | | __/ (__| |_| |__| (_| | |_) | * \___/|_.__// |\___|\___|\__|_____\__,_|_.__/ * |__/ * * www.ObjectLab.co.uk * * $Id: AbstractDateCalculator.java 309 2010-03-23 21:01:49Z marchy $ * * Copyright 2006 the original author or authors. * * 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 net.objectlab.kit.util; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.function.Consumer; /** * @author Benoit Xhenseval * */ public final class BigDecimalUtil { private static final double ROUNDING_UP_FLOAT = 0.5d; private static final int MAX_SCALE_FOR_INVERSE = 20; private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(); private static final BigDecimal SQRT_DIG = new BigDecimal(8); private static final BigDecimal SQRT_PRE = BigDecimal.TEN.pow(SQRT_DIG.intValue()); private BigDecimalUtil() { } /** * If the BigDecimal is not null call the consumer (depends on JDK8+) * @param bd the BigDecimal * @param consumer of the value, called if not null * @return true if consumed * @since 1.4.0 */ public static boolean ifNotNull(final BigDecimal bd, final Consumer<BigDecimal> consumer) { if (bd != null) { consumer.accept(bd); return true; } return false; } /** * Convenience method to create a BigDecimal with a String, can be statically imported. * @param val a string representing the BigDecimal * @return the new BigDecimal */ public static BigDecimal bd(final String val) { return new BigDecimal(val); } /** * Return the inverse of value if not null or zero, using scale. * @param value the nullable BigDecimal * @param scale scale for the result if value is not null * @return 1 / value (if not null of zero) */ public static BigDecimal inverse(final BigDecimal value, final int scale) { return inverse(value, scale, BigDecimal.ROUND_HALF_UP); } /** * Return the inverse of value if not null or zero, using scale. * @param value the nullable BigDecimal * @param scale scale for the result if value is not null * @param rounding the rounding (see BigDecimal) * @return 1 / value (if not null of zero) */ public static BigDecimal inverse(final BigDecimal value, final int scale, final int rounding) { if (isNotZero(value)) { return BigDecimal.ONE.divide(value, scale, rounding); } return null; } /** * Return the inverse of value if no tnull of zero * @param value the nullable BigDecimal * @return 1 / value (if not null of zero) */ public static BigDecimal inverse(final BigDecimal value) { if (isNotZero(value)) { return BigDecimal.ONE.setScale(MAX_SCALE_FOR_INVERSE).divide(value, BigDecimal.ROUND_HALF_UP); } return null; } /** * @param value the nullable BigDecimal * @return true if value !=null and <gt; 0. */ public static boolean isNotZero(final BigDecimal value) { return value != null && value.signum() != 0; } /** * @param value the nullable BigDecimal * @return true if value !=null and 0. */ public static boolean isZero(final BigDecimal value) { return value != null && value.signum() == 0; } /** * @param value the nullable BigDecimal * @return true if value !=null and < 0. */ public static boolean isNegative(final BigDecimal value) { return value != null && value.signum() == -1; } /** * @param value the nullable BigDecimal * @return true if value !=null and >0. */ public static boolean isStrictlyPositive(final BigDecimal value) { return value != null && value.signum() == 1; } /** * @param value the nullable BigDecimal * @return true if value ==null OR 0. */ public static boolean isNullOrZero(final BigDecimal value) { return value == null || value.signum() == 0; } /** * @param val1 the nullable BigDecimal * @param val2 the nullable BigDecimal * @return true if val1 == val2 (ignoring scale and null are treated as 0) */ public static boolean isSameValue(final BigDecimal val1, final BigDecimal val2) { return val1 == null && val2 == null || val1 != null && val2 != null && val1.compareTo(val2) == 0; } /** * @param val1 the nullable BigDecimal * @param val2 the nullable BigDecimal * @return true if val1 == val2 (ignoring scale and null are treated as 0) */ public static boolean isSameValueTreatNullAsZero(final BigDecimal val1, final BigDecimal val2) { return val1 == null && val2 == null || signum(val1) == 0 && signum(val2) == 0 || val1 != null && val2 != null && val1.compareTo(val2) == 0; } /** * Add 2 BigDecimal safely (i.e. handles nulls as zeros) * @param v1 the nullable BigDecimal * @param v2 the nullable BigDecimal * @return the sum of the 2 BigDecimal */ private static BigDecimal doAdd(final BigDecimal v1, final BigDecimal v2) { BigDecimal total = v1; if (v1 != null && v2 != null) { total = v1.add(v2); } else if (v2 != null) { total = v2; } return total; } /** * Add n BigDecimal safely (i.e. handles nulls) * @param start initial BigDecimal * @param values series of BigDecimals can be null/empty * @return the sum of the n non null BigDecimals */ public static BigDecimal add(final BigDecimal start, final BigDecimal... values) { BigDecimal total = start != null ? start : BigDecimal.ZERO; if (values != null) { for (final BigDecimal v : values) { total = doAdd(total, v); } } return total; } /** * Subtract n BigDecimal safely from the start value (i.e. handles nulls as zeros), returns 0 * @param start starting point, if null, use 0 * @param values series of BigDecimal to subtract from start, can be null / empty * @return start - the series of values */ public static BigDecimal subtract(final BigDecimal start, final BigDecimal... values) { BigDecimal total = start != null ? start : BigDecimal.ZERO; if (values != null) { for (final BigDecimal v : values) { total = doSubtract(total, v); } } return total; } /** * Subtract 2 BigDecimal safely (i.e. handles nulls) v1 - v2 */ private static BigDecimal doSubtract(final BigDecimal v1, final BigDecimal v2) { BigDecimal diff = v1; if (v1 != null && v2 != null) { diff = v1.subtract(v2); } else if (v2 != null) { diff = v2.negate(); } return diff; } /** * @return numerator / denominator if they are not null and the denominator is not zero, it returns null otherwise. */ public static BigDecimal divide(final BigDecimal numerator, final BigDecimal denominator, final int rounding) { BigDecimal diff = null; if (numerator != null && isNotZero(denominator)) { diff = numerator.divide(denominator, rounding); } return diff; } public static BigDecimal calculateWeight(final BigDecimal value, final BigDecimal total) { return BigDecimalUtil .setScale(BigDecimalUtil.divide(BigDecimalUtil.setScale(value, 9), BigDecimalUtil.setScale(total, 9), BigDecimal.ROUND_HALF_UP), 9); } /** * @return numerator / denominator if they are not null and the denominator is not zero, it returns null otherwise. */ public static BigDecimal divide(final int numeratorScale, final BigDecimal numerator, final BigDecimal denominator, final int rounding) { BigDecimal diff = null; if (numerator != null && isNotZero(denominator)) { diff = numerator.setScale(numeratorScale, rounding).divide(denominator, rounding); } return diff; } /** * @return numerator / denominator if they are not null and the denominator is not zero, it returns null otherwise. */ public static BigDecimal divide(final BigDecimal numerator, final BigDecimal denominator, final int scale, final int rounding) { BigDecimal diff = null; if (numerator != null && isNotZero(denominator)) { diff = numerator.divide(denominator, rounding); } return BigDecimalUtil.setScale(diff, scale, rounding); } public static BigDecimal multiply(final BigDecimal value, final BigDecimal multiplicand) { BigDecimal diff = null; if (value != null && multiplicand != null) { diff = value.multiply(multiplicand); } return diff; } public static BigDecimal multiply(final BigDecimal value, final BigDecimal... multiplicand) { BigDecimal diff = null; if (value != null && multiplicand != null) { diff = value; for (final BigDecimal bd : multiplicand) { if (bd != null) { diff = diff.multiply(bd); } } } return diff; } /** * Returns the ABS of the value, handles null. * @param value nullable BigDecimal * @return abs(value) or null if null */ public static BigDecimal abs(final BigDecimal value) { return value != null ? value.abs() : null; } /** * Returns the negate of the value, handles null. * @param value nullable BigDecimal * @return -value or null if null */ public static BigDecimal negate(final BigDecimal value) { return value != null ? value.negate() : null; } /** * Returns the negate of the value if condition is true, handles null. * @param condition triggers negate * @param value nullable BigDecimal * @return -value if condition==true */ public static BigDecimal negateIfTrue(final boolean condition, final BigDecimal value) { return condition ? negate(value) : value; } /** * Check ABS values of v1 and v2. * @param v1 nullable BigDecimal * @param v2 nullable BigDecimal * @return false if the ABS value match! */ public static boolean isNotSameAbsValue(final BigDecimal v1, final BigDecimal v2) { return !isSameAbsValue(v1, v2); } /** * Check values of v1 and v2. * @param v1 nullable BigDecimal * @param v2 nullable BigDecimal * @return false if the value match! */ public static boolean isNotSameValue(final BigDecimal v1, final BigDecimal v2) { return !isSameValue(v1, v2); } /** * Check ABS values of v1 and v2. * @param v1 nullable BigDecimal * @param v2 nullable BigDecimal * @return true if the ABS value match! */ public static boolean isSameAbsValue(final BigDecimal v1, final BigDecimal v2) { return isSameValue(abs(v1), abs(v2)); } /** * @return 1 if v1 > v2 or v2==null and v2!=null * @return 0 if v1 == v2 or v1==null and v2==null * @return -1 if v1 < v2 or v1==null and v2!=null */ public static int compareTo(final BigDecimal v1, final BigDecimal v2) { int ret = 1; if (v1 != null && v2 != null) { ret = v1.compareTo(v2); } else if (v1 == null && v2 == null) { ret = 0; } else if (v1 == null) { ret = -1; } return ret; } /** * @return true if the ABS(v1) > ABS(v2) */ public static int absCompareTo(final BigDecimal v1, final BigDecimal v2) { return compareTo(abs(v1), abs(v2)); } /** * @return true if the ABS( ABS(v1) - ABS(v2) ) */ public static BigDecimal absDiff(final BigDecimal v1, final BigDecimal v2) { return abs(doSubtract(abs(v1), abs(v2))); } /** * Safe shift (check for null), shift RIGHT if shift>0. */ public static BigDecimal movePoint(final BigDecimal v1, final int shift) { return v1 == null ? null : v1.movePointRight(shift); } /** * returns a new BigDecimal with correct scale after being round to n dec places. * * @param bd value * @param numberOfDecPlaces number of dec place to round to * @param finalScale final scale of result (typically numberOfDecPlaces < finalScale); * @return new bd or null */ public static BigDecimal roundTo(final BigDecimal bd, final int numberOfDecPlaces, final int finalScale) { return setScale(setScale(bd, numberOfDecPlaces, BigDecimal.ROUND_HALF_UP), finalScale); } /** * returns a new BigDecimal with correct scale. * * @param bd * @return new bd or null */ public static BigDecimal setScale(final BigDecimal bd, final int scale) { return setScale(bd, scale, BigDecimal.ROUND_HALF_UP); } /** * returns a new BigDecimal with correct Scale. * * @param bd * @return new bd or null */ public static BigDecimal setScale(final BigDecimal bd, final Integer scale) { return setScale(bd, scale, BigDecimal.ROUND_HALF_UP); } /** * returns a new BigDecimal with correct Scales.PERCENT_SCALE. This is used * by the table renderer. * * @param bd * @return new bd or null */ public static BigDecimal setScale(final BigDecimal bd, final Integer scale, final int rounding) { if (bd != null && scale != null) { return bd.setScale(scale, rounding); } return null; } /** * If value is null return 0 otherwise the signum(). * @param value * @return */ public static int signum(final BigDecimal value) { return value == null ? 0 : value.signum(); } /** * @return true if both v1/v2 are null or same sign. */ public static boolean isSameSignum(final BigDecimal v1, final BigDecimal v2) { return signum(v1) == signum(v2); } /** * @return true if both v1.signum() != v2.signum() and NOT zero. */ public static boolean hasSignedFlippedAndNotZero(final BigDecimal v1, final BigDecimal v2) { final int v1Sign = signum(v1); final int v2Sign = signum(v2); return v1Sign != v2Sign && v1Sign != 0 && v2Sign != 0; } /** * @return true if v1.signum() != v2.signum(). */ public static boolean hasSignedChanged(final BigDecimal v1, final BigDecimal v2) { return signum(v1) != signum(v2); } /** * @param bd * @param lowerLimit * @param upperLimit * @return true if outside the range */ public static boolean isOutsideRange(final BigDecimal bd, final BigDecimal lowerLimit, final BigDecimal upperLimit) { return !isInsideInclusiveRange(bd, lowerLimit, upperLimit); } /** * @param bd * @param lowerLimit * @param upperLimit * @return true if inside the inclusive range */ public static boolean isInsideInclusiveRange(final BigDecimal bd, final BigDecimal lowerLimit, final BigDecimal upperLimit) { return ObjectUtil.noneNull(bd, lowerLimit, upperLimit) && bd.compareTo(lowerLimit) >= 0 && bd.compareTo(upperLimit) <= 0; } /** * @return o1 if not null, otherwise fallBack */ public static BigDecimal assignNonNull(final BigDecimal o1, final BigDecimal fallBack) { return o1 != null ? o1 : fallBack; } /** * Calculate the weight of the constituent and add it to the running weighted value. * runningWeightedVal + valueToAdd * weightForValueToAdd / totalWeight * @param runningWeightedVal * @param valueToAdd * @param weightForValueToAdd * @param totalWeight * @return */ public static BigDecimal addWeightedConstituent(final BigDecimal runningWeightedVal, final BigDecimal valueToAdd, final BigDecimal weightForValueToAdd, final BigDecimal totalWeight) { return BigDecimalUtil.doAdd(runningWeightedVal, BigDecimalUtil.divide(BigDecimalUtil.multiply(valueToAdd, BigDecimalUtil.abs(weightForValueToAdd)), BigDecimalUtil.abs(totalWeight), BigDecimal.ROUND_HALF_UP)); } /** * @return true if all values are either null or zero */ public static boolean allNullOrZero(final BigDecimal... values) { for (final BigDecimal bd : values) { if (!isNullOrZero(bd)) { return false; } } return true; } /** * return a Number formatted or empty string if null. * @param bd */ public static String format(final BigDecimal bd) { return bd != null ? NUMBER_FORMAT.format(bd) : ""; } /** * return a Number formatted or empty string if null. * @param bd */ public static String percentFormat(final BigDecimal bd) { return bd != null ? NUMBER_FORMAT.format(bd.movePointRight(2)) : ""; } /** * true if ABS((startValue-newValue)/startValue) <= abs(thresholdPercent) * @param startValue * @param newValue * @param thresholdPercent * @return */ public static boolean movedInsideThresholdPercentage(final BigDecimal startValue, final BigDecimal newValue, final BigDecimal thresholdPercent) { return !movedStrictlyOutsideThresholdPercentage(startValue, newValue, thresholdPercent); } /** * true if ABS((startValue-newValue)/startValue) > abs(thresholdPercent) * @param startValue * @param newValue * @param thresholdPercent * @return */ public static boolean movedStrictlyOutsideThresholdPercentage(final BigDecimal startValue, final BigDecimal newValue, final BigDecimal thresholdPercent) { final BigDecimal s = BigDecimalUtil.setScale(startValue, 10); final BigDecimal n = BigDecimalUtil.setScale(newValue, 10); final BigDecimal diff = BigDecimalUtil.divide(BigDecimalUtil.doSubtract(s, n), s, BigDecimal.ROUND_HALF_UP); return BigDecimalUtil.absCompareTo(diff, thresholdPercent) > 0; } private static double roundUp(final double n, final int p) { double retval; if (Double.isNaN(n) || Double.isInfinite(n)) { retval = Double.NaN; } else { if (p != 0) { final double temp = Math.pow(10, p); final double nat = Math.abs(n * temp); retval = sign(n) * (nat == (long) nat ? nat / temp : Math.round(nat + ROUNDING_UP_FLOAT) / temp); } else { final double na = Math.abs(n); retval = sign(n) * (na == (long) na ? na : (long) na + 1); } } return retval; } /** * Returns a value rounded to p digits after decimal. * If p is negative, then the number is rounded to * places to the left of the decimal point. eg. * 10.23 rounded to -1 will give: 10. If p is zero, * the returned value is rounded to the nearest integral * value. * <p>If n is negative, the resulting value is obtained * as the round-up value of absolute value of n multiplied * by the sign value of n (@see MathX.sign(double d)). * Thus, -0.8 rounded-down to p=0 will give 0 not -1. * <p>If n is NaN, returned value is NaN. * @param n * @param p * @return */ private static double roundDown(final double n, final int p) { double retval; if (Double.isNaN(n) || Double.isInfinite(n)) { retval = Double.NaN; } else { if (p != 0) { final double temp = Math.pow(10, p); retval = sign(n) * Math.round(Math.abs(n) * temp - ROUNDING_UP_FLOAT) / temp; } else { retval = (long) n; } } return retval; } private static short sign(final double d) { return (short) (d == 0 ? 0 : d < 0 ? -1 : 1); } /** * Returns a value rounded-up to p digits after decimal. * If p is negative, then the number is rounded to * places to the left of the decimal point. eg. * 10.23 rounded to -1 will give: 20. If p is zero, * the returned value is rounded to the nearest integral * value. * <p>If n is negative, the resulting value is obtained * as the round-up value of absolute value of n multiplied * by the sign value of n (@see MathX.sign(double d)). * Thus, -0.2 rounded-up to p=0 will give -1 not 0. * <p>If n is NaN, returned value is NaN. * @param n * @param p * @return */ public static BigDecimal roundUp(final BigDecimal n, final int p) { if (n == null) { return null; } final int scale = n.scale(); return BigDecimalUtil.setScale(BigDecimal.valueOf(roundUp(n.doubleValue(), p)), scale); } /** * Returns a value rounded to p digits after decimal. * If p is negative, then the number is rounded to * places to the left of the decimal point. eg. * 10.23 rounded to -1 will give: 10. If p is zero, * the returned value is rounded to the nearest integral * value. * <p>If n is negative, the resulting value is obtained * as the round-up value of absolute value of n multiplied * by the sign value of n (@see MathX.sign(double d)). * Thus, -0.8 rounded-down to p=0 will give 0 not -1. * <p>If n is NaN, returned value is NaN. * @param n * @param p * @return */ public static BigDecimal roundDown(final BigDecimal n, final int p) { if (n == null) { return null; } final int scale = n.scale(); return BigDecimalUtil.setScale(BigDecimal.valueOf(roundDown(n.doubleValue(), p)), scale); } public static BigDecimal roundUpForIncrement(final BigDecimal n, final BigDecimal increment) { if (n == null) { return null; } final int scale = n.scale(); final int p = (int) (increment != null ? -Math.log10(increment.abs().doubleValue()) : 0); return BigDecimalUtil.setScale(BigDecimal.valueOf(roundUp(n.doubleValue(), p)), scale); } public static BigDecimal roundDownForIncrement(final BigDecimal n, final BigDecimal increment) { if (n == null) { return null; } final int p = (int) (increment != null ? -Math.log10(increment.abs().doubleValue()) : 0); final int scale = n.scale(); return BigDecimalUtil.setScale(BigDecimal.valueOf(roundDown(n.doubleValue(), p)), scale); } /** * Return minimum if the value is < minimum. */ public static BigDecimal ensureMin(final BigDecimal minimum, final BigDecimal value) { return BigDecimalUtil.compareTo(minimum, value) == 1 ? minimum : value; } /** * Return a negative amount based on amount. */ public static BigDecimal forceNegative(final BigDecimal amount) { return BigDecimalUtil.negate(BigDecimalUtil.abs(amount)); } /** * Return a negative amount based on amount if true, otherwise return the ABS. */ public static BigDecimal forceNegativeIfTrue(final boolean condition, final BigDecimal amount) { return condition ? BigDecimalUtil.negate(BigDecimalUtil.abs(amount)) : BigDecimalUtil.abs(amount); } /** * @return return the min amount */ public static BigDecimal min(final BigDecimal v1, final BigDecimal v2) { if (v1 == null) { return v2; } else if (v2 == null) { return v1; } return v1.compareTo(v2) <= 0 ? v1 : v2; } /** * @return return the max amount */ public static BigDecimal max(final BigDecimal... v1) { if (v1 == null) { return null; } BigDecimal max = null; for (final BigDecimal bd : v1) { max = BigDecimalUtil.compareTo(max, bd) >= 0 ? max : bd; } return max; } /** * Move by 2 DEC place to the left and take the long value, this * takes care of values like 0.18 in fractions. */ public static long longForFraction(final BigDecimal v) { if (v == null) { return 0L; } return BigDecimalUtil.movePoint(v, 2).longValue(); } /** * @return true if abs(abs(v1)-abs(v2)) < abs(threshold) */ public static boolean isDiffMoreThanAbsThreshold(final BigDecimal v1, final BigDecimal v2, final BigDecimal threshold) { final BigDecimal diff = BigDecimalUtil.absDiff(v1, v2); return BigDecimalUtil.compareTo(diff, BigDecimalUtil.abs(threshold)) > 0; } public static double doubleValue(final BigDecimal val) { return val != null ? val.doubleValue() : 0.0; } /** * @return true if value !=null and <=0. */ public static boolean isZeroOrLess(final BigDecimal value) { return value != null && value.signum() <= 0; } /** * Return the decimal part of the value. * @param val */ public static BigDecimal decimalPart(final BigDecimal val) { return BigDecimalUtil.subtract(val, val.setScale(0, BigDecimal.ROUND_DOWN)); } /** * Private utility method used to compute the square root of a BigDecimal. * * @author Luciano Culacciatti * @see <a href="http://www.codeproject.com/Tips/257031/Implementing-SqrtRoot-in-BigDecimal">http://www.codeproject.com/Tips/257031/Implementing-SqrtRoot-in-BigDecimal</a> */ private static BigDecimal sqrtNewtonRaphson(final BigDecimal c, final BigDecimal xn, final BigDecimal precision) { BigDecimal fx = xn.pow(2).add(c.negate()); BigDecimal fpx = xn.multiply(new BigDecimal(2)); BigDecimal xn1 = fx.divide(fpx, 2 * SQRT_DIG.intValue(), RoundingMode.HALF_DOWN); xn1 = xn.add(xn1.negate()); BigDecimal currentSquare = xn1.pow(2); BigDecimal currentPrecision = currentSquare.subtract(c); currentPrecision = currentPrecision.abs(); if (currentPrecision.compareTo(precision) <= -1) { return xn1; } return sqrtNewtonRaphson(c, xn1, precision); } /** * Uses Newton Raphson to compute the square root of a BigDecimal. * * @param c the BigDecimal for which we want the SQRT. * @author Luciano Culacciatti * @see <a href="http://www.codeproject.com/Tips/257031/Implementing-SqrtRoot-in-BigDecimal">http://www.codeproject.com/Tips/257031/Implementing-SqrtRoot-in-BigDecimal</a> */ public static BigDecimal bigSqrt(final BigDecimal c) { return c != null ? c.signum() == 0 ? BigDecimal.ZERO : sqrtNewtonRaphson(c, BigDecimal.ONE, BigDecimal.ONE.divide(SQRT_PRE)) : null; } }