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

Urizen picture Urizen · Jan 10, 2010 · Viewed 15.5k times · Source

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?

Answer

Brad Larson picture Brad Larson · Jan 10, 2010

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.