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(您可以设置一个默认值来使用)。
值得阅读的好东西:
- http://www.cimgf.com/2008/04/23/cocoa-tutorial-dont-be-lazy-with-nsdecimalnumber-like-me/
- https://gist.github.com/RonJeffries/009495abec81b2b1890b(Swift 便利函数仅供示例)
- https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDecimalNumber_Class/index.html
我认为 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
我认为这里最好的做法是明确使用浮点数而不是双精度数,因为如果您只需要两位小数,则不需要双精度数。这个函数只是帮助它更准确地四舍五入。
我有这个信息。
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(您可以设置一个默认值来使用)。
值得阅读的好东西:
- http://www.cimgf.com/2008/04/23/cocoa-tutorial-dont-be-lazy-with-nsdecimalnumber-like-me/
- https://gist.github.com/RonJeffries/009495abec81b2b1890b(Swift 便利函数仅供示例)
- https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDecimalNumber_Class/index.html
我认为 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
我认为这里最好的做法是明确使用浮点数而不是双精度数,因为如果您只需要两位小数,则不需要双精度数。这个函数只是帮助它更准确地四舍五入。