Is there a way to create an NSDecimal without using NSNumber and creating autoreleased objects?
Asked Answered
R

2

19

I am carrying out a number of calculations using NSDecimal and am creating each NSDecimal struct using the following technique:

[[NSNumber numberWithFloat:kFloatConstant] decimalValue]

I am using NSDecimal to avoid using autoreleased NSDecimalNumber objects (if the NSDecimalNumber approach to accurate calculations is used). However it seems that the NSNumber creation mechanism also returns an autoreleased NSNumber from which the decimal value is extracted.

Is there a way to create an NSDecimal without using NSNumber and creating autoreleased objects?

Rome answered 9/1, 2010 at 23:43 Comment(1)
It really seems like NSDecimal was designed for things with specific precision ranges where exact accuracy is important, like currency, which you tend not to want to convert either to or from a float or double because of the risk of lost precision. Would you be able to tell us more about your use-case?Chappelka
P
28

Unfortunately, Apple does not provide any easy ways of putting values into an NSDecimal struct. The struct definition itself can be found in the NSDecimal.h header:

typedef struct {
    signed   int _exponent:8;
    unsigned int _length:4;     // length == 0 && isNegative -> NaN
    unsigned int _isNegative:1;
    unsigned int _isCompact:1;
    unsigned int _reserved:18;
    unsigned short _mantissa[NSDecimalMaxSize];
} NSDecimal;

but I don't know that I would go around trying to reverse-engineer how the structs hold values. The underscores on the fields in the struct indicate that these are private and subject to change. I don't imagine that a lot of changes occur in the low-level NSDecimal functions, but I'd be nervous about things breaking at some point.

Given that, initializing an NSDecimal from a floating point number is best done in the way that you describe. However, be aware that any time you use a floating point value you are losing the precision you've gained by using an NSDecimal, and will be subjecting yourself to floating point errors.

I always work only with NSDecimals within my high-precision calculations, and take in and export NSStrings for exchanging these values with the outside world. To create an NSDecimal based on an NSString, you can use the approach we take in the Core Plot framework:

NSDecimal CPDecimalFromString(NSString *stringRepresentation)
{
    NSDecimal result;
    NSScanner *theScanner = [[NSScanner alloc] initWithString:stringRepresentation];
    [theScanner scanDecimal:&result];
    [theScanner release];

    return result;
}

Using an NSScanner instead of NSDecimalNumber -initWithString:locale: is about 90% faster in my benchmarks.

Polish answered 10/1, 2010 at 2:22 Comment(8)
The NSDecimal class is so misunderstood and that's because it is so counter intuitive. Why have apple made it so un-programmer friendly? What are the alternatives to NSDecimal?Menticide
@AndrewS - For one, NSDecimal isn't a class, but a struct, so it needs to be handled a little differently than your standard objects. The complexity in the NSDecimal handling functions seems to stem from a desire to make them as fast as possible. Note that they pass by reference into these functions, as just one optimization. There's no excuse for not having an easy constructor for NSDecimals, though.Polish
Is there a more primitive decimal like NSInteger?Menticide
@AndrewS - No. The floating-point scalars like float and double do not store their values in such a format to do base-10 arithmetic like NSDecimal.Polish
Unless one is starting with a string, I would avoid round-tripping through a sting just to convert one numeric type (float) to another (NSDecimal).Chappelka
@SlippD.Thompson - If you're working with NSDecimal, you need to either start with a string or a stored NSDecimalNumber from Core Data or you're going to lose precision from the beginning and open yourself up to base-2 floating point glitches. Also, there are known glitches with using NSDecimalNumber's superclass -numberWithDouble and the like: https://mcmap.net/q/386928/-proper-way-to-instantiate-an-nsdecimalnumber-from-float-or-double so even when starting from floats, you're better off initializing your NSDecimalNumber from a formatted string.Polish
I still wouldn't do it. If those concerns are important in a situation and there's no way around these other than wastefully round-tripping, then NSDecimal is probably the wrong tool for the job.Chappelka
It is exactly CorePlot that helped me find this topic - and the reason is that CPTDecimalFromCGFloat is on the top of CPU usage list in the profiler! It is ridiculous to use such a format for caching values that are used for drawing on screen!Cazares
C
2

To answer the “without autoreleased objects” part, simply:

NSDecimalNumber *intermediateNumber = [[NSDecimalNumber alloc] initWithFloat:kFloatConstant];
NSDecimal decimal = [intermediateNumber decimalValue];
[intermediateNumber release];
Chappelka answered 13/6, 2013 at 22:37 Comment(5)
The problem here isn't so much that the object is autoreleased, it's avoiding allocating it to begin with. Any allocation of an NSDecimalNumber, autoreleased or no, will be slower than dealing with an NSDecimal struct: #1705004 . There's significant overhead in the creation and destruction of these objects. You're also assuming they're starting with a float value, which would hopefully not be the case when dealing with NSDecimals and NSDecimalNumbers.Polish
Also, you probably don't want to use -initWithFloat: for the reasons described here: https://mcmap.net/q/386928/-proper-way-to-instantiate-an-nsdecimalnumber-from-float-or-doublePolish
@BradLarson That's why I prefixed the answer with “To answer the ‘without autoreleased objects’ part”.Chappelka
@BradLarson The initWithFloat: is there to match the numberWithFloat: and kFloatConstant in the question. It's pretty clear that the OP is starting with nothing more accurate than a float.Chappelka
@BradLarson Read over the inaccuracy issues; sounds to me like a bug in NSNumber/NSDecimalNumber's implementation. As I said above, if that much accuracy is important to you, don't use NSDecimal/NSDecimalNumber.Chappelka

© 2022 - 2024 — McMap. All rights reserved.