andy
andy

Reputation: 1356

NumberUtils throws Exception when convert a BigDecimal to Long but sucess when convert to Integer

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

Answers (2)

Erwin Bolwidt
Erwin Bolwidt

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

hagrawal7777
hagrawal7777

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

Related Questions