0

I am writing a unit test for a function which tests a time value. The function appears to work correctly when I test it manually, but it fails when I test it using Xcode's Unit Test functionality. I have investigated, and the problem appears to be with the Unit Test arithmetic. This is the Unit Test code:

- (void)testValidTime {
    for (double a = 0.0f; a < 25.0f; a++) {
        for (double b = 0.0f; b < 0.65f; b += 0.01f) {
            double c = a + b;
            if (a > 23.0f || b > 0.59f) {
                XCTAssertFalse([TimeClass timeIsValid:c], "Test failed - Time passed as valid %f",c);
            } else {
                XCTAssertTrue([TimeClass timeIsValid:c], "Time failed - Time failed as invalid %f",c);
            }
        }
    }
}

Interestingly, if I step through the code, the result of the addition (c) is formatted as the correct answer in the error message (0.590000, for example) - but if I examine what c actually contains it shows as 0.589999974. And if I can't trust the debugger (or the Test functionality) what can I trust!?

Any ideas gratefully received.

headbanger
  • 1,038
  • 1
  • 11
  • 32
  • 1
    The debugger is printing to 6 decimal places. To 6 decimal places that's correct. To 9 decimal places, the other value is correct. I expect this is the standard "is floating point math broken?" question. Is there something more here that we shouldn't mark as a duplicate? https://stackoverflow.com/questions/588004/is-floating-point-math-broken (It's unclear what you mean by "work correctly when I test manually." What precise code are you using to "test manually?") – Rob Napier Sep 09 '20 at 17:50
  • 2
    Whenever you put a f behind a number it makes it a float which has lower precision than a double - so remote those f's. That does not completely solve the rounding problem, but makes it a bit better. 0.01 has an infinite expansion in binary btw so no matter how accurate you are, on a binary system it will be approximate. – skaak Sep 09 '20 at 17:53
  • @skaak - yup - I was clutching at straws, in fact it doesn't make any difference. – headbanger Sep 09 '20 at 18:01
  • @RobNapier Can we agree that the test b > 0.59 should not allow 0.589999974 through? And yet it does for some reason. This is the crux of my problem. – headbanger Sep 09 '20 at 18:03
  • 1
    Whenever you test using floating point it is good to use a tolerance, e.g. do something like ```a = 0.3; b = sin( a ); c = asin( b ); if ( abs( a - c ) < 0.0001 ) then test passed else test failed``` is one way to retain tests but make them pass. – skaak Sep 09 '20 at 18:05
  • 1
    So you can not test for equality, ```a == b``` might fail but ```|a-b| – skaak Sep 09 '20 at 18:08
  • In the event I solved the problem by adding another couple of decimal places. 0.5999 (and removing the f's). So my test now passes, but it doesn't feel satisfactory. Thank you for your help! – headbanger Sep 09 '20 at 18:09
  • 2
    Ok ... even that loop might give you funny results, e.g. you might expect 65 steps but due to rounding (or roundoff error) you might have 66 with the last two values of b being something like 0.649999 when you hoped it would exit and 0.659999 when it actually exits. Pet topic of mine - people write papers about these things but no reason to bang your head about it -) ... even in the latest Swift manual there is this example where the instructor does something simple like print 2.0 / 5.0 and the result is this hillarious 0.40000003 or something .... – skaak Sep 09 '20 at 18:17
  • 1
    "b > 0.59 should not allow 0.589999974". I wouldn't assume that. You originally wrote it as `b > 0.59f` which is not the same thing as `0.59` (as skaak noted). You'd need to check very closely, and the debugger introduces rounding, so you need to be careful to make sure you know precisely what you're looking at. I'm betting the `>` is correct, and that the `+=` isn't doing precisely what you think it is due to binary rounding. – Rob Napier Sep 09 '20 at 19:27

0 Answers0