views:

59

answers:

2

Hi all,

I'm using NSNumber to store various values, but sometimes run into issues when doing calculations with the values and initializing new NSNumber objects for the results. I've figured out how to overcome it, but I couldn't for the life of me explain why it works and since my grasp on numerical values in computer environments (doubles, floats, etc) is weak I'd like to ask this question to learn. :-)

First example is when I am converting between different units of measurement, in this particular case it's between mmol/L and mg/dl. Getting an NSNumber which represents a mmol/L value, I extract its double value and perform the calculation. Then I create a new NSNumber with -initWithDouble and return the result.

However, I get odd quirks. If the mmol/L value is 10.0, the corresponding mg/dl value is 180.0 (the rate, obviously, is simply 18). But when I later need to let the user select a new value in a picker view and use the NSNumber -intValue to get the integer digits of the current value (using my own extension for getting the fractional digits), the int is 179! I've checked all the intermediate double values during calculation as well as the new NSNumber's double value and all is fine (180.00000 is the result). Interestingly, this doesn't happen for all values, just some (10.0 being one real example).

The second example is when I retrieve double values from an Sqlite3 database and store them in NSNumbers. Again, most values work fine, but occasionally I get weird stuff back. For instance, if I save 6.7 in the database (checking when it is saved that that is in fact the value), what my NSNumber will show after retrieval is 6.699999. (I can't actually remember at the moment of writing if that's what's in the database as well, but I think it is - I can check later.)

Both of these instances can be circumvented by using an intermediate float value and NSNumber initWithFloat instead of initWithDouble. So in my conversion, for example, I just do a float resultAsFloat = resultAsDouble and use initWithFloat for the new NSNumber.

Apologies for the long-winded question and if it's just my own knowledge about working with numerical values that is lacking, but I would really appreciate if someone could explain this to me!

Thanks,

Anders

* EDIT 1 *

Code for the unit conversion example:

-(NSNumber *)convertNumber:(NSNumber *)aNumber withUnit:(FCUnit *)aUnit {

// if origin unit and target unit are the same, return original number
if ([aUnit.uid isEqualToString:self.target.uid])
    return aNumber;

// determine if origin unit and target unit are comparable
if (aUnit.quantity != self.target.quantity)
    return nil;

// if so, convert the number...

// get bases
double originBase;
double targetBase;
if (aUnit.metre != nil) {

    originBase = [aUnit.metre doubleValue];
    targetBase = [self.target.metre doubleValue];

} else if (aUnit.kilogram != nil) {

    originBase = [aUnit.kilogram doubleValue];
    targetBase = [self.target.kilogram doubleValue];

} else if (aUnit.second != nil) {

    originBase = [aUnit.second doubleValue];
    targetBase = [self.target.second doubleValue];

} else if (aUnit.quantity == FCUnitQuantityGlucose) {

    // special case for glucose

    if ([aUnit.uid isEqualToString:FCKeyUIDGlucoseMillimolesPerLitre]) { // mmol/L -> mg/dl

        originBase = 1;
        targetBase = 0.0555555555555556; // since 1 / 0.0555555555555556 = 18

    } else if ([aUnit.uid isEqualToString:FCKeyUIDGlucoseMilligramsPerDecilitre]) { // mg/dl -> mmol/L

        originBase = 0.0555555555555556;
        targetBase = 1;
    }
}

// find conversion rate
double rate = originBase / targetBase;

// convert the value
double convert = [aNumber doubleValue] * rate;

// TMP FIX: this fixes an issue where the intValue of convertedNumber would be one less
// than it should be if the number was created with a double instead of a float. I have
// no clue as to why...
float convertAsFloat = convert;

// create new number object and return it
NSNumber *convertedNumber = [[NSNumber alloc] initWithFloat:convertAsFloat];
[convertedNumber autorelease];

return convertedNumber;
}
+2  A: 

Try using NSDecimalNumber. There's a good tutorial here:

http://www.cimgf.com/2008/04/23/cocoa-tutorial-dont-be-lazy-with-nsdecimalnumber-like-me/

Richard Turnbull
Thanks, yeah, might try that too, see if it makes a difference. But the question now was more WHY this happens rather than how to avoid it, since I already figured out how to circumvent the problem temporarily. :-)
Ansig