I'm receiving and parsing JSON and storing the data into Core Data. Some of the data is currency being stored as NSDecimalNumber, but some of these values have a higher precision than two decimal places.
For instance, if I get a value from the service such as 8.2399999995 I would like to store this in Core Data as 8.24. Is there any way to set up my model to a two decimal place precision? Or do I need to manually round each value after it's stored?
UPDATE
Leijonien thanks for the information. I tried to doing that and I'm having some trouble saving the formatted value. I checked the JSON and searched Google and it turns out I'm getting a clean value from the service. RESTKit is the problem....https://github.com/RestKit/RestKit/issues/1405.
However, I've created a category on one of my NSManagedObject classes, overridden the setter for the attribute I want, formatted the value, but I still see the long decimal value in my db. Here's my code.
- (void)setAmount:(NSDecimalNumber *)amount {
NSDecimalNumberHandler *round = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain
scale:2
raiseOnExactness:NO
raiseOnOverflow:NO
raiseOnUnderflow:NO
raiseOnDivideByZero:YES];
NSDecimalNumber *newAmount = [amount decimalNumberByRoundingAccordingToBehavior:round];
NSLog(#"%#", newAmount);
[self setPrimitiveValue:newAmount forKey:#"amount"];
}
What's weird is that when newAmount prints to the console it's in the format 8.24 like I want, but when I check the db it's saved as 8.2399999995. Am I doing something wrong here?
CoreData will just store the value you pass, so if you need only 2 digits, you should round the value yourself. Probably better to round the value before it's stored, so you only have to do it once per result.
As it turns out, the problem is not RESTKit. In fact, the problem appears to be Core Data. This is why printing to the console in my setter method printed the correctly formatted number, but the number has the wrong precision in the db. The best remedy for this situation was to override the getter method and format the number there, after it has been pulled from Core Data. The following seems to work...let me know if you've found anything else that works.
- (NSDecimalNumber*)amount {
[self willAccessValueForKey:#"amount"];
NSDecimalNumber* unroundedAmount = [self primitiveValueForKey:#"amount"];
[self didAccessValueForKey:#"amount"];
NSDecimalNumberHandler *round = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain
scale:2
raiseOnExactness:NO
raiseOnOverflow:NO
raiseOnUnderflow:NO
raiseOnDivideByZero:YES];
NSDecimalNumber *roundedAmount = [unroundedAmount decimalNumberByRoundingAccordingToBehavior:round];
return roundedAmount;
}
One thing to watch out for is that some (most?) decimal numbers can't be stored in floating-point binary in exactly the same way we think of them. There's alot to read on it out there, but I like the detailed approach of this one: http://www.cprogramming.com/tutorial/floating_point/understanding_floating_point_representation.html.
So for your number, the .24 can't be represented in floating-point because the binary can only give that the decimal component of a number is made up of .5, .25, .125, ... (that might be missing some special rules, but it's the general idea). So to represent .24 it uses as many of the lower values to get as close as it can.
NSDecimalNumber was set up to round it, but if you don't tell it to round anything you'll see the same 8.23999... value there too.
Related
I've been googling and trying to understand how things work with the Float values in swift, but can't seem to make any sense of it, I would really appreciate any help, I feel I'm just wasting my time.
For example, let's say that I have an API that returns some json data, I parse that data, make some calculations and then present some of the data to the user, something like this:
let balance : String = "773480.67" // value that was received through json api
let commission : String = "100000.00" // value that was received through json api
//framework maps the json properties
let floatBalance : Float = Float(balance)! // at this point value is 773480.688
let floatCommission : Float = Float(commission)! //100000.0
//we do some math with the values
let result : Float = floatBalance + floatCommission // this is somehow 873480.687
//and then show some of the values on a label
print("stringBalance: \(balance)") //stringBalance: 773480.67
print("floatBalance: \(floatBalance)") //floatBalance: 773481.0
print("floatCommission: \(floatCommission)") //floatCommission: 100000.0
print("result: \(result)") //result: 873481.0
print("label: \(String(format:"%.2f", result))") //label: 873480.69
print("just kill me now")
I'm using the EVReflection framework to map the json properties to an object, so the conversion from String to Float is done in the background without me doing much about it, but the values shown above are basically what I'm working with.
My question is, what do I need to do at the end to get the correct string (873480.67) from the resulting float (873480.687) or is my approach wrong from the start?
Thank you
Actually floats can not represent numbers accurately, you'll have to use Double.
Here is a very nice answer on that issue:
https://stackoverflow.com/a/3730040/4662531
EDIT:
Sorry, but actually Double should not be use to perform calculations (I'm assuming from the naming of your variables you are working on some banking things). That part of the above linked answer is really giving a great suggestion:
A solution that works in just about any language is to use integers
instead, and count cents. For instance, 1025 would be $10.25. Several
languages also have built-in types to deal with money. Among others,
Java has the BigDecimal class, and C# has the decimal type.
A colleague of mine that used to work in a banking company also confirmed that all calculations were done without using Floats or Double, but with Int as suggested in the link.
I noticed something weird in one of my applications and for some really reason, if I submit a small number, say 1,000,000,000, it will function normally but if I submit a large number, say 12,345,678,987,654,321, in this case it was 2147483647. So i set up a simple app that would just take the number and make it a label's text by
var theNum = textField.text.bridgeToObjectiveC().intValue
numLabel.text = "\(theNum)"
Now for smaller numbers it work but big numbers like the one above it doesn't. I should say that I am transferring the variables between views in case that has something to do with it. Also, I know the above code is in Swift but it does the same thing in Objective C. This is really messing with me because I can't find any reason that it would do this so all help will be appreciated!
Thanks
To hold some very large numbers or numbers with high precision you should use the NSDecimalNumber class. Note this is a class, not a primitive, you need to allocate it, call a stringValue to get the string and to do operations on it you again need to use methods. A short example:
NSDecimalNumber *number = [[NSDecimalNumber alloc] initWithString:textField.text];
textField.text = number.stringValue;
number = [number decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:10]];
In Core Data, I have many attributes declared as Integer 64, and then accessed through NSNumber properties (this is by default).
Does it matter if I store and access these values by:
NSNumber *mySetValue = [NSNumber numberWithInt:someIntValue];
[myObject setMyNumberProperty:mySetValue];
int myRetrievedValue = [myObject.myNumberProperty intValue];
or by
NSNumber *mySetValue = [NSNumber numberWithInteger:someIntegerValue];
[myObject setMyNumberProperty:mySetValue];
NSInteger myRetrievedValue = [myObject.myNumberProperty integerValue];
?
There are two case for which I would like to know the answer: 1) if the value needed is used for calculations (it holds a quantity or a value that will be converted to currency) and 2)if the value is just a type which will basically only be compared against itself and will not be used for any calculations. Is it okay to use numberWithInt and intValue in one case and not the other, both cases, or must numberWithInteger and integerValue be used in both cases?
Also, does it matter if I have previously stored all of the values as [NSNumber numberWithInt:] - can I simply change the way I store/retrieve the value now, or do I need to maintain consistency so as not to create a problem with current user data?
I am particularly interested in this working in both a 32 bit and 64 bit iOS app.
Also - does it make a difference to your answer if the Core Data value is Integer 32, Integer 16, Integer 64, etc?
You should be using NSInteger whenever you can. The reason is that it will be platform independent. On 32-bit architecture, an NSInteger will be an int, on 64-bit a long.
Therefore, you are OK having used the int-methods before - it is the smaller subset of the two.
What you have stored in your Core Data database is also OK for the same reason. The fact that you set the value to Integer64 ensures, that also long numbers will be stored correctly in the future.
The use as currency is also OK, with some caveats. If you are mainly interested in cents, not fraction of cents, you can obviously just keep track of the cents as integers. However, if you want to do more complex calculations that could involve fractions of cents, such as some accounting methods or currency conversion, and store these results you would need something like a float or (better) a double.
I am parsing some vertice information from an XML file which reads as follows (partial extract) :
21081.7 23447.6 2781.62 24207.4 18697.3 -2196.96
I save the string as an NSString and then convert to a float value (which I will later feed into OpenGL ES)
NSString * xPoint = [finishedParsingArray objectAtIndex:baseIndex];
NSLog(#"xPoiint is %#", xPoint);
float x = [xPoint floatValue];
The problem is that float x changes the values as follows :
21081.699219, 23447.599609, 2781.620117, 24207.400391, 18697.300781, -2196.959961
As you can see, it is changing the number of decimal places (not sure how it is doing this - must be hidden formatting in the xml file ?)
My question is how can I store the float to match the original number in the NSString / XML file to the same number of decimal places ?
Thanks in advance !
Your issue seems to be that you don't understand how floats are stored in memory and don't know that floats aren't precise.
Exact values often can't be stored and so the system picks the closest number it can to represent it. If you look carefully, you can see that each of the outputted numbers is very close to your inputted values.
For better accuracy, try using double instead. Double does encounter the same problems, but with better precision. Floats have about 6 significant digits; doubles have more than twice that. Source
Here are some other StackOverflow answers and external articles you should read:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
Floating Points on Wikipedia
This answer on a similar question
All primitives which store floating point numbers have an accuracy issue. Most of the time it's so small it doesn't matter, but sometimes it's vital.
When it's important to keep the exact number, I would suggest using NSDecimalNumber.
Currently, [NSDecimalNumber longLongValue] created with string #"9999999999999999" returns 10000000000000000.
This means the class converts it's value to double first, and re-converts into SInt64(signed long long)
How to evade this behavior? I want to get precise integral number within the range of SInt64.
PS.
I considered about converting to NSString and re-converting into SInt64 with NSScanner or strtoll, but I believe there's better way. But if you sure about there's no other way, please tell me that.
First: unless you're sure it's performance-critical, I'd write it into a string and scan it back. That's the easy way.
Now, if you really want to do it otherwise:
get an NSDecimal from your NSDecimalNumber
work with the private fields of the structure, initialize your long long value from the mantissa (possibly introduce checks to handle too-large mantissas)
multiply by 10^exponent; you can do that using binary exponentiation; again, check for overflow
Start with an NSDecimalNumber* originalValue.
Let int64_t approx = [originalValue longLongValue]. This will not be exact, but quite close.
Convert approx to NSDecimalNumber, calculate originalValue - approx, take the longLongValue, and add to approx. Now you got the correct result.