Repository: commons-lang Updated Branches: refs/heads/master 08aa21f92 -> b31877a46
LANG-1408: Rounding utilities for converting to BigDecimal Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/b31877a4 Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/b31877a4 Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/b31877a4 Branch: refs/heads/master Commit: b31877a46009d5ee52af9b5f737dabe241689931 Parents: 08aa21f Author: Rob Tompkins <chtom...@apache.org> Authored: Tue Aug 14 15:48:19 2018 -0400 Committer: Rob Tompkins <chtom...@apache.org> Committed: Tue Aug 14 15:48:19 2018 -0400 ---------------------------------------------------------------------- src/changes/changes.xml | 1 + .../apache/commons/lang3/math/NumberUtils.java | 162 +++++++++++++++++- .../commons/lang3/math/NumberUtilsTest.java | 164 +++++++++++++++++++ 3 files changed, 325 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-lang/blob/b31877a4/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index ee04711..bbb5f34 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -71,6 +71,7 @@ The <action> type attribute can be add,update,fix,remove. <action issue="LANG-1392" type="add" dev="pschumacher" due-to="Jeff Nelson">Methods for getting first non empty or non blank value</action> <action issue="LANG-1405" type="update" dev="ggregory" due-to="Lars Grefer">Remove checks for java versions below the minimum supported one</action> <action issue="LANG-1402" type="update" dev="chtompki" due-to="Mark Dacek">Null/index safe get methods for ArrayUtils</action> + <action issue="LANG-1408" type="add" dev="chtompki">Rounding utilities for converting to BigDecimal</action> </release> <release version="3.7" date="2017-11-04" description="New features and bug fixes. Requires Java 7, supports Java 8, 9, 10."> http://git-wip-us.apache.org/repos/asf/commons-lang/blob/b31877a4/src/main/java/org/apache/commons/lang3/math/NumberUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/lang3/math/NumberUtils.java b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java index 5d17a5b..821ee66 100644 --- a/src/main/java/org/apache/commons/lang3/math/NumberUtils.java +++ b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java @@ -20,6 +20,7 @@ import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; +import java.math.RoundingMode; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -40,6 +41,8 @@ public class NumberUtils { public static final Integer INTEGER_ZERO = Integer.valueOf(0); /** Reusable Integer constant for one. */ public static final Integer INTEGER_ONE = Integer.valueOf(1); + /** Reusable Integer constant for two */ + public static final Integer INTEGER_TWO = Integer.valueOf(2); /** Reusable Integer constant for minus one. */ public static final Integer INTEGER_MINUS_ONE = Integer.valueOf(-1); /** Reusable Short constant for zero. */ @@ -298,7 +301,7 @@ public class NumberUtils { * <code>0.0d</code> if the <code>BigDecimal</code> is <code>null</code>. * @since 3.8 */ - public static double toDouble(BigDecimal value) { + public static double toDouble(final BigDecimal value) { return toDouble(value, 0.0d); } @@ -319,7 +322,7 @@ public class NumberUtils { * defaultValue if the <code>BigDecimal</code> is <code>null</code>. * @since 3.8 */ - public static double toDouble(BigDecimal value, double defaultValue) { + public static double toDouble(final BigDecimal value, final double defaultValue) { return value == null ? defaultValue : value.doubleValue(); } @@ -422,6 +425,161 @@ public class NumberUtils { } } + /** + * Convert a <code>BigDecimal</code> to a <code>BigDecimal</code> with a scale of + * two that has been rounded using <code>RoundingMode.HALF_EVEN</code>. If the supplied + * <code>value</code> is null, then <code>BigDecimal.ZERO</code> is returned. + * + * <p>Note, the scale of a <code>BigDecimal</code> is the number of digits to the right of the + * decimal point.</p> + * + * @param value the <code>BigDecimal</code> to convert, may be null. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(BigDecimal value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a <code>BigDecimal</code> to a <code>BigDecimal</code> whose scale is the + * specified value with a <code>RoundingMode</code> applied. If the input <code>value</code> + * is <code>null</code>, we simply return <code>BigDecimal.ZERO</code>. + * + * @param value the <code>BigDecimal</code> to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(BigDecimal value, int scale, RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return value.setScale( + scale, + (roundingMode == null) ? RoundingMode.HALF_EVEN : roundingMode + ); + } + + /** + * Convert a <code>Float</code> to a <code>BigDecimal</code> with a scale of + * two that has been rounded using <code>RoundingMode.HALF_EVEN</code>. If the supplied + * <code>value</code> is null, then <code>BigDecimal.ZERO</code> is returned. + * + * <p>Note, the scale of a <code>BigDecimal</code> is the number of digits to the right of the + * decimal point.</p> + * + * @param value the <code>Float</code> to convert, may be null. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(Float value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a <code>Float</code> to a <code>BigDecimal</code> whose scale is the + * specified value with a <code>RoundingMode</code> applied. If the input <code>value</code> + * is <code>null</code>, we simply return <code>BigDecimal.ZERO</code>. + * + * @param value the <code>Float</code> to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(Float value, int scale, RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return toScaledBigDecimal( + BigDecimal.valueOf(value), + scale, + roundingMode + ); + } + + /** + * Convert a <code>Double</code> to a <code>BigDecimal</code> with a scale of + * two that has been rounded using <code>RoundingMode.HALF_EVEN</code>. If the supplied + * <code>value</code> is null, then <code>BigDecimal.ZERO</code> is returned. + * + * <p>Note, the scale of a <code>BigDecimal</code> is the number of digits to the right of the + * decimal point.</p> + * + * @param value the <code>Double</code> to convert, may be null. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(Double value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a <code>Double</code> to a <code>BigDecimal</code> whose scale is the + * specified value with a <code>RoundingMode</code> applied. If the input <code>value</code> + * is <code>null</code>, we simply return <code>BigDecimal.ZERO</code>. + * + * @param value the <code>Double</code> to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(Double value, int scale, RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return toScaledBigDecimal( + BigDecimal.valueOf(value), + scale, + roundingMode + ); + } + + /** + * Convert a <code>String</code> to a <code>BigDecimal</code> with a scale of + * two that has been rounded using <code>RoundingMode.HALF_EVEN</code>. If the supplied + * <code>value</code> is null, then <code>BigDecimal.ZERO</code> is returned. + * + * <p>Note, the scale of a <code>BigDecimal</code> is the number of digits to the right of the + * decimal point.</p> + * + * @param value the <code>String</code> to convert, may be null. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(String value) { + return toScaledBigDecimal(value, INTEGER_TWO, RoundingMode.HALF_EVEN); + } + + /** + * Convert a <code>String</code> to a <code>BigDecimal</code> whose scale is the + * specified value with a <code>RoundingMode</code> applied. If the input <code>value</code> + * is <code>null</code>, we simply return <code>BigDecimal.ZERO</code>. + * + * @param value the <code>String</code> to convert, may be null. + * @param scale the number of digits to the right of the decimal point. + * @param roundingMode a rounding behavior for numerical operations capable of + * discarding precision. + * @return the scaled, with appropriate rounding, <code>BigDecimal</code>. + * @since 3.8 + */ + public static BigDecimal toScaledBigDecimal(String value, int scale, RoundingMode roundingMode) { + if (value == null) { + return BigDecimal.ZERO; + } + return toScaledBigDecimal( + createBigDecimal(value), + scale, + roundingMode + ); + } + //----------------------------------------------------------------------- // must handle Long, Float, Integer, Float, Short, // BigDecimal, BigInteger and Byte http://git-wip-us.apache.org/repos/asf/commons-lang/blob/b31877a4/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java b/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java index c819765..468d91e 100644 --- a/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java @@ -27,6 +27,7 @@ import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; +import java.math.RoundingMode; import org.junit.Test; /** @@ -238,6 +239,169 @@ public class NumberUtilsTest { assertTrue("toShort(String,short) 2 failed", NumberUtils.toShort("1234.5", (short) 5) == 5); } + /** + * Test for {@link NumberUtils#toScaledBigDecimal(BigDecimal)}. + */ + @Test + public void testToScaledBigDecimalBigDecimal() { + assertTrue("toScaledBigDecimal(BigDecimal) 1 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(123.456)).equals(BigDecimal.valueOf(123.46))); + // Test RoudingMode.HALF_EVEN default rounding. + assertTrue("toScaledBigDecimal(BigDecimal) 2 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.515)).equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(BigDecimal) 3 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.525)).equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(BigDecimal) 4 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.525)) + .multiply(BigDecimal.valueOf(100)).toString() + .equals("2352.00")); + assertTrue("toScaledBigDecimal(BigDecimal) 5 failed", + NumberUtils.toScaledBigDecimal((BigDecimal) null).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(BigDecimal, int, RoundingMode)}. + */ + @Test + public void testToScaledBigDecimalBigDecimalIRM() { + assertTrue("toScaledBigDecimal(BigDecimal, int, RoudingMode) 1 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(123.456), 1, RoundingMode.CEILING).equals(BigDecimal.valueOf(123.5))); + assertTrue("toScaledBigDecimal(BigDecimal, int, RoudingMode) 2 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.5159), 3, RoundingMode.FLOOR).equals(BigDecimal.valueOf(23.515))); + assertTrue("toScaledBigDecimal(BigDecimal, int, RoudingMode) 3 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.525), 2, RoundingMode.HALF_UP).equals(BigDecimal.valueOf(23.53))); + assertTrue("toScaledBigDecimal(BigDecimal, int, RoudingMode) 4 failed", + NumberUtils.toScaledBigDecimal(BigDecimal.valueOf(23.521), 4, RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(1000)) + .toString() + .equals("23521.0000")); + assertTrue("toScaledBigDecimal(BigDecimal, int, RoudingMode) 5 failed", + NumberUtils.toScaledBigDecimal((BigDecimal) null, 2, RoundingMode.HALF_UP).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(Float)}. + */ + @Test + public void testToScaledBigDecimalFloat() { + assertTrue("toScaledBigDecimal(Float) 1 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(123.456f)).equals(BigDecimal.valueOf(123.46))); + // Test RoudingMode.HALF_EVEN default rounding. + assertTrue("toScaledBigDecimal(Float) 2 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(23.515f)).equals(BigDecimal.valueOf(23.51))); + // Note. NumberUtils.toScaledBigDecimal(Float.valueOf(23.515f)).equals(BigDecimal.valueOf(23.51)) + // because of roundoff error. It is ok. + assertTrue("toScaledBigDecimal(Float) 3 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(23.525f)).equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(Float) 4 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(23.525f)) + .multiply(BigDecimal.valueOf(100)).toString() + .equals("2352.00")); + assertTrue("toScaledBigDecimal(Float) 5 failed", + NumberUtils.toScaledBigDecimal((Float) null).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(Float, int, RoundingMode)}. + */ + @Test + public void testToScaledBigDecimalFloatIRM() { + assertTrue("toScaledBigDecimal(Float, int, RoudingMode) 1 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(123.456f), 1, RoundingMode.CEILING).equals(BigDecimal.valueOf(123.5))); + assertTrue("toScaledBigDecimal(Float, int, RoudingMode) 2 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(23.5159f), 3, RoundingMode.FLOOR).equals(BigDecimal.valueOf(23.515))); + // The following happens due to roundoff error. We're ok with this. + assertTrue("toScaledBigDecimal(Float, int, RoudingMode) 3 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(23.525f), 2, RoundingMode.HALF_UP).equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(Float, int, RoudingMode) 4 failed", + NumberUtils.toScaledBigDecimal(Float.valueOf(23.521f), 4, RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(1000)) + .toString() + .equals("23521.0000")); + assertTrue("toScaledBigDecimal(Float, int, RoudingMode) 5 failed", + NumberUtils.toScaledBigDecimal((Float) null, 2, RoundingMode.HALF_UP).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(Double)}. + */ + @Test + public void testToScaledBigDecimalDouble() { + assertTrue("toScaledBigDecimal(Double) 1 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(123.456d)).equals(BigDecimal.valueOf(123.46))); + // Test RoudingMode.HALF_EVEN default rounding. + assertTrue("toScaledBigDecimal(Double) 2 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(23.515d)).equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(Double) 3 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(23.525d)).equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(Double) 4 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(23.525d)) + .multiply(BigDecimal.valueOf(100)).toString() + .equals("2352.00")); + assertTrue("toScaledBigDecimal(Double) 5 failed", + NumberUtils.toScaledBigDecimal((Double) null).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(Double, int, RoundingMode)}. + */ + @Test + public void testToScaledBigDecimalDoubleIRM() { + assertTrue("toScaledBigDecimal(Double, int, RoudingMode) 1 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(123.456d), 1, RoundingMode.CEILING).equals(BigDecimal.valueOf(123.5))); + assertTrue("toScaledBigDecimal(Double, int, RoudingMode) 2 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(23.5159d), 3, RoundingMode.FLOOR).equals(BigDecimal.valueOf(23.515))); + assertTrue("toScaledBigDecimal(Double, int, RoudingMode) 3 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(23.525d), 2, RoundingMode.HALF_UP).equals(BigDecimal.valueOf(23.53))); + assertTrue("toScaledBigDecimal(Double, int, RoudingMode) 4 failed", + NumberUtils.toScaledBigDecimal(Double.valueOf(23.521d), 4, RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(1000)) + .toString() + .equals("23521.0000")); + assertTrue("toScaledBigDecimal(Double, int, RoudingMode) 5 failed", + NumberUtils.toScaledBigDecimal((Double) null, 2, RoundingMode.HALF_UP).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(Double)}. + */ + @Test + public void testToScaledBigDecimalString() { + assertTrue("toScaledBigDecimal(String) 1 failed", + NumberUtils.toScaledBigDecimal("123.456").equals(BigDecimal.valueOf(123.46))); + // Test RoudingMode.HALF_EVEN default rounding. + assertTrue("toScaledBigDecimal(String) 2 failed", + NumberUtils.toScaledBigDecimal("23.515").equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(String) 3 failed", + NumberUtils.toScaledBigDecimal("23.525").equals(BigDecimal.valueOf(23.52))); + assertTrue("toScaledBigDecimal(String) 4 failed", + NumberUtils.toScaledBigDecimal("23.525") + .multiply(BigDecimal.valueOf(100)).toString() + .equals("2352.00")); + assertTrue("toScaledBigDecimal(String) 5 failed", + NumberUtils.toScaledBigDecimal((String) null).equals(BigDecimal.ZERO)); + } + + /** + * Test for {@link NumberUtils#toScaledBigDecimal(Double, int, RoundingMode)}. + */ + @Test + public void testToScaledBigDecimalStringIRM() { + assertTrue("toScaledBigDecimal(String, int, RoudingMode) 1 failed", + NumberUtils.toScaledBigDecimal("123.456", 1, RoundingMode.CEILING).equals(BigDecimal.valueOf(123.5))); + assertTrue("toScaledBigDecimal(String, int, RoudingMode) 2 failed", + NumberUtils.toScaledBigDecimal("23.5159", 3, RoundingMode.FLOOR).equals(BigDecimal.valueOf(23.515))); + assertTrue("toScaledBigDecimal(String, int, RoudingMode) 3 failed", + NumberUtils.toScaledBigDecimal("23.525", 2, RoundingMode.HALF_UP).equals(BigDecimal.valueOf(23.53))); + assertTrue("toScaledBigDecimal(String, int, RoudingMode) 4 failed", + NumberUtils.toScaledBigDecimal("23.521", 4, RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(1000)) + .toString() + .equals("23521.0000")); + assertTrue("toScaledBigDecimal(String, int, RoudingMode) 5 failed", + NumberUtils.toScaledBigDecimal((String) null, 2, RoundingMode.HALF_UP).equals(BigDecimal.ZERO)); + } + @Test public void testCreateNumber() { // a lot of things can go wrong