这里有两个不同的问题。第一个——正如在评论中已经提到的那样——二进制浮点数不能精确表示数字8.7
。Swift使用IEEE 754标准来表示单精度和双精度浮点数,如果您进行赋值操作,会发生舍入错误。
let x = 8.7
那么最接近的可表示数字将存储在x
中,即为
8.699999999999999289457264239899814128875732421875
关于这个问题,更多信息可以在优秀的问答页面Is floating point math broken?中找到。
第二个问题是:为什么有时候这个数字会被打印成"8.7",有时候会被打印成"8.6999999999999993"?
let str = "8.7"
print(Double(str))
let x = 8.7
print(x)
Double("8.7")
和8.7
有什么不同?它们哪一个更精确呢?
为了回答这些问题,我们需要了解print()
函数的工作方式:
- 如果一个参数符合
CustomStringConvertible
,那么print
函数调用它的description
属性并将结果打印到标准输出。
- 否则,如果一个参数符合
CustomDebugStringConvertible
,那么print
函数调用它的debugDescription
属性并将结果打印到标准输出。
- 否则,使用其他机制。 (这里未导入,与我们的目的无关。)
Double
类型符合CustomStringConvertible
,因此
let x = 8.7
print(x) // 8.7
生成与
let x = 8.7
print(x.description) // 8.7
但是在发生什么事情呢?
let str = "8.7"
print(Double(str)) // Optional(8.6999999999999993)
Double(str)
是一个可选类型,而struct Optional
不符合CustomStringConvertible
协议,但符合CustomDebugStringConvertible
协议。因此,打印函数调用Optional
的debugDescription
属性,进而调用底层Double
的debugDescription
属性。因此,除了是可选类型以外,输出的数字与常规情况下一样。
let x = 8.7
print(x.debugDescription) // 8.6999999999999993
但是对于浮点数,description
和debugDescription
之间有什么区别呢?从Swift源代码中可以看到,两者最终都调用了Stubs.cpp
中的swift_floatingPointToString
函数,并分别将Debug
参数设置为false
和true
。这控制了数字转换成字符串的精度:
int Precision = std::numeric_limits<T>::digits10;
if (Debug) {
Precision = std::numeric_limits<T>::max_digits10;
}
有关这些常量的含义,请参见http://en.cppreference.com/w/cpp/types/numeric_limits:
digits10
- 可表示的十进制数字位数,不会发生变化,
max_digits10
- 必要的十进制数字位数以区分此类型的所有值。
因此,description
创建的字符串小于最大精度。该字符串可以转换为Double
,然后再次转换为字符串,得到相同的结果。debugDescription
创建一个具有更高精度的字符串,因此任何两个不同的浮点值都将产生不同的输出。
摘要:
- 大多数十进制数不能准确地表示为二进制浮点值。
- 浮点类型的
description
和debugDescription
方法在转换为字符串时使用不同的精度。因此,
- 打印可选浮点值与打印非可选值使用不同的精度进行转换。
因此,在打印之前,您可能需要取消封送可选项:
let str = "8.7"
if let d = Double(str) {
print(d)
}
为了更好的控制,可以使用 NSNumberFormatter
或者使用带有 %.<precision>f
格式的格式化打印。
另一个选择是使用 (NS)DecimalNumber
代替 Double
(例如用于货币金额),参见例如Swift中的舍入问题。
%.1f
。您可以通过将值设置为X.X
进行测试,如果出错,它应该返回0.0
... - l'L'l