/* * eGov suite of products aim to improve the internal efficiency,transparency, * accountability and the service delivery of the government organizations. * * Copyright (C) <2015> eGovernments Foundation * * The updated version of eGov suite of products as by eGovernments Foundation * is available at http://www.egovernments.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ or * http://www.gnu.org/licenses/gpl.html . * * In addition to the terms of the GPL license to be adhered to in using this * program, the following additional terms are to be complied with: * * 1) All versions of this program, verbatim or modified must carry this * Legal Notice. * * 2) Any misrepresentation of the origin of the material is prohibited. It * is required that all modified versions of this material be marked in * reasonable ways as different from the original version. * * 3) This license does not grant any rights to any user of the program * with regards to rights under trademark law for use of the trade names * or trademarks of eGovernments Foundation. * * In case of any queries, you can reach eGovernments Foundation at contact@egovernments.org. */ package org.egov.infra.utils; import java.math.BigDecimal; /** * Utility methods for money manipulation. * * @author Sunil D'Monte */ public class MoneyUtils { private static final BigDecimal HUNDRED = new BigDecimal("100"); private static final int DECIMALS = 0; private static final int DECIMALS2 = 2; private static final int ROUNDING_MODE = BigDecimal.ROUND_HALF_UP; /** * Splits up an amount into multiple amounts according to the specified * weightage. * <P> * E.g. An amount of 100 and weights (3, 7) will give (30, 70). * <P> * The algorithm used is the one described at * http://nida.se/patterns/poeaa/money.html. It guarantees that the sum of * the splits will exactly equal the input amount, even for fractional * cases: * <P> * E.g. An amount of 100 and weights (3, 8) will give (28, 72). * <P> * In such cases, the individual split amounts might not exactly match the * number you'd get from floating-point calculation (27.272 and 72.727 in * the example above). With the input amount in paise, the individual split * amounts will be at most 1 paisa off. * <P> * It is the job of the caller to do the "massaging" of rupee amounts into * paise amounts, and vice-versa for the output. * <P> * See the unit tests for more examples. * * @param amountInPaise * Has to be whole number, so if your amount is Rs. 100.342, * you'll have to convert that to 10034 paise. * @param weights * Numbers that specify the weight given to each split. They need * not add up to 100% (e.g. 3, 3, 4) - any arbitrary weights will * do. Also note that (2, 10, 16) is identical to (1, 5, 8). * @return The split amounts. If no weights or a single weight is input, the * input amount will be returned. */ public static long[] allocate(final long amountInPaise, final long[] weights) { if (weights.length == 0) return new long[] { amountInPaise }; final long[] splits = new long[weights.length]; long totalWeight = 0; for (final long w : weights) totalWeight = totalWeight + w; long remainder = amountInPaise; for (int i = 0; i < splits.length; i++) { // Compute the allocation using long arithmetic - e.g. // 100 * 3 / 7 will give 42, not 42.857 splits[i] = amountInPaise * weights[i] / totalWeight; remainder = remainder - splits[i]; } // Distribute the remainder amongst the splits, one paisa at a time. // Note that the // remainder will always be in the range [0, number-of-splits]. This is // because of the long // arithmetic - each allocation computed above will be < 1 off the real // value, // and there are (number-of-splits) allocations. for (int i = 0; i < remainder; i++) splits[i] = splits[i] + 1; return splits; } /** * This variant of the allocate method allows the original amount to be * input as a rupee amount i.e. as a BigDecimal, and receive the output * splits as BigDecimal amounts too. If the input amount is more than 2 * decimal places e.g. Rs. 150.632, it gets rounded to 2 decimal places * (using "half-up" rounding) and it is the rounded amount that gets * allocated. * * @param amountInRupees * @param weights * @return The splits, also as BigDecimals i.e. rupee amounts. Returns null * if the input amount is null. If no weights or a single weight is * input, the input amount will be returned. */ public static BigDecimal[] allocate(final BigDecimal amountInRupees, final long[] weights) { if (amountInRupees == null) return null; final BigDecimal[] resultInRupees = new BigDecimal[weights.length]; final long amountInPaise = roundAndConvertToPaise(amountInRupees); final long[] resultsInPaise = allocate(amountInPaise, weights); for (int i = 0; i < resultsInPaise.length; i++) resultInRupees[i] = BigDecimal.valueOf(resultsInPaise[i]).divide(HUNDRED); return resultInRupees; } /** * Converts a rupee amount in BigDecimal to a paise amount as an integer * (long). E.g. 150 -> 15000 150.23 -> 15023 150.234 -> 15023 150.235 -> * 15024 150.236 -> 15024 * * @param rupees * @return */ public static long roundAndConvertToPaise(BigDecimal rupees) { rupees = roundOffTwo(rupees); final long amountInPaise = rupees.multiply(HUNDRED).longValueExact(); return amountInPaise; } /** * Round off to two decimal big decimal value. * * @param amount * the amount * @return the big decimal */ public static BigDecimal roundOffTwo(final BigDecimal amount) { return amount.setScale(DECIMALS2, ROUNDING_MODE); } /** * This method is a utility method, which takes a BigDecimal and rounds that * to 0 places. * * @param amount * the amount * @return BigDecimal rounded off to 0 places */ public static BigDecimal roundOff(final BigDecimal amount) { return amount.setScale(DECIMALS, ROUNDING_MODE); } public static boolean isInteger(final String str) { try { Integer.parseInt(str); return true; } catch (final NumberFormatException nfe) { return false; } } }