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 ?
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 double
s. Submit a bug report at bugreport.apple.com and see what they say.