PHPUnit and floating point precision

Last week I’ve had some trouble unit testing a function that converted a point on a map given in degrees, minutes and seconds, to a longitude and a latitude. I did not write the function myself, but was only unit testing it. PHPUnit gave me some weird output, from which I could not extract a cause. Some mighty debugging got me to the bottom of it.

Consider the following class:

class MyClass
{
    public function getData()
    {
        $degrees = '10';
        $minutes = '45';
        $seconds = '16';
        return array(
            'int' => 1,
            'float' => $degrees + ($minutes/60 ) + ($seconds/3600 ),
            'string' => 'myString'
        );
    }
}

Notice the hard-coded values as strings, as if they are coming straight out of a file or database. Not that it makes any difference for this example.
If we now know the solution to the calculation beeing made, we can write a unit test. We can do this because our test environment is fully controlled per test, so we know exactly what values are sent to the class. (Allthough they are just hardcoded here for simplicity):

class MyClassTest extends PHPUnit_Framework_TestCase
{
    public function testGetData()
    {
        require_once 'MyClass.php';
        $subject = new MyClass();

        $expected = array(
            'int' => 1,
            'float' => 10.754444444444,
            'string' => 'myString'
        );
        $actual = $subject->getData();

        $this->assertEquals($expected, $actual);
    }
}

Running the unit test will result in the following:

There was 1 failure:

1) MyClassTest::testGetData
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array
 (
     [float] => 10.754444444444
     [int] => 1
     [string] => myString
 )

/test/TestClass.php:20

So PHPUnit tells us the 2 arrays are not equal, but it can not tell us what is wrong with it… This is of course a strange quirk brought to you by the marry world of floating point precicion. The PHP Manual also makes this clear by stating: “So never trust floating number results to the last digit, and never compare floating point numbers for equality.”
This problem can be fixed in several ways, these 2 will both result in a valid Unit Test:

1: change the line where the number is hard-coded to the equal calculation used in the class:

        //change this
        $expected = array(
            'int' => 1,
            'float' => 10.754444444444,
            'string' => 'myString'
        );
        //to this
        $expected = array(
            'int' => 1,
            'float' => 10 + (45/60) + (16/3600),
            'string' => 'myString'
        );

2: alter the result by rounding it

        $actual = $subject->getData();
        //add this line
        if (array_key_exists('float', $actual)) $actual['float'] = round($actual['float'], 12);

There are a couple of other options to get this test working like extending PHPUnit to check value types and acting accordingly. Best way is to put this in a custom assert method which you can use when you know there can be floating point troubles.
Another solution might come from using the BC Math Functions, allthough I’m not sure about this one…