NSDecimalNumber usage for precision with currency in Swift [duplicate] - swift

This question already has an answer here:
How to store 1.66 in NSDecimalNumber
(1 answer)
Closed 5 years ago.
I've read a lot that NSDecimalNumber is the best format to use when using currency.
However, I'm still getting floating point issues.
For example.
let a: NSDecimalNumber = 0.07 //0.07000000000000003
let b: NSDecimalNumber = 7.dividing(by: 100) //0.06999999999999999
I know I could use Decimal and b would be what I'm expecting:
let b: Decimal = 7 / 100 //0.07
I'm using Core Data in my app. So I'm stuck with NSDecimalNumber. Unless I want convert a lot of NSDecimalNumbers to Decimals.
Can someone help me get 0.07?

The problem is that you’re effectively doing floating point math (with the problems it has faithfully capturing fractional decimal values in a Double) and creating a Decimal (or NSDecimalNumber) from the Double value that already has introduced this discrepancy. Instead, you want to create your Decimal values before doing your division (or before having a fractional Double value, even if a literal).
So, the following is equivalent to your example, whereby it is building a Double representation (with the limitations that entails) of 0.07, and you end up with a value that is not exactly 0.07:
let value = Decimal(7.0 / 100.0) // or NSDecimalNumber(value: 7.0 / 100.0)
Whereas this does not suffer this problem because we are dividing a decimal 7 by a decimal 100:
let value = Decimal(7) / Decimal(100) // or NSDecimalNumber(value: 7).dividing(by: 100)
Or, other ways to create 0.07 value but avoiding Double in the process include using strings:
let value = Decimal(string: "0.07") // or NSDecimalNumber(string: "0.07")
Or specifying the mantissa/significant and exponent:
let value = Decimal(sign: .plus, exponent: -2, significand: 7) // or NSDecimalNumber(mantissa: 7, exponent: -2, isNegative: false)
Bottom line, avoid Double representations entirely when using Decimal (or NSDecimalNumber), and you won't suffer the problem you described.

Related

Unable to reproduce Decimal to unit test a function using NSDecimalRound [duplicate]

This question already has an answer here:
How to store 1.66 in NSDecimalNumber
(1 answer)
Closed 1 year ago.
let decimalA: Decimal = 3.24
let decimalB: Double = 3.24
let decimalC: Decimal = 3.0 + 0.2 + 0.04
print (decimalA) // Prints 3.240000000000000512
print (decimalB) // Prints 3.24
print (decimalC) // Prints 3.24
I'm totally confused. Why do these things happen? I know why floating point numbers lose precision, but I can't understand why Decimal lose precision while storing decimal numbers.
I want to know how can I initialize Decimal type without losing precision. The reason why these happen is also very helpful to me.
Sorry for my poor English.
The problem is that all floating point literals are inferred to have type Double, which results in a loss of precision. Unfortunately Swift can't initialise floating point literals to Decimal directly.
If you want to keep precision, you need to initialise Decimal from a String literal rather than a floating point literal.
let decimalA = Decimal(string: "3.24")!
let double = 3.24
let decimalC: Decimal = 3.0 + 0.2 + 0.04
print(decimalA) // Prints 3.24
print(double) // Prints 3.24
print(decimalC) // Prints 3.24
Bear in mind this issue only happens with floating point literals, so if your floating point numbers are generated/parsed in runtime (such as reading from a file or parsing JSON), you shouldn't face the precision loss issue.

Why is x / 100 == 0 when x != 0 in swift? [duplicate]

This question already has answers here:
Is the Swift divide "/" operator not working or have I missed something?
(3 answers)
Division not working properly in Swift
(3 answers)
Closed 1 year ago.
I have created a for loop in which I calculate a few values.
for i in 1...100{
let xValue = i/100
print(xValue) // returns 0 every time except when i == 100
}
This is a recreation of a part of that for loop. Why is it that I do not get the right value for 'xValue'?
For info I have also tried the following:
let xValue: Float = Float(i/100)
And that doesn't work either, despite me being very specific. I must have forgotten something basic about these arithmetic
operators in swift.
When you divide an Int by an Int, the result will be rounded down. Use a floating point type, like Double or Float for more precision.
for i in 1...100 {
let xValue = Float(i)/100
print(xValue)
}
To address your attempted solution - when you do:
let xValue: Float = Float(i/100)
The Int result is first computed in i/100 (and rounded down to 0) then you are casting to a Float.
Therefore, we cast i to a Float before the division so the result is computed as a Float.
Since i and 100 are both integer values, / will do integer division and the result will be truncated to 0.
Even when you do let xValue: Float = Float(i/100), the result of division inside the parentheses is already truncated to 0 before the value can be converted to a Float.
Convert i to a floating-point value before dividing to prevent the result from being truncated.
for i in 1...100{
let xValue = Float(i)/100
print(xValue)
}

String Format Specifiers : rounding rule used for decimal values

I am using String(format:) to convert a Float. I thought the number would be rounded.
Sometimes it is.
String(format: "%.02f", 1.455) //"1.46"
Sometimes not.
String(format: "%.02f", 1.555) //"1.55"
String(round(1.555 * 100) / 100.0) //"1.56"
I guess 1.55 cannot be represented exactly as binary. And that it becomes something like 1.549999XXXX
But NumberFormatter doesn't seem to cause the same problem... Why? Should it be preferred over String(format:)?
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
if let string = formatter.string(for: 1.555) {
print(string) // 1.56
}
Reference to the problem (to use String (format :) to round a decimal number) can be found in the answers (or more often comments) to these questions: Rounding a double value to x number of decimal places in swift and How to format a Double into Currency - Swift 3. But the problem it covers (math with FloatingPoint) has been dealt with many times on SO (for all languages).
String(format:) does not have the function of rounding a decimal number (even if it is unfortunately proposed in some answers) but of formatting it (as its name suggests). This formatting sometimes causes a rounding. That is true. But we have to keep in mind a problem that the number 1.555 is... not worth 1.555.
In Swift, Double and Float, that conform to the FloatingPoint protocol respect the IEEE 754 specification. However, some values ​​cannot be exactly represented by the IEEE 754 standard.
In the same way that you can't represent a third exactly in a (finite) decimal expansion, there are lots of numbers which look simple in decimal, but which have long or infinite expansions in a binary expansion." (source)
To be convinced of this, we can use The Float Converter to convert between the decimal representation of numbers (like "1.02") and the binary format used by all modern CPUs (IEEE 754 floating point). For 1.555, the value actually stored in float is 1.55499994754791259765625
So the problem does not come from String (format :). For example, we can try another way to round to the thousandth and we find the same problem. :
round (8.45 * pow (10.0, 3.0)) / pow (10.0, 3.0)
// 8.449999999999999
That is how it is : "Binary floating point arithmetic is fine so long as you know what's going on and don't expect values ​​to be exactly the decimal ones you put in your program".
So the real question is : is this really a problem for you to use ? It depends on the app. Generally if we convert a number into a String by limiting its precision (by rounding), it is because we consider that this precision is not useful to the user. If this is the kind of data we're talking about, then it's okay to use a FloatingPoint.
However, to format it it may be more relevant to use a NumberFormatter. Not necessarily for its rounding algorithm, but rather because it allows you to locate the format :
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.locale = Locale(identifier: "fr_FR")
formatter.string(for: 1.55)!
// 1,55
formatter.locale = Locale(identifier: "en_US")
formatter.string(for: 1.55)!
// 1.55
Conversely, if we are in a case where precision matters, we must abandon Double / Float and use Decimal. Still to keep our rounding example, we can use this extension (which may be the best answer to the question "Rounding a double value to x number of decimal places in swift ") :
extension Double {
func roundedDecimal(to scale: Int = 0, mode: NSDecimalNumber.RoundingMode = .plain) -> Decimal {
var decimalValue = Decimal(self)
var result = Decimal()
NSDecimalRound(&result, &decimalValue, scale, mode)
return result
}
}
1.555.roundedDecimal(to: 2)
// 1.56

How do I convert a fractional decimal to a whole number in Swift

What is the easiest way to convert the fractional part of a float decimal (the part on the right) to a whole number integer.
For example:
0.25 converts to 25
0.09 converts to 9
0.90 converts to 90
I've tried several ways, including converting the float to a string and extracting the fraction, but for some reason it leaves off any trailing zeros. For example 0.90 would convert to a string as 0.9.
Here is an example:
let a = 0.90
let fractionalPart = a.truncatingRemainder(dividingBy: 1.0)
let modifiedFractionalPart = Int(fractionalPart * 100.0)
let string = String(modifiedFractionalPart)
// prints 90
If you aren't allowed to multiply by 100.0, meaning you don't actually have to limit your fractional part to two decimal places, rather you need to have the whole part after the . then use the following:
let a = 0.09017
let fractionalPart = String(a).components(separatedBy: ".")[1] // "09017"
Then if you have to convert it to an Int just do:
let fractionalPartInt = Int(fractionalPart) // 09017
Apple's Foundation Framework provides a way to do this:
let numberFormatter = NumberFormatter()
numberFormatter.multiplier = 100
print(numberFormatter.string(from: 0.25))
Specific documentation for NumberFormatter is also available
The advantage of using the NumberFormatter is that it is:
More modular -- if you need to change the conversion factor, just change the multiplier
More expressive -- having self-documenting code is extremely useful when viewing an old project

How to store 1.66 in NSDecimalNumber

I know float or double are not good for storing decimal number like money and quantity. I'm trying to use NSDecimalNumber instead. Here is my code in Swift playground.
let number:NSDecimalNumber = 1.66
let text:String = String(describing: number)
NSLog(text)
The console output is 1.6599999999999995904
How can I store the exact value of the decimal number 1.66 in a variable?
In
let number:NSDecimalNumber = 1.66
the right-hand side is a floating point number which cannot represent
the value "1.66" exactly. One option is to create the decimal number
from a string:
let number = NSDecimalNumber(string: "1.66")
print(number) // 1.66
Another option is to use arithmetic:
let number = NSDecimalNumber(value: 166).dividing(by: 100)
print(number) // 1.66
With Swift 3 you may consider to use the "overlay value type" Decimal instead, e.g.
let num = Decimal(166)/Decimal(100)
print(num) // 1.66
Yet another option:
let num = Decimal(sign: .plus, exponent: -2, significand: 166)
print(num) // 1.66
Addendum:
Related discussions in the Swift forum:
Exact NSDecimalNumber via literal
ExpressibleByFractionLiteral
Related bug reports:
SR-3317
Literal protocol for decimal literals should support precise decimal accuracy, closed as a duplicate of
SR-920
Re-design builtin compiler protocols for literal convertible types.