Round question in quick

I have this information.

let params2: [String: AnyObject] = [ "app_token": myapptoken, "member_access_token": accessToken!, "pay_process": 0, "payamount_credit": 9.87 //hardcode ] 

When printing params2

Result

["app_token": myapptoken, "member_access_token": accessToken, "payamount_credit": 9.869999999999999, "pay_process": 0]

"payamount_credit": 9.87 now "payamount_credit": 9.869999999999999

I tried all the ways that exist around, but it behaves the same.

NSString(format: "%.\2f", 9.87)

Double(round(1000*9.87)/1000)

The strangest thing is that what happens only with this particular number (9.87) is something mystical.

Playground screen.

enter image description here

0
source share
3 answers

Do not use floats or doubles to represent the currency, for the exact (pun intended) this reason. Use NSDecimalNumber instead (see What data type Swift is used for currency ).

NSDecimalNumber makes some effort to use, but does base 10 arithmetic. To use it correctly, you need to read the API a bit. In particular, for rounding you need to provide NSDecimalNumberHandler (you can set a default value for use).

Good reading material:

I think the Playground is showing an unexpected result because it forces the internal value of NSDecimalNumber to Double before displaying it (I would welcome alternative or authoritative explanations because I'm curious). However, the internal representation must be correct.

Try this on your playground:

 let myRoundingBehavior = NSDecimalNumberHandler(roundingMode: .RoundBankers, scale: 2, raiseOnExactness: true, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true) NSDecimalNumber.setDefaultBehavior(myRoundingBehavior) let integerPrice = NSDecimalNumber(mantissa: 987, exponent: -2, isNegative: false) integerPrice // 9.870000000000001 Float(integerPrice) // 9.87 Double(integerPrice) // 9.870000000000001 integerPrice.description // "9.87" 

Do not assume that Float is a more accurate representation of a number; it just works better here. If you keep your calculations (e.g. taxes, discounts, shipping, etc.) in the NSDecimalNumber area, they will be accurate.


Swift 3 / Xcode beta 1 bonus:

The behavior on the playground is still unchanged, but changes to the Great API Renaming Rodeo change the code above to:

 let myRoundingBehavior = NSDecimalNumberHandler(roundingMode: .roundBankers, scale: 2, raiseOnExactness: true, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true) 

The change here is that the global NSRoundingMode enumeration disappears and is replaced with the enum RoundingMode, which is a member of NSDecimalNumber.

+4
source

The problem is the numerical precession , just bet, some numbers cannot be defined as Double without loss of accuracy.

Instead, you should use NSDecimalNumber :

 let num = NSDecimalNumber(string: "9.87") 
+3
source

Try using this function:

 let price: Double = 9.87 func roundDouble(num: Double) -> Float { //Create NSDecimalNumberHandler to specify rounding behavior let numHandler = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundDown, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) let roundedNum = NSDecimalNumber(double: num).decimalNumberByRoundingAccordingToBehavior(numHandler) //Convert to float so you don't get the endless repeating decimal. return Float(roundedNum) } roundDouble(price) //9.87 

I think the best thing that can be done here is to explicitly use float instead of double, because double precision is not needed if you want only two decimal places. This feature just helps round it up a bit more accurately.

+1
source

Source: https://habr.com/ru/post/1257453/


All Articles