NSDecimalNumber to unsignedLongLong - wrong value - iphone

Here is code:
NSNumber* number = [NSDecimalNumber decimalNumberWithString :#"11111111111111111"];
NSLog(#"%# = %lld", number, [number unsignedLongLongValue]);
And I get output:
11111111111111111 = 11111111111111112
Is it known bug or I do something wrong?
UPDATE:
bug reported: https://bugreport.apple.com/cgi-bin/WebObjects/RadarWeb.woa/3/wo/Stnn5Aaocknzx52c64zbN0/5.83.28.0.9

I believe the issue is that you are using NSNumber's -longLongValue method on an NSDecimalNumber. NSDecimalNumber stores a high-precision representation of the number internally as a decimal, but NSNumber just keeps it as an IEEE 754 double precision floating-point value. If I recall correctly, if you use the standard NSNumber superclass methods on an NSDecimalNumber, the internal number is first converted to a double, which can introduce binary floating-point artifacts like this.
As neoneye suggests, a way around this might be to use an NSString as an intermediary. For example:
NSDecimalNumber* number = [NSDecimalNumber decimalNumberWithString :#"11111111111111111"];
double number2 = [#"11111111111111111" doubleValue];
NSLog(#"%# = %lld = %f", number, [[number descriptionWithLocale:nil] longLongValue], number2);
Will produce the result 11111111111111111 = 11111111111111111 = 11111111111111112.000000, showing both the correct result from the decimal number extraction and the error that happens when this number is temporarily stored in a double.

Related

Convert float to float and truncate after two decimal places Objective-C

I have a float var1 = cashInHandAmount = 4.73000002
I simply want as:
var2 = 4.73.
I have tried like this:
NSString *floatString = [NSString stringWithFormat:#"%.02f",cashInHandAmount];//it prints 4.73
[self.editcash setMaxValue:[floatString floatValue]];//but it again sets 4.73000002 why?
can you guys please help me regarding this?
%f simply rounds for the output. Transforming a float value into a string and back does not work, if the exact value (i. e. 4.73) has no representation in the float format. So transforming it back will "round" the stored value 4.73 into the float format, which is obviously 4.730…02.
You should rarely use (binary, IEEE) floats for financial calculating. Financial values (amount of money) is in most cases an integral value of cents, but no float value of dollars (or whatever). Additionally you can think about using NSDecimal and NSDecimalNumber to ensure, that every value with two digits of precision is storable in the format.
Edit:
float f = 4.73000002;
float rounded = roundf(f * 100.0f) / 100.0f;
NSLog(#"%10.10f", rounded);
outputs:
2014-07-01 10:25:48.653 xctest[596:303] 4.7300000191
It is difficult to check, but probably float can not represent 4.73 exactly. The nearest representable value is 4.7300000191. This is what I said: A rounded decimal representation is not always a possible "binary float" representation. You will face that problem with many values.
Try this :
float var1 = 4.73000002;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
formatter.maximumFractionDigits = 2; //Set number of fractional digits
NSNumber *num = [NSNumber numberWithFloat:var1];
NSString *roundedNum = [formatter stringFromNumber:num];
DLOG(#"Answer : %#", roundedNum);
If you want truncate all other decimals you can declare a simple method like this:
- (float)sanitizeFloat:(float)value
{
float newValue = [[NSString stringWithFormat:#"%.2f",value] floatValue];
return newValue;
}
and then
float aFloat = 45.070809;
NSLog(#"%f",[self sanitizeFloat:aFloat]);
In your case:
[self.editcash setMaxValue:[self sanitizeFloat:cashInHandAmount]];
The output will be 45.070000
If you're working with monetary amounts that have fixed decimal places, you should always use NSDecimal or NSDecimalNumber - you shouldn't use floats at all, as they are inexact.
You could use:
-[NSDecimalNumber decimalNumberWithString:]
to create your number, and there are many methods for working with NSDecimalNumbers - see Apple's docs or NSDecimalNumber.h for details.

How to convert NSDecimalNumber to CLLocationDegrees without losing precision?

I need to convert NSDecimalNumber with lat/lon values which I need to convert to CLLocationDegrees. I used -[NSDecimalNumber doubleValue] method. But the value loses its precision. I want the values to be same. The following is what I am talking about(I hope everyone would be aware about this issue already).
NSString *coordStr = #"-33.890934125621094";
NSDecimalNumber *lat = [NSDecimalNumber decimalNumberWithString:coordStr];
NSLog(#"%#", lat); // -33.890934125621094
NSLog(#"%lf", [lat doubleValue]); // -33.890934
Is there any way that I should do instead of doing the above?
Try out to convert the String with the doubleValue method
NSString *coordStr = #"-33.890934125621094";
NSLog(#"%.17g",coordStr.doubleValue); //-33.890934125621094
Check out this post: How to print a double with full precision on iOS?
edit:
the docu says: typedef double CLLocationDegrees; so you can double test = coordStr.doubleValue; the problem is only, that NSLog doesn't print the complete value. but instead the double var saves the complete value.
CLLocationDegree is a double. So the maximum precision can only equal that of a double datatype.

String to double, precision lost

I have 53.025709438664 in string when I do [string_variable doubleValue], I get 53.025709.
I use NSLog(#"%f") to print it.
How I can get full precision value ?
Please have a look at the format specifiers provided in Apple doc.
You cannot get all the digits after decimal point.
Edit:
If you know exact number of digits after decimal, then we could use something as follows
NSString *s = #" 53.025709438664";
double d = [s doubleValue];
NSLog(#"%.12f",d);

basic math problem with Integer and NSDecimalNumber - Invalid operands to binary

Man the simplest stuff always seems so crazy in Objective-C to me, anyways I need to do some basic subtraction and multiplication and am stumped.
I have:
client.PricingDiscount <-- this is an Integer 16 property on a CoreData NSManagedObject
sku.RetailPrice <-- this is a Decimal property on a CoreData NSManagedObject
I am simply trying to get a NSDecimalNumber to display like so:
NSDecimalNumber *showPrice = sku.RetailPrice * (100 - client.PricingDiscount);
I have tried a bunch of different forms of this and cannot figure out what the hell I am doing wrong here.
Standard operators cannot be used with NSDecimalNumber
NSDecimalNumber *one = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithInt:100] decimalValue]];
NSDecimalNumber *factor = [one decimalNumberBySubtracting:[NSDecimalNumber decimalNumberWithDecimal:[client.PricingDiscount decimalValue]]];
NSDecimalNumber *showPrice = [sku.RetailPrice decimalNumerByMultiplying:factor];
NSDecimalNumber is an object wrapper for a number--you are treating it like a C value. Instead, try:
float price = [sku.retailPrice floatValue] * (100 - [client.pricingDiscount floatValue]);
NSDecimalNumber *showPrice = [NSDecimalNumber numberWithFloat:price];
It's confusing, but NSNumber and NSDecimalNumber are Objective-C wrappers for C types, typically used for storage in container objects such as NSArray (or Core Data). NSInteger and NSDecimal, on the other hand, are C types (NSInteger just maps to int).
EDIT: falconcreek's answer is better for avoiding accuracy loss. When working with integers, though, you'll typically use unboxed C types.
NSDecimalNumber *showPrice = sku.RetailPrice * (100 - client.PricingDiscount);
NSDecimalNumber is a class. What you are doing here is calculating a value, and then assigning that to be the value of a pointer to an NSDecimalNumber object. This is bad mojo.
I haven't used NSDecimalNumber before, but if you need that particular object, what you want is something like this:
NSNumber* number = [NSNumber numberWithFloat:(sku.RetailPrice * (100 - client.PricingDiscount))];
NSDecimal* decimal = [number decimalValue];
NSDecimalNumber* showPrice = [NSDecimalNumber decimalNumberWithDecimal:decimal];

How to display currency without rounding as string in Xcode?

I have trouble when I have currency value
999999999999999999.99
From that value, I want to display it as String. The problem is that value always rounded to
1000000000000000000.00
I'm not expect that value rounded. I want the original value displayed. Do you have any idea how to solve this problem? I tried this code from some answer/turorial in stackoverflow.com :
NSMutableString *aString = [NSMutableString stringWithCapacity:30];
NSNumberFormatter *aCurrency = [[NSNumberFormatter alloc]init];
[aCurrency setFormatterBehavior:NSNumberFormatterBehavior10_4];
[aCurrency setNumberStyle:NSNumberFormatterCurrencyStyle];
[aCurrency setMinimumFractionDigits:2];
[aCurrency setMaximumFractionDigits:2];
[aString appendString:[aCurrency stringFromNumber:productPrice]];
//productPrice is NSDecimalNumber which is have value 999999999999999999.99
cell.textLabel.text = aString;
NSLog(#"the price = %#",cell.textLabel.text);
//it prints $1,000,000,000,000,000,000.00
[aCurrency release];
Unless you have very good reasons not to, it is generally best to keep currency values in a fixed-point format, rather than floating point. Ada supports this directly, but for C-ish languages what you do is keep the value in units of pennies, rather than dollars, and only do the conversion whenever you go to display it.
So in this case the value of productPrice would be 99999999999999999999 (cents). To display it, you'd do something like this (If this were C. I don't know the language):
int const modulus = productPrice % 100;
printf ("The price = %d.%d\n", (int) ((productPrice - modulus) / 100), modulus);
I'd also use an integer rather than a floating point variable to keep the value in almost all cases. It won't work in this case (even if you use a 64-bit integer) because your value is mind-bogglingly large. We're talking 1 million times larger than the US National Debt! If a dollar value that large ever makes sense for anything in my lifetime, we are all in big trouble.
Have you tried these:
* – setRoundingBehavior:
* – roundingBehavior
* – setRoundingIncrement:
* – roundingIncrement
* – setRoundingMode:
* – roundingMode