Search code examples
iosobjective-cnsnumbernsdecimalnumber

Compare NSDecimalNumber to NSNumber gets wrong result


The code is simple and you can reproduce it.

NSDecimalNumber *d1 = [NSDecimalNumber decimalNumberWithString:@"6560601600245628933"];
BOOL res = [d1 isEqualToNumber:@(6560601600245628934)];
NSLog(@"%@", @(res));

isEqualToNumber: always returns YES, even I compare d1 to other numbers like @6560601600245628930... Any ideas? Is it a bug ?


Solution

  • Analysis

    NSDecimalNumber's compare: (which is called by isEqualToNumber:) checks for its argument (@(6560601600245628934) here) being another NSDecimalNumber and if not resorts to NSNumber's compare: (NSNumber being NSDecimalNumber's superclass).

    As an NSNumber d1 reports its type as double – that is CFNumberGetType() returns kCFNumberDoubleType – and given a double NSNumber's compare: compares the two values as double.

    Your first value (decimalNumberWithString:@"6560601600245628933") is stored without precious loss as an NSDecimalNumber, but has more significant digits than a double and the conversion loses precision.

    Your second value (@(6560601600245628934)) is stored by NSNumber as a 64-bit integer (kCFNumberSInt64Type) without precision loss, but when converted to double loses precision.

    With the variations you've tried both numbers when represented as double are the same.

    Workaround

    Change:

    @(6560601600245628934)
    

    to:

    [NSDecimalNumber numberWithLongLong:6560601600245628934LL]
    

    to create an NSDecimalNumber value directly from your integer. This will result in both arguments to isEqualToNumber: being NSDecimalNumber and no precision will be lost doing the comparison.

    Bug or feature?

    It might be classed as a documentation bug in that I haven't found it documented anywhere (which is far from conclusive!) that the comparison will be done with doubles. Submit a bug report at bugreport.apple.com and see what they say.