What's The Correct Number Type for Financial Variables in Swift

What's the correct number type for financial variables in Swift?

If you are concerned about storing for example $1.23 in a float or double, and the potential inaccuracies you will get from floating point precision errors, that is if you actually want to stick to integer amounts of cents or pence (or whatever else). Then use an integer to store your value and use the pence/cent as your unit instead of pounds/dollars. You will then be 100% accurate when dealing in integer amounts of pence/cents, and it's easier than using a class like NSDecimalNumber. The display of that value is then purely a presentation issue.

If however you need to deal with fractions of a pence/cent, then NSDecimalNumber is probably what you want.

I recommend looking into how classes like this actually work, and how floating point numbers work too, because having an understanding of this will help you to see why precision errors arise and just what the precision limits are of a class like NSDecimalNumber, why it's better for storing decimal numbers, why floats are good at storing numbers like 17/262144 (i.e. where the denominator is a power of two) but can't store 1/100, etc.

Why not use Double or Float to represent currency?

Because floats and doubles cannot accurately represent the base 10 multiples that we use for money. This issue isn't just for Java, it's for any programming language that uses base 2 floating-point types.

In base 10, you can write 10.25 as 1025 * 10-2 (an integer times a power of 10). IEEE-754 floating-point numbers are different, but a very simple way to think about them is to multiply by a power of two instead. For instance, you could be looking at 164 * 2-4 (an integer times a power of two), which is also equal to 10.25. That's not how the numbers are represented in memory, but the math implications are the same.

Even in base 10, this notation cannot accurately represent most simple fractions. For instance, you can't represent 1/3: the decimal representation is repeating (0.3333...), so there is no finite integer that you can multiply by a power of 10 to get 1/3. You could settle on a long sequence of 3's and a small exponent, like 333333333 * 10-10, but it is not accurate: if you multiply that by 3, you won't get 1.

However, for the purpose of counting money, at least for countries whose money is valued within an order of magnitude of the US dollar, usually all you need is to be able to store multiples of 10-2, so it doesn't really matter that 1/3 can't be represented.

The problem with floats and doubles is that the vast majority of money-like numbers don't have an exact representation as an integer times a power of 2. In fact, the only multiples of 0.01 between 0 and 1 (which are significant when dealing with money because they're integer cents) that can be represented exactly as an IEEE-754 binary floating-point number are 0, 0.25, 0.5, 0.75 and 1. All the others are off by a small amount. As an analogy to the 0.333333 example, if you take the floating-point value for 0.01 and you multiply it by 10, you won't get 0.1. Instead you will get something like 0.099999999786...

Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, errors will compound and you'll end up with values that are visibly not accurate. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.

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 Rust has the rust_decimal crate, and C# has the decimal type.

Which Swift datatype do I use for currency

I suggest you start with a typealias for Decimal. Example:

typealias Dollars = Decimal
let a = Dollars(123456)
let b = Dollars(1000)
let c = a / b
print(c)

Output:

123.456

If you are trying to parse a monetary value from a string, use a NumberFormatter and set its generatesDecimalNumbers property to true.

How to format a Double into Currency - Swift 3

You can use this string initializer if you want to force the currency to $:

String(format: "Tip Amount: $%.02f", tipAmount)

If you want it to be fully dependent on the locale settings of the device, you should use a NumberFormatter. This will take into account the number of decimal places for the currency as well as positioning the currency symbol correctly. E.g. the double value 2.4 will return "2,40 €" for the es_ES locale and "¥ 2" for the jp_JP locale.

let formatter = NumberFormatter()
formatter.locale = Locale.current // Change this to another locale if you want to force a specific locale, otherwise this is redundant as the current locale is the default already
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: tipAmount as NSNumber) {
tipAmountLabel.text = "Tip Amount: \(formattedTipAmount)"
}

Trying to format a UITextField to behave like a currency calculator for only numeric input in iOS

Here's a nice way to do it. Remember to set your UITextField to self in the viewDidLoad method and your header file must conform to the UITextFieldDelegate protocol

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{

NSString *cleanCentString = [[textField.text componentsSeparatedByCharactersInSet: [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""];
NSInteger centValue = [cleanCentString intValue];
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
NSNumber *myNumber = [f numberFromString:cleanCentString];
NSNumber *result;

if([textField.text length] < 16){
if (string.length > 0)
{
centValue = centValue * 10 + [string intValue];
double intermediate = [myNumber doubleValue] * 10 + [[f numberFromString:string] doubleValue];
result = [[NSNumber alloc] initWithDouble:intermediate];
}
else
{
centValue = centValue / 10;
double intermediate = [myNumber doubleValue]/10;
result = [[NSNumber alloc] initWithDouble:intermediate];
}

myNumber = result;
NSLog(@"%ld ++++ %@", (long)centValue, myNumber);
NSNumber *formatedValue;
formatedValue = [[NSNumber alloc] initWithDouble:[myNumber doubleValue]/ 100.0f];
NSNumberFormatter *_currencyFormatter = [[NSNumberFormatter alloc] init];
[_currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
textField.text = [_currencyFormatter stringFromNumber:formatedValue];
return NO;
}else{

NSNumberFormatter *_currencyFormatter = [[NSNumberFormatter alloc] init];
[_currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
textField.text = [_currencyFormatter stringFromNumber:00];

UIAlertView *alert = [[UIAlertView alloc]initWithTitle: @"Deposit Amount Limit"
message: @"You've exceeded the deposit amount limit. Kindly re-input amount"
delegate: self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"OK",nil];

[alert show];
return NO;
}
return YES;
}

Confusion with unwrapping an optional value when rounding numbers

First of all, This will help you find the issue:

if let distance = item.distance {
if let distanceInt = Int(distance) {
let x = Float(distanceInt)
let y = Double(round(1000*x)/1000)
print(y)
} else {
print("Distance (\(distance)) is not convertible to Int. It has a value, but this value is not representing an integer number.")
}
} else {
print("distance is nil. It should be some number but it is not set yet")
}

Here you can see this string: "0.45991480288961" can not be converted to an Int. So you need to convert it directly to a Double:

if let distance = item.distance {
if let distanceDouble = Double(distance) {
let x = Float(distanceDouble)
let y = Double(round(1000*x)/1000)
print(y)
} else {
print("Distance (\(distance)) is not convertible to Double. It has a value, but this value is not representing a double number.")
}
} else {
print("distance is nil. It should be some number but it is not set yet")
}


Related Topics



Leave a reply



Submit