浮点数在计算机中表达为二进制(binary)小数。例如:十进制小数:
0.125
是 1/10 + 2/100 + 5/1000 的值,同样二进制小数:
0.001
是 0/2 + 0/4 + 1/8。这两个数值相同。唯一的实质区别是第一个写为十进制小数记法,第二个是二进制。
遗憾的是,大多数十进制小数不能精确的表达二进制小数。
这个问题更早的时候首先在十进制中发现。考虑小数形式的 1/3 ,你可以来个十进制的近似值。
0.3
或者更进一步的,
0.33
或者更进一步的,
0.333
诸如此类。如果你写多少位,这个结果永远不是精确的 1/3 ,但是可以无限接近 1/3 。
同样,无论在二进制中写多少位,十进制数 0.1 都不能精确表达为二进制小数。二进制来表达 1/10 是一个无限循环小数:
0.0001100110011001100110011001100110011001100110011...
在任意无限位数值中中止,你可以得到一个近似。
在一个典型的机器上运行 Python,一共有53位的精度来表示一个浮点数,所以当你输入十进制的0.1 的时候,看到是一个二进制的小数:
0.00011001100110011001100110011001100110011001100110011010
非常接近,但是不完全等于,1/10。
这是很容易忘记,存储的值是一个近似的原小数,由于浮体的方式,显示在提示符的解释。Python 中只打印一个小数近似的真实机器所存储的二进制近似的十进制值。如果 Python 要打印存储的二进制近似真实的十进制值 0.1,那就要显示:
~~~
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
~~~
这么多位的数字对大多数人是没有用的,所以 Python 显示一个舍入的值
~~~
>>> 1 / 10
0.1
~~~
只要记住即使打印的结果看上去是精确的 1/10,真正存储的值是最近似的二进制小数。
有趣地是,存在许多不同的十进制数共享着相同的近似二进制小数。例如,数字 0.1 和0.10000000000000001 以及 0.1000000000000000055511151231257827021181583404541015625 都是3602879701896397 / 2 ** 55 的近似值。因为所有这些十进制数共享相同的近似值,在保持恒等式eval(repr(x)) == x 的同时,显示的可能是它们中的任何一个。
历史上,Python 提示符和内置的 repr() 函数选择一个 17 位精度的数字,0.10000000000000001。从 Python 3.1 开始,Python(在大多数系统上)能够从这些数字当中选择最短的一个并简单地显示0.1。
注意,这是二进制浮点数的自然性质:它不是 Python 中的一个 bug,也不是你的代码中的 bug。你会看到所有支持硬件浮点数算法的语言都会有这个现象(尽管有些语言默认情况下或者在所有输出模式下可能不会 _显示_ 出差异)。
为了输出更好看,你可能想用字符串格式化来生成固定位数的有效数字:
~~~
>>> format(math.pi, '.12g') # give 12 significant digits
'3.14159265359'
>>> format(math.pi, '.2f') # give 2 digits after the point
'3.14'
>>> repr(math.pi)
'3.141592653589793'
~~~
认识到这,在真正意义上,是一种错觉是很重要的:你在简单地舍入真实机器值的 _显示_。
例如,既然 0.1 不是精确的 1/10,3 个 0.1 的值相加可能也不会得到精确的 0.3:
~~~
>>> .1 + .1 + .1 == .3
False
~~~
另外,既然 0.1 不能更接近 1/10 的精确值而且 0.3 不能更接近 3/10 的精确值,使用 round() 函数提前舍入也没有帮助:
~~~
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
~~~
虽然这些数字不可能再更接近它们想要的精确值,round() 函数可以用于在计算之后进行舍入,这样的话不精确的结果就可以和另外一个相比较了:
~~~
>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
~~~
二进制浮点数计算有很多这样意想不到的结果。“0.1”的问题在下面”误差的表示”一节中有准确详细的解释。更完整的常见怪异现象请参见 [浮点数的危险](http://www.lahey.com/float.htm)。
最后我要说,“没有简单的答案”。也不要过分小心浮点数!Python 浮点数计算中的误差源之于浮点数硬件,大多数机器上每次计算误差不超过 2**53 分之一。对于大多数任务这已经足够了,但是你要在心中记住这不是十进制算法,每个浮点数计算可能会带来一个新的舍入错误。
虽然确实有问题存在,对于大多数平常的浮点数运算,你只要简单地将最终显示的结果舍入到你期望的十进制位数,你就会得到你期望的最终结果。str() 通常已经足够用了,对于更好的控制可以参阅 [格式化字符串语法](http://python.usyiyi.cn/python_341/library/string.html#formatstrings) 中 str.format() 方法的格式说明符。
对于需要精确十进制表示的情况,可以尝试使用 decimal 模块,它实现的十进制运算适合会计方面的应用和高精度要求的应用。
fractions 模块支持另外一种形式的运算,它实现的运算基于有理数(因此像1/3这样的数字可以精确地表示)。
如果你是浮点数操作的重度使用者,你应该看一下由 SciPy 项目提供的 Numerical Python 包和其它用于数学和统计学的包。参看 。
当你真的 _真_ 想要知道浮点数精确值的时候,Python 提供这样的工具可以帮助你。float.as_integer_ratio() 方法以分数的形式表示一个浮点数的值:
~~~
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
~~~
因为比值是精确的,它可以用来无损地重新生成初始值:
~~~
>>> x == 3537115888337719 / 1125899906842624
True
~~~
float.hex() 方法以十六进制表示浮点数,给出的同样是计算机存储的精确值:
~~~
>>> x.hex()
'0x1.921f9f01b866ep+1'
~~~
精确的十六进制表示可以用来准确地重新构建浮点数:
~~~
>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True
~~~
因为可以精确表示,所以可以用在不同版本的 Python(与平台相关)之间可靠地移植数据以及与支持同样格式的其它语言(例如 Java 和 C99)交换数据。
另外一个有用的工具是 math.fsum() 函数,它帮助求和过程中减少精度的损失。当数值在不停地相加的时候,它会跟踪“丢弃的数字”。这可以给总体的准确度带来不同,以至于错误不会累积到影响最终结果的点:
~~~
>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True
~~~
- Python 入门指南
- 1. 开胃菜
- 2. 使用 Python 解释器
- 2.1. 调用 Python 解释器
- 2.2. 解释器及其环境
- 3. Python 简介
- 3.1. 将 Python 当做计算器
- 3.2. 编程的第一步
- 4. 深入 Python 流程控制
- 4.1. if 语句
- 4.2. for 语句
- 4.3. range() 函数
- 4.4. break 和 continue 语句, 以及循环中的 else 子句
- 4.5. pass 语句
- 4.6. 定义函数
- 4.7. 深入 Python 函数定义
- 4.8. 插曲:编码风格
- 5. 数据结构
- 5.1. 关于列表更多的内容
- 5.2. del 语句
- 5.3. 元组和序列
- 5.4. 集合
- 5.5. 字典
- 5.6. 循环技巧
- 5.7. 深入条件控制
- 5.8. 比较序列和其它类型
- 6. 模块
- 6.1. 深入模块
- 6.2. 标准模块
- 6.3. dir() 函数
- 6.4. 包
- 7. 输入和输出
- 7.1. 格式化输出
- 7.2. 文件读写
- 8. 错误和异常
- 8.1. 语法错误
- 8.2. 异常
- 8.3. 异常处理
- 8.4. 抛出异常
- 8.5. 用户自定义异常
- 8.6. 定义清理行为
- 8.7. 预定义清理行为
- 9. 类
- 9.1. 术语相关
- 9.2. Python 作用域和命名空间
- 9.3. 初识类
- 9.4. 一些说明
- 9.5. 继承
- 9.6. 私有变量
- 9.7. 补充
- 9.8. 异常也是类
- 9.9. 迭代器
- 9.10. 生成器
- 9.11. 生成器表达式
- 10. Python 标准库概览
- 10.1. 操作系统接口
- 10.2. 文件通配符
- 10.3. 命令行参数
- 10.4. 错误输出重定向和程序终止
- 10.5. 字符串正则匹配
- 10.6. 数学
- 10.7. 互联网访问
- 10.8. 日期和时间
- 10.9. 数据压缩
- 10.10. 性能度量
- 10.11. 质量控制
- 10.12. “瑞士军刀”
- 11. 标准库浏览 – Part II
- 11.1. 输出格式
- 11.2. 模板
- 11.3. 使用二进制数据记录布局
- 11.4. 多线程
- 11.5. 日志
- 11.6. 弱引用
- 11.7. 列表工具
- 11.8. 十进制浮点数算法
- 12. 接下来?
- 13. 交互式输入行编辑历史回溯
- 13.1. 行编辑
- 13.2. 历史回溯
- 13.3. 快捷键绑定
- 13.4. 其它交互式解释器
- 14. 浮点数算法:争议和限制
- 14.1. 表达错误
- 15. 附录
- 15.1. 交互模式