{NSDecimalNumber integerValue} ведет себя странно в iOS8
ОК, команда, это странно. [NSDecimalNumber integerValue] ведет себя странно.
Я сидел в точке останова, пытаясь выяснить, почему некоторые части моего приложения не работают в iOS8, и я смотрю на переменную под названием "timeSeconds". Это появляется как это в Представлении Переменных XCode:
_timeSeconds (NSDecimalNumber *) 344.514533996581994496
Но когда я запрашиваю его в отладчике, я вижу это:
(lldb) p [self.timeSeconds doubleValue]
(double) $14 = 344.51453399658192
(lldb) p [self.timeSeconds intValue]
(int) $15 = 344
(lldb) p [self.timeSeconds integerValue]
(NSInteger) $16 = -5
(lldb) p (NSInteger)[self.timeSeconds intValue]
(NSInteger) $17 = 344
Видишь что "-5"? Может ли кто-нибудь из вас, прекрасные люди, воспроизвести или объяснить это, прежде чем я подам радар?
Вот SSCCE:
NSDecimalNumber *n = [NSDecimalNumber decimalNumberWithString:@"344.514533996581994496"];
NSLog(@"%@", n); // 344.514533996581994496
NSLog(@"%ld", (long)[n intValue]); // 344
NSLog(@"%ld", (long)[n integerValue]); // -5
NSLog(@"%ld", (long)[n unsignedIntegerValue]); // 12
Заранее спасибо!
Мэтью
4 ответа
Результат для integerValue удивителен, но из того, что я понимаю как задокументировано:
NSDecimalNumber наследуется от NSNumber. В примечаниях по подклассам из NSNumber указано, что "... подкласс должен переопределить метод доступа, соответствующий объявленному типу - например, если ваша реализация objCType возвращает" i ", вы должны переопределить intValue..."
Для objCType задан внутренний указатель, поэтому он должен быть таким же, как и для NSNumber.
NSDecimal не переопределяет intergerValue. Это переопределяет doubleValue, так что должно работать нормально.
Единственное, что заставляет меня задуматься: похоже, оно не переопределяет intValue...
Какая отличная ошибка! Я просто оказался обманутым этим. Итак, просто, чтобы сделать вывод:
Не делайте:
NSInteger x = [myDecimalNumber integerValue];
Вместо этого сделайте ЭТО:
NSInteger x = (NSInteger)[myDecimalNumber doubleValue];
Вы можете использовать use intValue или unsignedIntValue просто отлично, но НЕ integerValue или unsignedIntegerValue. Вот модульный тест, который демонстрирует проблему, показывая, что она связана с числами, требующими более 64 бит точности:
//
// NSDecimalNumberBugTests.m
//
// Created by Lane Roathe on 6/1/17.
// For Quicken, Inc.
//
#import <XCTest/XCTest.h>
@interface NSDecimalNumberBugTests : XCTestCase
@end
@implementation NSDecimalNumberBugTests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testBug {
// Use XCTAssert and related functions to verify your tests produce the correct results.
NSDecimalNumber* decimalLength;
NSUInteger interval;
// Start with a number that requires 65+ bits
// This FAILS (interval is zero)
decimalLength = [NSDecimalNumber decimalNumberWithString:@"1.8446744073709551616"];
interval = decimalLength.unsignedIntegerValue;
XCTAssert(interval == 1);
// This Works, interval is 1
interval = decimalLength.unsignedIntValue;
XCTAssert(interval == 1);
// Now test with a number that fits in 64 bits
// This WORKS (interval is 1)
decimalLength = [NSDecimalNumber decimalNumberWithString:@"1.8446744073709551615"];
interval = decimalLength.unsignedIntegerValue;
XCTAssert(interval == 1);
}
@end