Jitendra Singh - 1 year ago 78

Objective-C Question

I'm having issue with decimal numbers stored in core data. When I'm saving number 0.6789 into database it is saved as 0.6788999999999999.

I have read somewhere that it is recommended to have decimal number columns as decimal to maintain the precision as core data automatically handles NSDecimalNumber for decimal columns in core data.

Below is my entity class:

`@interface TestEntity : NSManagedObject`

@property (nonatomic, retain) NSDecimalNumber * cost;

@end

@implementation TestEntity

@dynamic cost;

@end

This is how I'm inserting data:

`TestEntity *envelope = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity" inManagedObjectContext:self.managedObjectContext];`

envelope.cost = [NSDecimalNumber decimalNumberWithString:@"0.6789"];

[self.managedObjectContext save:nil];

Please someone help me on, how should I handle the decimal numbers and their precision when inserted into database?

Answer Source

Core Data is backed by SQLite. According to the SQLite documentation SQLite only supports integers up to 64 bit and floating point numbers with the 64bit IEEE 754 format (`double`

in Objective C). So the precision is limited to these types, which is about 18.75 significant digits for integers and 15.75 digits for floating point numbers.

Usually a `double`

or `int64_t`

are right for most of the applications except for those where precision beyond 15-18 significant digits is required. In some jurisdictions, banking institutions have to store decimal numbers with high precision by law and they use special types for this.

If your application is standard I would use `int64_t`

or `double`

. If you use `NSDecimalNumber`

it is going to be stored as a 64-bit type by the SQLite engine (floating point or integer depending on the existence of a fractional part). This means your precision will be limited to 15-18 digits.

If you want to use `NSDecimalNumber`

because of its high precision arithmetic, you still will be limited to 15/18-digits but rounding errors will not be introduced if your dynamic range can be expressed as a 15/18-digit number (with fractional part or without).

If your dynamic range is greater, `NSDecimalNumber`

provides 38 digits. In order to store it in a Core Data SQLite-backed persistent storage and be able to maintain the 38 digits, you should convert it to string, back and forth, so that it gets stored in Core Data as a string. You could create a category on your subclass of `NSManagedObject`

with a couple of methods to store (encode) and retrieve (decode) the number to a string. The underlying property in your model should the be a string, instead of a number.

`NSDecimalNumber`

has two methods `-stringValue`

and `-initWithString:`

to do the conversion for you. You just need to construct two methods that rely on these in your custom category.

In summary:

- If you need decimal numbers, use
`double`

for 15 significant digits - If you don't need decimal numbers, use
`int64_t`

for 18 significant digits - If you need decimal numbers with high precision accuracy, use
`NSDecimalNumber`

for 38 significant digits (with the conversion to string for storage in CoreData)