Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/main/java/org/apache/xmlbeans/impl/util/MathUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public static int safeFloatToInt(float f) {
if (Float.isInfinite(f)) {
throw new IllegalArgumentException("Cannot convert infinity to int");
}
if (f > Integer.MAX_VALUE || f < Integer.MIN_VALUE) {
// compare as double: widening Integer.MAX_VALUE to float rounds it up to 2^31
if ((double) f > Integer.MAX_VALUE || (double) f < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value out of range: " + f);
}
return (int) f;
Expand Down Expand Up @@ -101,7 +102,7 @@ public static BigInteger parseAsBigInteger(String s) {

/**
* @param s string to parse
* @return valid Float
* @return valid float
* @throws NumberFormatException if parse fails
* @throws IllegalArgumentException if string is too long
* @throws NullPointerException if string is null
Expand Down Expand Up @@ -130,7 +131,7 @@ public static float parseAsFloat(String s, int maxNumberOfChars) {

/**
* @param s string to parse
* @return valid float
* @return valid double
* @throws NumberFormatException if parse fails
* @throws IllegalArgumentException if string is too long
* @throws NullPointerException if string is null
Expand All @@ -142,7 +143,7 @@ public static double parseAsDouble(String s) {
/**
* @param s string to parse
* @param maxNumberOfChars maximum number of characters allowed in the string
* @return valid float
* @return valid double
* @throws NumberFormatException if parse fails
* @throws IllegalArgumentException if string is too long
* @throws NullPointerException if string is null
Expand Down Expand Up @@ -198,13 +199,23 @@ public static int parseAsInt(String s) {
* @throws NullPointerException if value is null
*/
public static BigInteger toBigInteger(BigDecimal value) {
if (value == null) {
throw new NullPointerException("Cannot convert null to BigInteger");
}
BigDecimal normalized = value.stripTrailingZeros();
int integerDigits = normalized.precision() - normalized.scale();
// the scale check is not redundant: for a very negative scale (eg 1E+2147483647) the
// subtraction above overflows and integerDigits comes out negative
if (integerDigits > DEFAULT_MAX_NUMBER_CHARS || normalized.scale() < -DEFAULT_MAX_NUMBER_CHARS) {
throw new IllegalArgumentException(
"BigDecimal magnitude too large to convert safely: approx "
+ integerDigits + " integer digits (limit " + DEFAULT_MAX_NUMBER_CHARS + ")");
}
if (integerDigits <= 0) {
// abs(value) is less than 1, so it truncates to zero - avoid BigDecimal.toBigInteger()
// computing 10^scale, which is very expensive for a large scale (eg 1E-10000000)
return BigInteger.ZERO;
}
return normalized.toBigInteger();
}

Expand Down
58 changes: 58 additions & 0 deletions src/test/java/org/apache/xmlbeans/impl/util/TestMathUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ Licensed to the Apache Software Foundation (ASF) under one or more

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Duration;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;

public class TestMathUtil {
@Test
Expand Down Expand Up @@ -52,4 +54,60 @@ public void testToLongWithValueOutOfRange() {
BigDecimal expected2 = BigDecimal.valueOf(Long.MIN_VALUE).subtract(BigDecimal.ONE);
assertThrows(IllegalArgumentException.class, () -> MathUtil.toLong(expected2));
}

@Test
public void testToBigIntegerNull() {
assertThrows(NullPointerException.class, () -> MathUtil.toBigInteger(null));
assertThrows(NullPointerException.class, () -> MathUtil.toLong(null));
assertThrows(NullPointerException.class, () -> MathUtil.toInt(null));
}

@Test
public void testToBigIntegerSmallValuesTruncateToZero() {
assertEquals(BigInteger.ZERO, MathUtil.toBigInteger(new BigDecimal("0.5")));
assertEquals(BigInteger.ZERO, MathUtil.toBigInteger(new BigDecimal("-0.999")));
assertEquals(BigInteger.ZERO, MathUtil.toBigInteger(BigDecimal.ZERO));
assertEquals(BigInteger.ONE, MathUtil.toBigInteger(new BigDecimal("1.5")));
}

@Test
public void testToBigIntegerSmallExponent() {
// BigDecimal.toBigInteger() would compute 10^10000000 here, taking seconds
assertTimeoutPreemptively(Duration.ofSeconds(2), () ->
assertEquals(BigInteger.ZERO, MathUtil.toBigInteger(new BigDecimal("1E-10000000"))));
}

@Test
public void testToBigIntegerMaxNegativeScale() {
BigDecimal value = new BigDecimal("1E+2147483647");
assertThrows(IllegalArgumentException.class, () -> MathUtil.toBigInteger(value));
}

@Test
public void testSafeFloatToInt() {
assertEquals(1, MathUtil.safeFloatToInt(1.75f));
assertEquals(-1, MathUtil.safeFloatToInt(-1.75f));
assertEquals(Integer.MIN_VALUE, MathUtil.safeFloatToInt(Integer.MIN_VALUE));
}

@Test
public void testSafeFloatToIntWithValueOutOfRange() {
// 2^31 is the nearest float above Integer.MAX_VALUE and must not be accepted
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeFloatToInt(2147483648f));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeFloatToInt(-2147483904f));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeFloatToInt(Float.MAX_VALUE));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeFloatToInt(Float.NaN));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeFloatToInt(Float.POSITIVE_INFINITY));
}

@Test
public void testSafeDoubleToInt() {
assertEquals(1, MathUtil.safeDoubleToInt(1.75));
assertEquals(Integer.MAX_VALUE, MathUtil.safeDoubleToInt(Integer.MAX_VALUE));
assertEquals(Integer.MIN_VALUE, MathUtil.safeDoubleToInt(Integer.MIN_VALUE));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeDoubleToInt(2147483648d));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeDoubleToInt(-2147483649d));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeDoubleToInt(Double.NaN));
assertThrows(IllegalArgumentException.class, () -> MathUtil.safeDoubleToInt(Double.NEGATIVE_INFINITY));
}
}
Loading