Reputation: 1356
System.out.println(org.springframework.util.NumberUtils.convertNumberToTargetClass(new BigDecimal("18446744073709551611"), Integer.class));
System.out.println(org.springframework.util.NumberUtils.convertNumberToTargetClass(new BigDecimal("18446744073709551611"), Long.class));
first line returns -5 but second line throws exception java.lang.IllegalArgumentException: Could not convert number [18446744073709551611] of type [java.math.BigDecimal] to target class [java.lang.Long]: overflow
Is that the expect result of method org.springframework.util.NumberUtils.convertNumberToTargetClass
?
Upvotes: 3
Views: 2042
Reputation: 31299
This is a bug in Spring.
The bounds checking for the conversion to long is correct. It first converts the BigDecimal to a BigInteger and then checks if it is within range of a Long.
88 else if (targetClass.equals(Long.class)) {
89 BigInteger bigInt = null;
90 if (number instanceof BigInteger) {
91 bigInt = (BigInteger) number;
92 }
93 else if (number instanceof BigDecimal) {
94 bigInt = ((BigDecimal) number).toBigInteger();
95 }
96 // Effectively analogous to JDK 8's BigInteger.longValueExact()
97 if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
98 raiseOverflowException(number, targetClass);
99 }
100 return (T) new Long(number.longValue());
101 }
However the conversion to Integer (and other primitive numeric types) is broken.
It first takes the longValue
if the BigDecimal and then checks if this long value is in range of the type. However if it wasn't in range of the long value, then the long value is a truncated number that is meaningless and may seem to be within range of the smaller type, but wasn't really because the top bits were truncated off in the initial conversion to long.
In your example, 18446744073709551611 truncated to a Long yields -5, which is perfectly in range for an Integer - but the original number clearly was not in range of an Integer.
81 else if (targetClass.equals(Integer.class)) {
82 long value = number.longValue();
83 if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
84 raiseOverflowException(number, targetClass);
85 }
86 return (T) new Integer(number.intValue());
87 }
To implement this correctly, Spring would need to change the conversion to Integer (and to Short and Byte) to work the same way as the conversion to Long: first convert to BigInteger, then check the range, then convert to the correct type (Integer/Byte/Short).
Upvotes: 2
Reputation: 14678
Is that the expect result of method
Yes and no.
Yes, this is the expected result when you specify java.lang.Long
and gets an overflow for Long.
No, in general, because as per method signature convertNumberToTargetClass(Number number, Class targetClass)
if the targetClass
is a subclass of java.lang.Number
then you do not get this exception. In your case, java.lang.Long
is subclass, so it will not come but you are getting because of overflow.
Below code snippet from org.springframework.util.NumberUtils.convertNumberToTargetClass
public static <T extends Number> T convertNumberToTargetClass(Number number, Class<T> targetClass) throws IllegalArgumentException {
......
else if (targetClass.equals(Long.class)) {
BigInteger bigInt = null;
if (number instanceof BigInteger) {
bigInt = (BigInteger) number;
}
else if (number instanceof BigDecimal) {
bigInt = ((BigDecimal) number).toBigInteger();
}
// Effectively analogous to JDK 8's BigInteger.longValueExact()
if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
raiseOverflowException(number, targetClass);
}
return (T) new Long(number.longValue());
}
.......
}
And here is the code snippet for raiseOverflowException
, place from where you are getting your exception:
private static void [More ...] raiseOverflowException(Number number, Class<?> targetClass) {
throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow");
}
Check out the implementation of org.springframework.util.NumberUtils.convertNumberToTargetClass
here
Upvotes: 0