swift 轮刊

Round Issue in swift

我有这个信息。

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

打印时params2

The result is

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

"payamount_credit": 9.87现在是"payamount_credit": 9.869999999999999

我已经尝试了所有存在的方式,但表现相同。

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

Double(round(1000*9.87)/1000)

最奇怪的是,只有这个特定的数字 (9.87) 才会发生,这很神秘。

Playground Screen.

不要使用浮点数或双精度数来表示货币,正是出于这个原因(双关语)。请改用 NSDecimalNumber(参见 )。

NSDecimalNumber 需要一些努力才能使用,但它确实执行以 10 为基数的算术运算。要正确使用它,您必须稍微阅读 API。特别是,要进行舍入,您需要提供一个 NSDecimalNumberHandler(您可以设置一个默认值来使用)。

值得阅读的好东西:

认为 Playground 显示了一个意想不到的结果,因为它在显示之前将 NSDecimalNumber 的内部值强制转换为 Double(我欢迎其他或权威的解释,因为我确实觉得这很好奇)。但是内部表示应该是正确的。

在您的 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"

不要假设 Float 是数字的更准确表示,它恰好在这里工作得更好。如果您在 NSDecimalNumber 范围内进行计算(如税收、折扣、运费等),它们将是准确的。


Swift 3 / Xcode 测试版 1 奖励:

Playground 行为仍然相同,但改变了 Great API 重命名 Rodeo 将以上代码更改为:

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

这里的变化是全局 NSRoundingMode 枚举消失了,取而代之的是作为 NSDecimalNumber 成员的枚举 RoundingMode。

问题出在numeric precission,简单地说,有些数字不能定义为Double而不丢失精度。

你应该使用 NSDecimalNumber:

let num = NSDecimalNumber(string: "9.87")

尝试使用这个函数:

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

我认为这里最好的做法是明确使用浮点数而不是双精度数,因为如果您只需要两位小数,则不需要双精度数。这个函数只是帮助它更准确地四舍五入。