{NSDecimalNumber integerValue} behaving strangely in iOS8
Asked Answered
A

4

10

OK team, this is weird. [NSDecimalNumber integerValue] is behaving strangely.

I'm sat at a breakpoint, trying to figure out why some parts of my app are broken in iOS8, and I'm looking at a variable called "timeSeconds". It appears like this in the Xcode Variables View:

_timeSeconds    (NSDecimalNumber *) 344.514533996581994496

But when I query it in the debugger, I see this:

(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

See that "-5"? Can any of you beautiful people reproduce or explain that, before I file a radar?

Here's the 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

Thanks in advance!

Matthew

Array answered 6/9, 2014 at 22:52 Comment(2)
Matthew - what did you find? NSDecimalNumber on iOS 8 is causing huge problems in my live app!Leftist
10 years later this is still an issue. I just wasted hours tracking down a bug in my code due to this issue.Larimor
T
14

The result for integerValue is surprising, but from what I understand as documented:

NSDecimalNumber inherits from NSNumber. In the subclassing notes from NSNumber it is stated that "... subclass must override the accessor method that corresponds to the declared type—for example, if your implementation of objCType returns “i”, you must override intValue ..."

objCType is set to the inner pointer, so it should be the same as for NSNumber.

NSDecimal does not override intergerValue. It does override doubleValue, so that should work fine.

The only thing that makes me wonder: it seems it does not override intValue either ...

Tarp answered 7/11, 2014 at 20:58 Comment(2)
I think you are definitely on the right track. I'd like to understand this a little bit better and if you do figure out about intValue, please post your answer. But I am going to go ahead and award the bounty because I think this is the right direction and I don't want the bounty to run out without awarding it to you.Leftist
This is a related question where there is a more specific case of an NSDecimal problem that is likely related to this issue. #26741733Leftist
R
3

What a great bug! I just found myself tricked by it. So, just to conclude dogsgods answer:

Do NOT:

NSInteger x = [myDecimalNumber integerValue];

Instead, do THIS:

NSInteger x = (NSInteger)[myDecimalNumber doubleValue];
Rafi answered 23/2, 2015 at 16:33 Comment(0)
J
0

Use myDecimalNumber.intValue instead of integerValue

Ji answered 31/8, 2016 at 10:31 Comment(0)
B
0

You can use use intValue or unsignedIntValue just fine, but NOT integerValue or unsignedIntegerValue. Here is a unit test that demonstrates the issue, showing that it's related to numbers requiring more than 64 bits of precision:

//
//  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
Between answered 1/6, 2017 at 20:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.