This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new a2b2b35  [LANG-1646] NumberUtils to return requested floating point 
type for zero
a2b2b35 is described below

commit a2b2b35ac3b969686c647e57f5ca3bacc63f2c56
Author: aherbert <aherb...@apache.org>
AuthorDate: Fri Mar 5 16:12:41 2021 +0000

    [LANG-1646] NumberUtils to return requested floating point type for zero
---
 src/changes/changes.xml                            |  3 +-
 .../org/apache/commons/lang3/math/NumberUtils.java | 70 +++++++++++-------
 .../apache/commons/lang3/math/NumberUtilsTest.java | 86 +++++++++++++++++++++-
 3 files changed, 131 insertions(+), 28 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 08fdbb6..92274b1 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -47,7 +47,8 @@ The <action> type attribute can be add,update,fix,remove.
 
   <release version="3.12.1" date="2021-MM-DD" description="New features and 
bug fixes (Java 8).">
     <!-- FIX -->
-    <action issue="LANG-1645" type="fix" dev="aherbert" due-to="Alex 
Herbert">NumberUtils to recognise hex integers prefixed with +</action>
+    <action issue="LANG-1645" type="fix" dev="aherbert" due-to="Alex 
Herbert">NumberUtils.createNumber to recognise hex integers prefixed with 
+.</action>
+    <action issue="LANG-1646" type="fix" dev="aherbert" due-to="Alex 
Herbert">NumberUtils.createNumber to return requested floating point type for 
zero.</action>
   </release>
 
   <release version="3.12.0" date="2021-02-26" description="New features and 
bug fixes (Java 8).">
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 ab321b0..4125fac 100644
--- a/src/main/java/org/apache/commons/lang3/math/NumberUtils.java
+++ b/src/main/java/org/apache/commons/lang3/math/NumberUtils.java
@@ -706,6 +706,8 @@ public class NumberUtils {
         // if both e and E are present, this is caught by the checks on expPos 
(which prevent IOOBE)
         // and the parsing which will detect if e or E appear in a number due 
to using the wrong offset
 
+        // Detect if the return type has been requested
+        final boolean requestType = !Character.isDigit(lastChar) && lastChar 
!= '.';
         if (decPos > -1) { // there is a decimal point
             if (expPos > -1) { // there is an exponent
                 if (expPos < decPos || expPos > length) { // prevents double 
exponent causing IOOBE
@@ -713,7 +715,8 @@ public class NumberUtils {
                 }
                 dec = str.substring(decPos + 1, expPos);
             } else {
-                dec = str.substring(decPos + 1);
+                // No exponent, but there may be a type character to remove
+                dec = str.substring(decPos + 1, requestType ? length - 1 : 
length);
             }
             mant = getMantissa(str, decPos);
         } else {
@@ -723,11 +726,12 @@ public class NumberUtils {
                 }
                 mant = getMantissa(str, expPos);
             } else {
-                mant = getMantissa(str);
+                // No decimal, no exponent, but there may be a type character 
to remove
+                mant = getMantissa(str, requestType ? length - 1 : length);
             }
             dec = null;
         }
-        if (!Character.isDigit(lastChar) && lastChar != '.') {
+        if (requestType) {
             if (expPos > -1 && expPos < length - 1) {
                 exp = str.substring(expPos + 1, length - 1);
             } else {
@@ -735,7 +739,6 @@ public class NumberUtils {
             }
             //Requesting a specific type..
             final String numeric = str.substring(0, length - 1);
-            final boolean allZeros = isAllZeros(mant) && isAllZeros(exp);
             switch (lastChar) {
                 case 'l' :
                 case 'L' :
@@ -755,7 +758,7 @@ public class NumberUtils {
                 case 'F' :
                     try {
                         final Float f = createFloat(str);
-                        if (!(f.isInfinite() || f.floatValue() == 0.0F && 
!allZeros)) {
+                        if (!(f.isInfinite() || f.floatValue() == 0.0F && 
!isZero(mant, dec))) {
                             //If it's too big for a float or the float value = 
0 and the string
                             //has non-zeros in it, then float does not have 
the precision we want
                             return f;
@@ -769,7 +772,7 @@ public class NumberUtils {
                 case 'D' :
                     try {
                         final Double d = createDouble(str);
-                        if (!(d.isInfinite() || d.doubleValue() == 0.0D && 
!allZeros)) {
+                        if (!(d.isInfinite() || d.doubleValue() == 0.0D && 
!isZero(mant, dec))) {
                             return d;
                         }
                     } catch (final NumberFormatException nfe) { // NOPMD
@@ -809,16 +812,15 @@ public class NumberUtils {
         }
 
         //Must be a Float, Double, BigDecimal
-        final boolean allZeros = isAllZeros(mant) && isAllZeros(exp);
         try {
             final Float f = createFloat(str);
             final Double d = createDouble(str);
             if (!f.isInfinite()
-                    && !(f.floatValue() == 0.0F && !allZeros)
+                    && !(f.floatValue() == 0.0F && !isZero(mant, dec))
                     && f.toString().equals(d.toString())) {
                 return f;
             }
-            if (!d.isInfinite() && !(d.doubleValue() == 0.0D && !allZeros)) {
+            if (!d.isInfinite() && !(d.doubleValue() == 0.0D && !isZero(mant, 
dec))) {
                 final BigDecimal b = createBigDecimal(str);
                 if (b.compareTo(BigDecimal.valueOf(d.doubleValue())) == 0) {
                     return d;
@@ -837,18 +839,6 @@ public class NumberUtils {
      * <p>Returns mantissa of the given number.</p>
      *
      * @param str the string representation of the number
-     * @return mantissa of the given number
-     */
-    private static String getMantissa(final String str) {
-        return getMantissa(str, str.length());
-    }
-
-    /**
-     * <p>Utility method for {@link #createNumber(java.lang.String)}.</p>
-     *
-     * <p>Returns mantissa of the given number.</p>
-     *
-     * @param str the string representation of the number
      * @param stopPos the position of the exponent or decimal point
      * @return mantissa of the given number
      */
@@ -860,11 +850,41 @@ public class NumberUtils {
     }
 
     /**
-     * <p>Utility method for {@link #createNumber(java.lang.String)}.</p>
+     * Utility method for {@link #createNumber(java.lang.String)}.
      *
-     * <p>Returns {@code true} if s is {@code null}.</p>
+     * <p>This will check if the magnitude of the number is zero by checking 
if there
+     * are only zeros before and after the decimal place.</p>
      *
-     * @param str  the String to check
+     * <p>Note: It is <strong>assumed</strong> that the input string has been 
converted
+     * to either a Float or Double with a value of zero when this method is 
called.
+     * This eliminates invalid input for example {@code ".", ".D", ".e0"}.</p>
+     *
+     * <p>Thus the method only requires checking if both arguments are null, 
empty or
+     * contain only zeros.</p>
+     *
+     * <p>Given {@code s = mant + "." + dec}:</p>
+     * <ul>
+     * <li>{@code true} if s is {@code "0.0"}
+     * <li>{@code true} if s is {@code "0."}
+     * <li>{@code true} if s is {@code ".0"}
+     * <li>{@code false} otherwise (this assumes {@code "."} is not possible)
+     * </ul>
+     *
+     * @param mant the mantissa decimal digits before the decimal point (sign 
must be removed; never null)
+     * @param dec the decimal digits after the decimal point (exponent and 
type specifier removed;
+     *            can be null)
+     * @return true if the magnitude is zero
+     */
+    private static boolean isZero(final String mant, String dec) {
+        return isAllZeros(mant) && isAllZeros(dec);
+    }
+
+    /**
+     * Utility method for {@link #createNumber(java.lang.String)}.
+     *
+     * <p>Returns {@code true} if s is {@code null} or empty.</p>
+     *
+     * @param str the String to check
      * @return if it is all zeros or {@code null}
      */
     private static boolean isAllZeros(final String str) {
@@ -876,7 +896,7 @@ public class NumberUtils {
                 return false;
             }
         }
-        return !str.isEmpty();
+        return true;
     }
 
     //-----------------------------------------------------------------------
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 db3d5d2..971bd5c 100644
--- a/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/math/NumberUtilsTest.java
@@ -68,11 +68,12 @@ public class NumberUtilsTest {
             + " for isCreatable/createNumber using \"" + val + "\" but got " + 
isValid + " and " + canCreate);
     }
 
+    @SuppressWarnings("deprecation")
     private void compareIsNumberWithCreateNumber(final String val, final 
boolean expected) {
-        final boolean isValid = NumberUtils.isCreatable(val);
+        final boolean isValid = NumberUtils.isNumber(val);
         final boolean canCreate = checkCreateNumber(val);
         assertTrue(isValid == expected && canCreate == expected, "Expecting " 
+ expected
-            + " for isCreatable/createNumber using \"" + val + "\" but got " + 
isValid + " and " + canCreate);
+            + " for isNumber/createNumber using \"" + val + "\" but got " + 
isValid + " and " + canCreate);
     }
 
     @Test
@@ -638,6 +639,22 @@ public class NumberUtilsTest {
         // Test with +2 in final digit (+1 does not cause roll-over to 
BigDecimal)
         assertEquals(new BigDecimal("1.7976931348623159e+308"), 
NumberUtils.createNumber("1.7976931348623159e+308"));
 
+        // Requested type is parsed as zero but the value is not zero
+        final Double nonZero1 = Double.valueOf(((double) Float.MIN_VALUE) / 2);
+        assertEquals(nonZero1, NumberUtils.createNumber(nonZero1.toString()));
+        assertEquals(nonZero1, NumberUtils.createNumber(nonZero1.toString() + 
"F"));
+        // Smallest double is 4.9e-324.
+        // Test a number with zero before and/or after the decimal place to 
hit edge cases.
+        final BigDecimal nonZero2 = new BigDecimal("4.9e-325");
+        assertEquals(nonZero2, NumberUtils.createNumber("4.9e-325"));
+        assertEquals(nonZero2, NumberUtils.createNumber("4.9e-325D"));
+        final BigDecimal nonZero3 = new BigDecimal("1e-325");
+        assertEquals(nonZero3, NumberUtils.createNumber("1e-325"));
+        assertEquals(nonZero3, NumberUtils.createNumber("1e-325D"));
+        final BigDecimal nonZero4 = new BigDecimal("0.1e-325");
+        assertEquals(nonZero4, NumberUtils.createNumber("0.1e-325"));
+        assertEquals(nonZero4, NumberUtils.createNumber("0.1e-325D"));
+
         assertEquals(Integer.valueOf(0x12345678), 
NumberUtils.createNumber("0x12345678"));
         assertEquals(Long.valueOf(0x123456789L), 
NumberUtils.createNumber("0x123456789"));
 
@@ -658,6 +675,55 @@ public class NumberUtilsTest {
     }
 
     /**
+     * LANG-1646: Support the requested Number type (Long, Float, Double) of 
valid zero input.
+     */
+    @Test
+    public void testCreateNumberZero() {
+        // Handle integers
+        assertEquals(Integer.valueOf(0), NumberUtils.createNumber("0"));
+        assertEquals(Integer.valueOf(0), NumberUtils.createNumber("-0"));
+        assertEquals(Long.valueOf(0), NumberUtils.createNumber("0L"));
+        assertEquals(Long.valueOf(0), NumberUtils.createNumber("-0L"));
+
+        // Handle floating-point with optional leading sign, trailing exponent 
(eX)
+        // and format specifier (F or D).
+        // This should allow: 0. ; .0 ; 0.0 ; 0 (if exponent or format 
specifier is present)
+
+        // Exponent does not matter for zero
+        final int[] exponents = {-2345, 0, 13};
+        final String[] zeros = {"0.", ".0", "0.0", "0"};
+        final Float f0 = Float.valueOf(0);
+        final Float fn0 = Float.valueOf(-0F);
+        final Double d0 = Double.valueOf(0);
+        final Double dn0 = Double.valueOf(-0D);
+
+        for (final String zero : zeros) {
+            // Assume float if no preference.
+            // This requires a decimal point if there is no exponent.
+            if (zero.indexOf('.') != -1) {
+                assertCreateNumberZero(zero, f0, fn0);
+            }
+            for (final int exp : exponents) {
+                assertCreateNumberZero(zero + "e" + exp, f0, fn0);
+            }
+            // Type preference
+            assertCreateNumberZero(zero + "F", f0, fn0);
+            assertCreateNumberZero(zero + "D", d0, dn0);
+            for (final int exp : exponents) {
+                final String number = zero + "e" + exp;
+                assertCreateNumberZero(number + "F", f0, fn0);
+                assertCreateNumberZero(number + "D", d0, dn0);
+            }
+        }
+    }
+
+    private static void assertCreateNumberZero(String number, Object zero, 
Object negativeZero) {
+        assertEquals(zero, NumberUtils.createNumber(number), () -> "Input: " + 
number);
+        assertEquals(zero, NumberUtils.createNumber("+" + number), () -> 
"Input: +" + number);
+        assertEquals(negativeZero, NumberUtils.createNumber("-" + number), () 
-> "Input: -" + number);
+    }
+
+    /**
      * Tests isCreatable(String) and tests that createNumber(String) returns a 
valid number iff isCreatable(String)
      * returns false.
      */
@@ -717,6 +783,14 @@ public class NumberUtilsTest {
         compareIsCreatableWithCreateNumber("+0xF", true); // LANG-1645
         compareIsCreatableWithCreateNumber("+0xFFFFFFFF", true); // LANG-1645
         compareIsCreatableWithCreateNumber("+0xFFFFFFFFFFFFFFFF", true); // 
LANG-1645
+        compareIsCreatableWithCreateNumber(".0", true); // LANG-1646
+        compareIsCreatableWithCreateNumber("0.", true); // LANG-1646
+        compareIsCreatableWithCreateNumber("0.D", true); // LANG-1646
+        compareIsCreatableWithCreateNumber("0e1", true); // LANG-1646
+        compareIsCreatableWithCreateNumber("0e1D", true); // LANG-1646
+        compareIsCreatableWithCreateNumber(".D", false); // LANG-1646
+        compareIsCreatableWithCreateNumber(".e10", false); // LANG-1646
+        compareIsCreatableWithCreateNumber(".e10D", false); // LANG-1646
     }
 
     @Test
@@ -795,6 +869,14 @@ public class NumberUtilsTest {
         compareIsNumberWithCreateNumber("+0xF", true); // LANG-1645
         compareIsNumberWithCreateNumber("+0xFFFFFFFF", true); // LANG-1645
         compareIsNumberWithCreateNumber("+0xFFFFFFFFFFFFFFFF", true); // 
LANG-1645
+        compareIsNumberWithCreateNumber(".0", true); // LANG-1646
+        compareIsNumberWithCreateNumber("0.", true); // LANG-1646
+        compareIsNumberWithCreateNumber("0.D", true); // LANG-1646
+        compareIsNumberWithCreateNumber("0e1", true); // LANG-1646
+        compareIsNumberWithCreateNumber("0e1D", true); // LANG-1646
+        compareIsNumberWithCreateNumber(".D", false); // LANG-1646
+        compareIsNumberWithCreateNumber(".e10", false); // LANG-1646
+        compareIsNumberWithCreateNumber(".e10D", false); // LANG-1646
     }
 
     @Test

Reply via email to