{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];

Использование myDecimalNumber.intValue вместо integerValue

Вы можете использовать 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
Другие вопросы по тегам