javastringnumberformatexception

Math.abs(x) is not working properly for specifically -2147483648, giving NumberFormat Exception


I have written a method to reverse a signed int. If the reversed number is not in range of INT_MAX and INT_MIN it will return 0. It is working for almost all cases except for -2147483648 While debugging i have found that the code is not handling sign properly for -2147483648 while its working for different signed inputs.

Can someone explanin why Math.abs(x) is not working properly for this testcase. Method is given below.

public int reverse(int x) {
        boolean flag = (x>0);
        x = Math.abs(x);

        String num = new StringBuilder(Integer.toString(x)).reverse().toString();
        
        long temp = Long.parseLong(num);

        if(!flag){
            temp = -temp;
        }

        if((Integer.MAX_VALUE < temp) || ( temp < Integer.MIN_VALUE)){
            return 0;
        }

        return (int) temp;
    }

Solution

  • Code Explanation

    In Java, the integer representation goes from Integer.MIN_VALUE = -2^31 = -2147483648 to Integer.MAX_VALUE = 2^31-1 = 2147483647.

    There is no positive value opposite to -2147483648 that can be properly represented in Java's integer range when performing Math.abs(x). Hence, when the parameter -2147483648 is passed to the method, the result overflows back to the smallest representable integer, which is -2147483648 again, since Math.abs() simply negates the given parameter when this is negative. Therefore, in your line

    String num = new StringBuilder(Integer.toString(x)).reverse().toString();
    

    you're actually reversing -2147483648 into 8463847412-, which causes the NumberFormatException being thrown when performing

    long temp = Long.parseLong(num);
    

    since 8463847412- is not a long.

    Alternative Solutions

    In Java, error cases should be handled with an appropriate Exception describing the nature of the problem. Although your current version does throw an exception (NumberFormatException), it doesn't really address the actual issue, i.e. x not being representable as a positive integer when equal to Integer.MIN_VALUE.

    As others have already suggested, you could use Math.absExact() instead of Math.abs(), as it throws an ArithmeticException if the result overflows the positive int range.

    public int reverse(int x) {
        boolean flag = (x > 0);
        x = Math.absExact(x);
        // ... rest  of the implementation
    }
    

    However, Math.absExact() is available only from Java 15. Furthermore, personally speaking, I would throw an IllegalAegumentException, as I prefer the users of my API to receive a "higher-level" exception closer to what they're doing, rather than re-throwing an exception closer to my internal implementation.

    public int reverse(int x) {
        if (x == Integer.MIN_VALUE) 
           throw new IllegalArgumentException(String.format("%d cannot be represented as a positive integer... More detailed explanation...", x)); 
        boolean flag = (x > 0);
        x = Math.abs(x);
        // ... rest  of the implementation
    }
    

    However, this is purely a matter of style; both exceptions (ArithmeticException and IllegalAegumentException) are completely fine. Nonetheless, the thing that everybody can agree on is that, regardless of the exception used, an appropriate exception must be thrown when the actual error occurs. The NumberFormatException is not an appropriate way, as it is just a consequence of an error not handled when presented itself.