我有这个信息。
let params2: [String: AnyObject] = [
"app_token": myapptoken,
"member_access_token": accessToken!,
"pay_process": 0,
"payamount_credit": 9.87 //hardcode
]
打印时
params2
结果是
["app_token": myapptoken, "member_access_token": accessToken, "payamount_credit": 9.869999999999999, "pay_process": 0]
现在的
"payamount_credit": 9.87
是"payamount_credit": 9.869999999999999
我已经尝试了所有现有的方法,但行为都是一样的。
NSString(format: "%.\2f", 9.87)
Double(round(1000*9.87)/1000)
最奇怪的事情是,只有这个特定数字(9.87)才会发生,这是神秘的。
游乐场屏幕。
问题在于数字精度,简单来说,有些数字无法在不损失精度的情况下定义为
Double
。
您应该使用 NSDecimalNumber:
let num = NSDecimalNumber(string: "9.87")
不要使用浮点数或双精度数来表示货币,正是出于这个原因(双关语)。使用 NSDecimalNumber 代替(请参阅我对货币使用哪种 Swift 数据类型)。
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 beta 1 奖励:
Playground 行为仍然相同,但更改了 Great API Renaming Rodeo 将上述代码更改为:
let myRoundingBehavior = NSDecimalNumberHandler(roundingMode: .roundBankers,
scale: 2,
raiseOnExactness: true,
raiseOnOverflow: true,
raiseOnUnderflow: true,
raiseOnDivideByZero: true)
这里的变化是全局 NSRoundingMode 枚举消失,并被作为 NSDecimalNumber 成员的枚举 RoundingMode 取代。
尝试使用此功能:
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
我认为这里最好的做法是显式使用浮点数而不是双精度数,因为如果您只想要两位小数,则不需要双精度数的精度。这个函数只是帮助更准确地舍入它。