views:

68

answers:

4

I have a UIPicker where the user inputs a specified time (i.e. 13:00, 13:01, 13:02, etc.) - which determines their score. Once they hit the button an alert comes up with the score that is determined through this 'if-else' statement. Everything seems to work great MOST of the time - but I am getting some erratic behavior. This is the code:

    //Gets my value from the UIPicker and then converts it into a format that can be used in the 'if' statement.    
    NSInteger runRow = [runTimePicker selectedRowInComponent:2];
    NSString *runSelected = [runTimePickerData objectAtIndex:runRow];
    NSString *runSelectedFixed = [runSelected stringByReplacingOccurrencesOfString:@":" withString:@"."];

    //The actual 'if' statment.
    if ([runSelectedFixed floatValue] <= 13.00) {
    runScore = 100;
} else if ([runSelectedFixed floatValue] <= 13.06) {
    runScore = 99;
} else if ([runSelectedFixed floatValue] <= 13.12) {
    runScore = 97;
} else if ([runSelectedFixed floatValue] <= 13.18) {
    runScore = 96;
} else if ([runSelectedFixed floatValue] <= 13.24) {
    runScore = 94;
} else if ([runSelectedFixed floatValue] <= 13.30) {
    runScore = 93;
} else if ([runSelectedFixed floatValue] <= 13.36) {
    runScore = 92;
} else if ([runSelectedFixed floatValue] <= 13.42) {
    runScore = 90;
} else if ([runSelectedFixed floatValue] <= 13.48) {
    runScore = 89;
} else if ([runSelectedFixed floatValue] <= 13.54) {
    runScore = 88;
}

Now, when I test the program, I will get the expected result when I choose '13:00' which is '100'. I also get the expected result of '99' when I choose all of the times between '13:01 and 13:05'. BUT, when I choose '13:06' it gives me a score of '97'. I also get a score of '97' on '13:07 through 13:12' - which is the desired result. Why would I get a '97' right on '13:12' but not get a '99' right on '13:06'???? Could this be a memory leak or something???

+4  A: 

No, not a memory leak, but a fundamental aspect of how floating point numbers work. Not every rational number can be represented accurately by a float. Some numbers are more accurate than others. It is well documented else where

You would be better off splitting the time into integer hours and minutes..

NSArray *components = [@"13:42" componentsSeparatedByString:@":"]
int hrs = [[components objectAtIndex:0] intValue];
int mins = [[components objectAtIndex:1] intValue];
mustISignUp
A: 

why don't you instead convert it to an integer? e.g.

NSString *runSelectedFixed = [runSelected stringByReplacingOccurrencesOfString:@":" withString:@""];

..

Anders K.
This seems to work :)
Rob
A: 

How are you converting times (13:06) to floats (13.06)? 6/60ths =0.1, which would lead to the results you are seeing. I'd guess this is the real cause, but a good solution would be the same as suggested by @mustISignUp.

mbmcavoy
+1  A: 

When you enter a raw floating point number in your source code, it is assumed to be a double. When you compare this to [runSelectedFixed floatValue], which is a float type, the compiler must first cast your float to a double and then do the comparison.

Because floating point numbers cannot perfectly represent all base-10 numbers, your values are internally rounded to the nearest floating-point representation. For example, when you enter 13.06 the float representation may actually be 13.058 (Just a theoretical example, you actually have more precision than this). Since a double has more precision, it may hold 13.059 when you enter 13.06. Thus, once you compare the two you won't always get the result you expect. Be sure to read the Wikipedia article on floating point so you understand the limitations and proper use of floating point code.

You can make all of the floating point numbers in your code above float literals by adding an f to the end of each one. For example:

} else if ([runSelectedFixed floatValue] <= 13.06f) {

This will keep all of your numbers to the same precision and help avoid the issue you're seeing above. However, you should not use a 100-line if-else block at accomplish what could be done with a simple equation:

int runScore = 100 - (int)(([runSelectedFixed floatValue] - 13.0f) / 0.06f);

Add bounds checked as appropriate for your code. Also consider splitting runSelectedFixed into hrs and minutes components first as mustISignUp suggested.

Tom S