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 <[email protected]>
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