@@ -873,12 +873,47 @@ - (NSData *)serializePrimitive:(id)value {
873
873
874
874
- (id )fixDoubleParsing : (id )value
875
875
__attribute__((no_sanitize(" float-cast-overflow" ))) {
876
- // The parser for double values in JSONSerialization at the root takes some
877
- // short-cuts and delivers wrong results (wrong rounding) for some double
878
- // values, including 2.47. Because we use the exact bytes for hashing on the
879
- // server this will lead to hash mismatches. The parser of NSNumber seems to
880
- // be more in line with what the server expects, so we use that here
881
- if ([value isKindOfClass: [NSNumber class ]]) {
876
+ if ([value isKindOfClass: [NSDecimalNumber class ]]) {
877
+ // In case the value is an NSDecimalNumber, we may be dealing with
878
+ // precisions that are higher than what can be represented in a double.
879
+ // In this case it does not suffice to check for integral numbers by
880
+ // casting the [value doubleValue] to an int64_t, because this will
881
+ // cause the compared values to be rounded to double precision.
882
+ // Coupled with a bug in [NSDecimalNumber longLongValue] that triggers
883
+ // when converting values with high precision, this would cause
884
+ // values of high precision, but with an integral 'doubleValue'
885
+ // representation to be converted to bogus values.
886
+ // A radar for the NSDecimalNumber issue can be found here:
887
+ // http://www.openradar.me/radar?id=5007005597040640
888
+ // Consider the NSDecimalNumber value: 999.9999999999999487
889
+ // This number has a 'doubleValue' of 1000. Using the previous version
890
+ // of this method would cause the value to be interpreted to be integral
891
+ // and then the resulting value would be based on the longLongValue
892
+ // which due to the NSDecimalNumber issue would turn out as -844.
893
+ // By using NSDecimal logic to test for integral values,
894
+ // 999.9999999999999487 will not be considered integral, and instead
895
+ // of triggering the 'longLongValue' issue, it will be returned as
896
+ // the 'doubleValue' representation (1000).
897
+ // Please note, that even without the NSDecimalNumber issue, the
898
+ // 'correct' longLongValue of 999.9999999999999487 is 999 and not 1000,
899
+ // so the previous code would cause issues even without the bug
900
+ // referenced in the radar.
901
+ NSDecimal original = [(NSDecimalNumber *)value decimalValue ];
902
+ NSDecimal rounded;
903
+ NSDecimalRound (&rounded, &original, 0 , NSRoundPlain);
904
+ if (NSDecimalCompare (&original, &rounded) != NSOrderedSame) {
905
+ NSString *doubleString = [value stringValue ];
906
+ return [NSNumber numberWithDouble: [doubleString doubleValue ]];
907
+ } else {
908
+ return [NSNumber numberWithLongLong: [value longLongValue ]];
909
+ }
910
+ } else if ([value isKindOfClass: [NSNumber class ]]) {
911
+ // The parser for double values in JSONSerialization at the root takes
912
+ // some short-cuts and delivers wrong results (wrong rounding) for some
913
+ // double values, including 2.47. Because we use the exact bytes for
914
+ // hashing on the server this will lead to hash mismatches. The parser
915
+ // of NSNumber seems to be more in line with what the server expects, so
916
+ // we use that here
882
917
CFNumberType type = CFNumberGetType ((CFNumberRef)value);
883
918
if (type == kCFNumberDoubleType || type == kCFNumberFloatType ) {
884
919
// The NSJSON parser returns all numbers as double values, even
0 commit comments