String to Double conversion loses precision in Swift - ios

I want to covert a string to double and keep the same value:
let myStr = "2.40"
let numberFormatter = NSNumberFormatter()
numberFormatter.locale = NSLocale(localeIdentifier: "fr_FR")
let myDouble = numberFormatter.numberFromString(myStr)?.doubleValue ?? 0.0
myDouble is now
Double? 2.3999999999999999
So how to convert "2.40" to exact be 2.40 as Double ??
Update:
Even rounding after conversion does not seem to work
I don't want to print, I want to calculate and it's important that the number should be correct, it's Money calculation and rates

First off: you don't! What you encountered here is called floating point inaccuracy. Computers cannot store every number precisely. 2.4 cannot be stored lossless within a floating point type.
Secondly: Since floating point is always an issue and you are dealing with money here (I guess you are trying to store 2.4 franc) your number one solution is: don't use floating point numbers. Use the NSNumber you get from the numberFromString and do not try to get a Double out of it.
Alternatively shift the comma by multiplying and store it as Int.
The first solutions might look something like:
if let num = myDouble {
let value = NSDecimalNumber(decimal: num.decimalValue)
let output = value.decimalNumberByMultiplyingBy(NSDecimalNumber(integer: 10))
}

Related

"%.2f" is rounding my number up instead of cutting

I have the following number
7.9775609756097534
and I'm using the code below to only show two decimals
let formatted = String(format: "Angle: %.2f", angle)
the problem is that the result is:
7.98
instead of
7.97
For cases such as yours we use NumberFormatter. It is a class designed to do what you need and more. For your case it should be enough to use the following:
let numberFormatter = NumberFormatter()
numberFormatter.roundingMode = .down
numberFormatter.minimumFractionDigits = 2
numberFormatter.maximumFractionDigits = 2
numberFormatter.string(from: 1.236)
This now locks fraction digits to be exactly 2. By increasing minimum fraction digits more "0" may be appended as in 0.10 may become 0.100. Maximum fraction digits will simply restrict up to what point the number will be displayed.
There are other options as well such as making 1234567.89 show as 1.234.567,89 which is really nice for users that are used to such formatting.
Alternatively, you can do bit string manipulation
let numberInFloat:Float = 7.9775609756097534
let numberInString: String = String(format: "%f", numberInFloat)
let numberParts = numberInString.components(separatedBy: ".")
print(String(format: "Output: %#.%#", String(numberParts[0]), String(numberParts[1].prefix(2))))
Output: 7.97

decimal place value not working in swift

I'm not really sure what I am doing wrong here. I have a double:
let distanceInMiles = distanceInMeters/1609.344 and the results are 2.99685063032388e-09
But I want to round the number to one decimal place, so I do this:
let miles = String(format: "%.1f", distanceInMiles)
But when I print it out miles = 0.0. The decimal place works but it turns my number into 0 instead of 2.9
2.99685063032388e-09 is not equal to 2.9, it is 0.00000000299685063032388
http://www.wolframalpha.com/input/?i=2.99685063032388e-09
There is actually a cool way to do this, using the exponent format specifier instead of float. (From this list https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html)
let miles = String(format: "%.1e", distanceInMiles)

Getting weird value in Double

Hello i made a "Clicker" as a first project while learning swift i have an automated timer that is supposed to remove some numbers from other numbers but sometimes i get values like 0.600000000000001 and i have no idea why.
Here is my "Attack" function that removes 0.2 from the Health of a zombie.
let fGruppenAttackTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("fGruppenAttackTime"), userInfo: nil, repeats: true)
func fGruppenAttackTime() {
zHealth -= 0.2
if zHealth <= 0 {
zHealth = zSize
pPengar += pPengarut
}
...
}
And here is my attackZ button that is supposed to remove 1 from the health of the zombie
#IBAction func attackZ(sender: UIButton) {
zHealth -= Double(pAttack)
fHunger -= 0.05
fGruppenHunger.progress = Float(fHunger / 100)
Actionlbl.text = ""
if zHealth <= 0 {
zHealth = zSize
pPengar += pPengarut
}
}
Lastly here are the variables value:
var zHealth = 10.0
var zSize = 10.0
var pAttack = 1
var pPengar = 0
var pPengarut = 1
When the timer is on and the function is running and i click the button i sometimes get weird values like 0.600000000000001 and if i set the 0.2 in the function to 0.25 i get 0.0999999999999996 sometimes. I wonder why this happens and what to do with it.
In trojanfoe's answer, he shares a link that describes the source of the problem regarding rounding of floating point numbers.
In terms of what to do, there are a number of approaches:
You can shift to integer types. For example, if your existing values can all be represented with a maximum of two decimal places, multiply those by 100 and then use Int types everywhere, excising the Double and Float representations from your code.
You can simply deal with the very small variations that Double type introduces. For example:
If displaying the results in the UI, use NumberFormatter to convert the Double value to a String using a specified number of decimal places.
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 0 // or you might use `2` here, too
formatter.numberStyle = .decimal
print(formatter.string(for: value)!)
By the way, the NSNumberFormatter enjoys another benefit, too, namely that it honors the localization settings for the user. For example, if the user lives in Germany, where the decimal place is represented with a , rather than a ., the NSNumberFormatter will use the user's native number formatting.
When testing to see if a number is equal to some value, rather than just using == operator, look at the difference between two values and seeing if they're within some permissible rounding threshold.
You can use Decimal/NSDecimalNumber, which doesn't suffer from rounding issues when dealing with decimals:
var value = Decimal(string: "1.0")!
value -= Decimal(string: "0.9")!
value -= Decimal(string: "0.1")!
Or:
var value = Decimal(1)
value -= Decimal(sign: .plus, exponent: -1, significand: 9)
value -= Decimal(sign: .plus, exponent: -1, significand: 1)
Or:
var value = Decimal(1)
value -= Decimal(9) / Decimal(10)
value -= Decimal(1) / Decimal(10)
Note, I explicitly avoid using any Double values such as Decimal(0.1) because creating a Decimal from a fractional Double only captures whatever imprecision Double entails, where as the three examples above avoid that entirely.
It's because of floating point rounding errors.
For further reading, see What Every Computer Scientist Should Know About Floating-Point Arithmetic.
Squeezing infinitely many real numbers into a finite number of bits
requires an approximate representation. Although there are infinitely
many integers, in most programs the result of integer computations can
be stored in 32 bits. In contrast, given any fixed number of bits,
most calculations with real numbers will produce quantities that
cannot be exactly represented using that many bits. Therefore the
result of a floating-point calculation must often be rounded in order
to fit back into its finite representation. This rounding error is the
characteristic feature of floating-point computation.

Round function doesn't work as expected

I'm trying round a Double value with two decimal places:
var x = 0.68999999999999995
var roundX = round(x * 100.0) / 100.0
println(roundX) // print 0.69
If print the value is correct.. but the var value isn't that i expect, continue 0.68999999999999995
I need the Double value... not String like other StackOverflow answers :(
Floating point numbers like doubles do not have a number of decimal places. They store values in binary, and a value like .69 can't be represented exactly. It's just the nature of binary floating point on computers.
Use a number formatter, or use String(format:) as #KRUKUSA suggests
var x:Double = 0.68999999999999995
let stringWithTwoDecimals = String(format: "%.2f", x)
println(stringWithTwoDecimals)

NSDecimalNumber issues

In my iOS swift application, I receive some json from the web which contains some double values which represent currency. It looks like this:
[{"Amount": 5.0},{"Amount":-26.07},{"Amount": 4}, ...etc]
I cast these as Doubles and then try to feed these values as a Swift "Double" into the NSDecimalNumber's constructor like this:
let amount = NSDecimalNumber(double: amountAsDouble)
I'm running into problems with this approach because very frequently the NSDecimalNumber I created will contain a different number that goes 16 places passed the decimal point.
let amount = NSDecimalNumber(double: -15.97)
println(amount)
this returns -15.970000000000004096
I don't want this, I want -15.97.
Thanks,
A Double is stored with 18 decimal digits, you can't do anything about that, it's how it works.
Read here: http://en.wikipedia.org/wiki/Double-precision_floating-point_format
However, at the time of displaying the value on the screen, you can use NSNumberFormatter like this:
let amountInDouble: Double = -15.970000000000004096
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.roundingIncrement = 0.01
formatter.maximumFractionDigits = 2
let amountAsString = formatter.stringFromNumber(NSNumber(double: amountInDouble))
if let amountAsString = amountAsString {
println(amountAsString) // -15.97
}
I recently went through this for myself. I ended up using an NSNumberFormatter to get the proper decimal places.
let currFormatter = NSNumberFormatter()
currFormatter.numberStyle = .DecimalStyle
currFormatter.roundingIncrement = 0.01
currFormatter.minimumFractionDigits = 2
currFormatter.maximumFractionDigits = 2
let doubleAmount = currFormatter.numberFromString(amountAsDouble) as NSNumber!
let amount = doubleAmount as Double
println(amount)
Here's a tip: If you use NSJSONSerializer, numbers with decimal points are actually turned into NSDecimalNumber for you. NSDecimalNumber is a subclass of NSNumber. So what you are doing: You've got a perfectly fine NSDecimalNumber, round the value to double, and try to turn the double back into an NSDecimalNumber. Just check that what you have is indeed an NSDecimalNumber, and do no conversion if it is.
This is because the intermediate double representation is causing problems.
You should take the values from your dictionary as NSString objects and use the + decimalNumberWithString: method to convert without losing precision. In swift:
let amount = NSDecimalNumber(string: amountAsString)
let amount = NSDecimalNumber.init(value: -15.97)
let roundValue = amount.rounding(accordingToBehavior: NSDecimalNumberHandler(roundingMode: .bankers, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false))
print(roundValue)

Resources