简体   繁体   中英

Coredata and NSDecimalNumber

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?

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)

SQLite which is backend for CoreData has no support for decimals and float is used instead.


From SQLite documentation:

Each column in an SQLite 3 database is assigned one of the following type affinities: TEXT NUMERIC INTEGER REAL BLOB

More details are at SQLite documentation https://sqlite.org/datatype3.html 3.1.1. Affinity Name Examples.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM