All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.objectlab.kit.util.BigDecimalUtil Maven / Gradle / Ivy

/*
 * 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.
 *
 * Click here to learn more
 *           ___  _     _           _   _          _
 *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
 *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
 *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
 *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
 *                   |__/
 *
 *                     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.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 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 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) {
        if (isNotZero(value)) {
            return BigDecimal.ONE.divide(value, scale, BigDecimal.ROUND_HALF_UP);
        }
        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.
     * 

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. *

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. *

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. *

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(new BigDecimal(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. *

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. *

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(new BigDecimal(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(new BigDecimal(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(new BigDecimal(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)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy