Thursday, August 01, 2013

Math tests

Java's java.lang.Math library provides methods for performing numeric operations. It's a good idea to have a basic test list to test the math functions of whichever programming language you are using at the moment. You should know about the behavior of math functions (in cases like overflow) so that you won't waste a lot of time looking for bugs in the wrong places.

For example, consider this:

int index = (int) Double.NaN;

I assumed that Java would throw some kind of overflow exception but, no, you get index == 0! I can imagine a program where I try to calculate an index based on a floating point number. When the floating point number becomes NaN your index becomes zero, which would be a valid value. Your program would continue and the error would manifest itself in some irrelevant place.

Before you use any math library, test it to gain insight in its behavior. This is especially critical if you plan to write software with a lot of numerical operations. Write wrapper methods that throw exceptions to aid debugging.

Below is a test class containing interesting test cases that I could think of, written with JUnit.

package math;

import org.junit.Test;

/**
 * Interesting Math library tests, some of which result in unexpected behavior.
 *
 * @author skorkmaz, 2013
 */
public class MathTest {

    @Test
    public void testDivisionByZeroInt() {
        System.out.println("Test: Division by zero. int result = 1 / 0");
        try {
            int result = 1 / 0;
        } catch (ArithmeticException e) {
            /*Throws exception as I would normally expect.*/
            System.out.println(e.toString());
        }
        System.out.println("-------------------------------------------------");
    }

    @Test
    public void testDivisionByZeroInt2() {
        System.out.println("Test: Division by zero. int result = (int) (1.0/0)");
        int result = (int) (1.0 / 0);
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testDivisionByZeroDouble() {
        System.out.println("Test: Division by zero. double result = 1.0/0");
        double result = 1.0 / 0;
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testDivisionByZeroDoubleNaN() {
        System.out.println("Test: Division by zero. double result = Double.NaN/0");
        double result = Double.NaN/0;
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testPowDoubleNaN() {
        System.out.println("Test: Pow Double.Nan. double result = Math.pow(Double.NaN, 0)");
        double result = Math.pow(Double.NaN, 0);
        /*No exception is thrown and returns 1.0!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testIntegerOverflowLong() {
        System.out.println("Test: Integer overflow from long. int result = (int) Long.MAX_VALUE");
        int result = (int) Long.MAX_VALUE;
        /*No exception is thrown, result = -1!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testIntegerOverflowDouble() {
        System.out.println("Test: Integer overflow from double. int result = (int) Double.MAX_VALUE");
        int result = (int) Double.MAX_VALUE;
        /*No exception is thrown, result = 2147483647 (Integer.MAX_VALUE)!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");      
    }
 
    @Test
    public void testIntegerOverflowNaN() {
        System.out.println("Test: Integer overflow from NaN. int result = (int) Double.NaN");
        int result = (int) Double.NaN;
        /*No exception is thrown and it returns 0 which may mask possible problems!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testNaNComparison() {
        System.out.println("Test: NaN comparison. boolean result = Double.NaN == Double.NaN");
        boolean result = Double.NaN == Double.NaN;
        /*Returns false*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testSqrtNegative() {
        System.out.println("Test: sqrt(negative). double result = Math.sqrt(-1)");
        double result = Math.sqrt(-1);
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testZeroComparison() {
        System.out.println("Test: Zero comparison. double result = 0.0 == -0.0");
        boolean result = 0.0 == -0.0;
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testZeroTimesInf() {
        System.out.println("Test: Zero * inf. double result = 0.0 * Double.POSITIVE_INFINITY");
        double result = 0.0 * Double.POSITIVE_INFINITY;
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testInfDivZero() {
        System.out.println("Test: inf/zero. double result = Double.POSITIVE_INFINITY / 0.0");
        double result = Double.POSITIVE_INFINITY / 0.0;
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testInfDivNaN() {
        System.out.println("Test: inf/NaN. double result = Double.POSITIVE_INFINITY / Double.NaN");
        double result = Double.POSITIVE_INFINITY / Double.NaN;
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
 
    @Test
    public void testInfDivInf() {
        System.out.println("Test: inf/inf. double result = Double.POSITIVE_INFINITY / Double.POSITIVE_INFINITY");
        double result = Double.POSITIVE_INFINITY / Double.POSITIVE_INFINITY;
        /*No exception is thrown!*/
        System.out.println("result = " + result);
        System.out.println("-------------------------------------------------");
    }
}

No comments: