forked from sixu05202004/pythontutorial3
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfloatingpoint.html
More file actions
463 lines (378 loc) · 36.2 KB
/
floatingpoint.html
File metadata and controls
463 lines (378 loc) · 36.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>15. 浮点数算法:争议和限制 — Python tutorial 3.6.3 documentation</title>
<link href='https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic|Roboto+Slab:400,700|Inconsolata:400,700&subset=latin,cyrillic' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="index" title="Index"
href="genindex.html"/>
<link rel="search" title="Search" href="search.html"/>
<link rel="top" title="Python tutorial 3.6.3 documentation" href="index.html"/>
<link rel="next" title="16. 附录" href="appendix.html"/>
<link rel="prev" title="14. 交互式输入行编辑历史回溯" href="interactive.html"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
</head>
<body class="wy-body-for-nav" role="document">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-nav-search">
<a href="index.html" class="fa fa-home"> Python tutorial</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="appetite.html">1. 开胃菜</a></li>
<li class="toctree-l1"><a class="reference internal" href="interpreter.html">2. 使用 Python 解释器</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interpreter.html#tut-invoking">2.1. 调用 Python 解释器</a></li>
<li class="toctree-l2"><a class="reference internal" href="interpreter.html#tut-interp">2.2. 解释器及其环境</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="introduction.html">3. Python 简介</a><ul>
<li class="toctree-l2"><a class="reference internal" href="introduction.html#tut-calculator">3.1. 将 Python 当做计算器</a></li>
<li class="toctree-l2"><a class="reference internal" href="introduction.html#tut-firststeps">3.2. 编程的第一步</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="controlflow.html">4. 深入 Python 流程控制</a><ul>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#if">4.1. if 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#for">4.2. for 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#range">4.3. range() 函数</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#break-continue-else">4.4. break 和 continue 语句, 以及循环中的 else 子句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#pass">4.5. pass 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#tut-functions">4.6. 定义函数</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#tut-defining">4.7. 深入 Python 函数定义</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#tut-codingstyle">4.8. 插曲:编码风格</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="datastructures.html">5. 数据结构</a><ul>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-morelists">5.1. 关于列表更多的内容</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#del">5.2. del 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-tuples">5.3. 元组和序列</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-sets">5.4. 集合</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-dictionaries">5.5. 字典</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-loopidioms">5.6. 循环技巧</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-conditions">5.7. 深入条件控制</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-comparing">5.8. 比较序列和其它类型</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="modules.html">6. 模块</a><ul>
<li class="toctree-l2"><a class="reference internal" href="modules.html#tut-moremodules">6.1. 深入模块</a></li>
<li class="toctree-l2"><a class="reference internal" href="modules.html#tut-standardmodules">6.2. 标准模块</a></li>
<li class="toctree-l2"><a class="reference internal" href="modules.html#dir">6.3. dir() 函数</a></li>
<li class="toctree-l2"><a class="reference internal" href="modules.html#tut-packages">6.4. 包</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="inputoutput.html">7. 输入和输出</a><ul>
<li class="toctree-l2"><a class="reference internal" href="inputoutput.html#tut-formatting">7.1. 格式化输出</a></li>
<li class="toctree-l2"><a class="reference internal" href="inputoutput.html#tut-files">7.2. 文件读写</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="errors.html">8. 错误和异常</a><ul>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-syntaxerrors">8.1. 语法错误</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-exceptions">8.2. 异常</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-handling">8.3. 异常处理</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-raising">8.4. 抛出异常</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-userexceptions">8.5. 用户自定义异常</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-cleanup">8.6. 定义清理行为</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-cleanup-with">8.7. 预定义清理行为</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="classes.html">9. 类</a><ul>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-object">9.1. 术语相关</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#python">9.2. Python 作用域和命名空间</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-firstclasses">9.3. 初识类</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-remarks">9.4. 一些说明</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-inheritance">9.5. 继承</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-private">9.6. 私有变量</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-odds">9.7. 补充</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-exceptionclasses">9.8. 异常也是类</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-iterators">9.9. 迭代器</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-generators">9.10. 生成器</a></li>
<li class="toctree-l2"><a class="reference internal" href="classes.html#tut-genexps">9.11. 生成器表达式</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="stdlib.html">10. Python 标准库概览</a><ul>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-os-interface">10.1. 操作系统接口</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-file-wildcards">10.2. 文件通配符</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-command-line-arguments">10.3. 命令行参数</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-stderr">10.4. 错误输出重定向和程序终止</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-string-pattern-matching">10.5. 字符串正则匹配</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-mathematics">10.6. 数学</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-internet-access">10.7. 互联网访问</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-dates-and-times">10.8. 日期和时间</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-data-compression">10.9. 数据压缩</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-performance-measurement">10.10. 性能度量</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-quality-control">10.11. 质量控制</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-batteries-included">10.12. “瑞士军刀”</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="stdlib2.html">11. 标准库浏览 – Part II</a><ul>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-output-formatting">11.1. 输出格式</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-templating">11.2. 模板</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-binary-formats">11.3. 使用二进制数据记录布局</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-multi-threading">11.4. 多线程</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-logging">11.5. 日志</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-weak-references">11.6. 弱引用</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-list-tools">11.7. 列表工具</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-decimal-fp">11.8. 十进制浮点数算法</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="venv.html">12. 虚拟环境和包</a><ul>
<li class="toctree-l2"><a class="reference internal" href="venv.html#id2">12.1. 简介</a></li>
<li class="toctree-l2"><a class="reference internal" href="venv.html#id4">12.2. 创建虚拟环境</a></li>
<li class="toctree-l2"><a class="reference internal" href="venv.html#pip">12.3. 使用 pip 管理包</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="whatnow.html">13. 接下来?</a></li>
<li class="toctree-l1"><a class="reference internal" href="interactive.html">14. 交互式输入行编辑历史回溯</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interactive.html#tab">14.1. Tab 补全和历史记录</a></li>
<li class="toctree-l2"><a class="reference internal" href="interactive.html#tut-commentary">14.2. 其它交互式解释器</a></li>
</ul>
</li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">15. 浮点数算法:争议和限制</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#tut-fp-error">15.1. 表达错误</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="appendix.html">16. 附录</a><ul>
<li class="toctree-l2"><a class="reference internal" href="appendix.html#tut-interac">16.1. 交互模式</a></li>
</ul>
</li>
</ul>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Python tutorial</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> »</li>
<li>15. 浮点数算法:争议和限制</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/floatingpoint.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document">
<div class="section" id="tut-fp-issues">
<span id="id1"></span><h1>15. 浮点数算法:争议和限制<a class="headerlink" href="#tut-fp-issues" title="Permalink to this headline">¶</a></h1>
<p>浮点数在计算机中表达为二进制(binary)小数。例如:十进制小数:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mf">0.125</span>
</pre></div>
</div>
<p>是 1/10 + 2/100 + 5/1000 的值,同样二进制小数:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mf">0.001</span>
</pre></div>
</div>
<p>是 0/2 + 0/4 + 1/8。这两个数值相同。唯一的实质区别是第一个写为十进制小数记法,第二个是二进制。</p>
<p>不幸的是,大多数十进制小数不能完全用二进制小数表示。结果是,一般情况下,你输入的十进制浮点数仅由实际存储在计算机中的近似的二进制浮点数表示。</p>
<p>这个问题更早的时候首先在十进制中发现。考虑小数形式的 1/3 ,你可以来个十进制的近似值。</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mf">0.3</span>
</pre></div>
</div>
<p>或者更进一步的,</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mf">0.33</span>
</pre></div>
</div>
<p>或者更进一步的,</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mf">0.333</span>
</pre></div>
</div>
<p>诸如此类。如果你写多少位,这个结果永远不是精确的 1/3 ,但是可以无限接近 1/3 。</p>
<p>同样,无论在二进制中写多少位,十进制数 0.1 都不能精确表达为二进制小数。二进制来表达 1/10 是一个无限循环小数:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mf">0.0001100110011001100110011001100110011001100110011</span><span class="o">...</span>
</pre></div>
</div>
<p>在任何有限数量的位停下来,你得到的都是近似值。今天在大多数机器上,浮点数的近似使用的小数以最高的 53 位为分子,2 的幂为分母。至于 1/10 这种情况,其二进制小数是 <code class="docutils literal"><span class="pre">3602879701896397</span> <span class="pre">/</span> <span class="pre">2</span> <span class="pre">**</span> <span class="pre">55</span></code>,它非常接近但不完全等于1/10真实的值。</p>
<p>由于显示方式的原因,许多使用者意识不到是近似值。Python 只打印机器中存储的二进制值的十进制近似值。在大多数机器上,如果 Python 要打印 0.1 存储的二进制的真正近似值,将会显示:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="mf">0.1</span>
<span class="go">0.1000000000000000055511151231257827021181583404541015625</span>
</pre></div>
</div>
<p>这么多位的数字对大多数人是没有用的,所以 Python 显示一个舍入的值</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="mi">1</span> <span class="o">/</span> <span class="mi">10</span>
<span class="go">0.1</span>
</pre></div>
</div>
<p>只要记住即使打印的结果看上去是精确的 1/10,真正存储的值是最近似的二进制小数。</p>
<p>有趣地是,存在许多不同的十进制数共享着相同的近似二进制小数。例如,数字 <code class="docutils literal"><span class="pre">0.1</span></code> 和 <code class="docutils literal"><span class="pre">0.10000000000000001</span></code> 以及 <code class="docutils literal"><span class="pre">0.1000000000000000055511151231257827021181583404541015625</span></code> 都是 <code class="docutils literal"><span class="pre">3602879701896397</span> <span class="pre">/</span> <span class="pre">2</span> <span class="pre">**</span> <span class="pre">55</span></code> 的近似值。因为所有这些十进制数共享相同的近似值,在保持恒等式 <code class="docutils literal"><span class="pre">eval(repr(x))</span> <span class="pre">==</span> <span class="pre">x</span></code> 的同时,显示的可能是它们中的任何一个。</p>
<p>历史上,Python 提示符和内置的 <a class="reference external" href="https://docs.python.org/3/library/functions.html#repr">repr()</a> 函数选择一个 17 位精度的数字,<code class="docutils literal"><span class="pre">0.10000000000000001</span></code>。从 Python 3.1 开始,Python(在大多数系统上)能够从这些数字当中选择最短的一个并简单地显示 <code class="docutils literal"><span class="pre">0.1</span></code>。</p>
<p>注意,这是二进制浮点数的自然性质:它不是 Python 中的一个 bug,也不是你的代码中的 bug。你会看到所有支持硬件浮点数算法的语言都会有这个现象(尽管有些语言默认情况下或者在所有输出模式下可能不会 <em>显示</em> 出差异)。</p>
<p>为了输出更好看,你可能想用字符串格式化来生成固定位数的有效数字:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">format</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">pi</span><span class="p">,</span> <span class="s1">'.12g'</span><span class="p">)</span> <span class="c1"># give 12 significant digits</span>
<span class="go">'3.14159265359'</span>
<span class="gp">>>> </span><span class="nb">format</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">pi</span><span class="p">,</span> <span class="s1">'.2f'</span><span class="p">)</span> <span class="c1"># give 2 digits after the point</span>
<span class="go">'3.14'</span>
<span class="gp">>>> </span><span class="nb">repr</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">pi</span><span class="p">)</span>
<span class="go">'3.141592653589793'</span>
</pre></div>
</div>
<p>认识到这,在真正意义上,是一种错觉是很重要的:你在简单地舍入真实机器值的 <em>显示</em>。</p>
<p>例如,既然 0.1 不是精确的 1/10,3 个 0.1 的值相加可能也不会得到精确的 0.3:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="o">.</span><span class="mi">1</span> <span class="o">+</span> <span class="o">.</span><span class="mi">1</span> <span class="o">+</span> <span class="o">.</span><span class="mi">1</span> <span class="o">==</span> <span class="o">.</span><span class="mi">3</span>
<span class="go">False</span>
</pre></div>
</div>
<p>另外,既然 0.1 不能更接近 1/10 的精确值而且 0.3 不能更接近 3/10 的精确值,使用 <a class="reference external" href="https://docs.python.org/3/library/functions.html#round">round()</a> 函数提前舍入也没有帮助:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">round</span><span class="p">(</span><span class="o">.</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="nb">round</span><span class="p">(</span><span class="o">.</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="nb">round</span><span class="p">(</span><span class="o">.</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="nb">round</span><span class="p">(</span><span class="o">.</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="go">False</span>
</pre></div>
</div>
<p>虽然这些数字不可能再更接近它们想要的精确值,<a class="reference external" href="https://docs.python.org/3/library/functions.html#round">round()</a> 函数可以用于在计算之后进行舍入,这样的话不精确的结果就可以和另外一个相比较了:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">round</span><span class="p">(</span><span class="o">.</span><span class="mi">1</span> <span class="o">+</span> <span class="o">.</span><span class="mi">1</span> <span class="o">+</span> <span class="o">.</span><span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="o">==</span> <span class="nb">round</span><span class="p">(</span><span class="o">.</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="go">True</span>
</pre></div>
</div>
<p>二进制浮点数计算有很多这样意想不到的结果。“0.1”的问题在下面”误差的表示”一节中有准确详细的解释。更完整的常见怪异现象请参见 <a class="reference external" href="http://www.lahey.com/float.htm">浮点数的危险</a>。</p>
<p>最后我要说,“没有简单的答案”。也不要过分小心浮点数!Python 浮点数计算中的误差源之于浮点数硬件,大多数机器上每次计算误差不超过 2**53 分之一。对于大多数任务这已经足够了,但是你要在心中记住这不是十进制算法,每个浮点数计算可能会带来一个新的舍入错误。</p>
<p>虽然确实有问题存在,对于大多数平常的浮点数运算,你只要简单地将最终显示的结果舍入到你期望的十进制位数,你就会得到你期望的最终结果。<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str">str()</a> 通常已经足够用了,对于更好的控制可以参阅 <a class="reference external" href="https://docs.python.org/3/library/string.html#formatstrings">格式化字符串语法</a> 中 <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str.format">str.format()</a> 方法的格式说明符。</p>
<p>对于需要精确十进制表示的情况,可以尝试使用 <a class="reference external" href="https://docs.python.org/3/library/decimal.html#module-decimal">decimal</a> 模块,它实现的十进制运算适合会计方面的应用和高精度要求的应用。</p>
<p><a class="reference external" href="https://docs.python.org/3/library/fractions.html#module-fractions">fractions</a> 模块支持另外一种形式的运算,它实现的运算基于有理数(因此像1/3这样的数字可以精确地表示)。</p>
<p>如果你是浮点数操作的重度使用者,你应该看一下由 SciPy 项目提供的 Numerical Python 包和其它用于数学和统计学的包。参看 <<a class="reference external" href="http://scipy.org">http://scipy.org</a>>。</p>
<p>当你真的 <em>真</em> 想要知道浮点数精确值的时候,Python 提供这样的工具可以帮助你。<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#float.as_integer_ratio">float.as_integer_ratio()</a> 方法以分数的形式表示一个浮点数的值:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">x</span> <span class="o">=</span> <span class="mf">3.14159</span>
<span class="gp">>>> </span><span class="n">x</span><span class="o">.</span><span class="n">as_integer_ratio</span><span class="p">()</span>
<span class="go">(3537115888337719, 1125899906842624)</span>
</pre></div>
</div>
<p>因为比值是精确的,它可以用来无损地重新生成初始值:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">x</span> <span class="o">==</span> <span class="mi">3537115888337719</span> <span class="o">/</span> <span class="mi">1125899906842624</span>
<span class="go">True</span>
</pre></div>
</div>
<p><a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#float.hex">float.hex()</a> 方法以十六进制表示浮点数,给出的同样是计算机存储的精确值:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">x</span><span class="o">.</span><span class="n">hex</span><span class="p">()</span>
<span class="go">'0x1.921f9f01b866ep+1'</span>
</pre></div>
</div>
<p>精确的十六进制表示可以用来准确地重新构建浮点数:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">x</span> <span class="o">==</span> <span class="nb">float</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s1">'0x1.921f9f01b866ep+1'</span><span class="p">)</span>
<span class="go">True</span>
</pre></div>
</div>
<p>因为可以精确表示,所以可以用在不同版本的 Python(与平台相关)之间可靠地移植数据以及与支持同样格式的其它语言(例如 Java 和 C99)交换数据。</p>
<p>另外一个有用的工具是 <a class="reference external" href="https://docs.python.org/3/library/math.html#math.fsum">math.fsum()</a> 函数,它帮助求和过程中减少精度的损失。当数值在不停地相加的时候,它会跟踪“丢弃的数字”。这可以给总体的准确度带来不同,以至于错误不会累积到影响最终结果的点:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">sum</span><span class="p">([</span><span class="mf">0.1</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">)</span> <span class="o">==</span> <span class="mf">1.0</span>
<span class="go">False</span>
<span class="gp">>>> </span><span class="n">math</span><span class="o">.</span><span class="n">fsum</span><span class="p">([</span><span class="mf">0.1</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">)</span> <span class="o">==</span> <span class="mf">1.0</span>
<span class="go">True</span>
</pre></div>
</div>
<div class="section" id="tut-fp-error">
<span id="id5"></span><h2>15.1. 表达错误<a class="headerlink" href="#tut-fp-error" title="Permalink to this headline">¶</a></h2>
<p>这一节详细说明 “0.1” 示例,教你怎样自己去精确的分析此类案例。假设这里你已经对浮点数表示有基本的了解。</p>
<p><em class="dfn">Representation error</em> 提及事实上有些(实际是大多数)十进制小数不能精确的表示为二进制小数。这是 Python (或 Perl,C,C++,Java,Fortran 以及其它很多)语言往往不能按你期待的样子显示十进制数值的根本原因。</p>
<p>这是为什么? 1/10 不能精确的表示为二进制小数。大多数今天的机器(2000年十一月)使用 IEEE-754 浮点数算法,大多数平台上 Python 将浮点数映射为 IEEE-754 “双精度浮点数”。754 双精度包含 53 位精度,所以计算机努力将输入的 0.1 转为 <em>J</em>/2**<em>N</em> 最接近的二进制小数。<em>J</em> 是一个 53 位的整数。改写:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mi">1</span> <span class="o">/</span> <span class="mi">10</span> <span class="o">~=</span> <span class="n">J</span> <span class="o">/</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">N</span><span class="p">)</span>
</pre></div>
</div>
<p>为:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="n">J</span> <span class="o">~=</span> <span class="mi">2</span><span class="o">**</span><span class="n">N</span> <span class="o">/</span> <span class="mi">10</span>
</pre></div>
</div>
<p><em>J</em> 重现时正是 53 位(是 <code class="docutils literal"><span class="pre">>=</span> <span class="pre">2**52</span></code> 而非 <code class="docutils literal"><span class="pre"><</span> <span class="pre">2**53</span></code> ), <em>N</em> 的最佳值是 56:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="mi">2</span><span class="o">**</span><span class="mi">52</span> <span class="o"><=</span> <span class="mi">2</span><span class="o">**</span><span class="mi">56</span> <span class="o">//</span> <span class="mi">10</span> <span class="o"><</span> <span class="mi">2</span><span class="o">**</span><span class="mi">53</span>
<span class="go">True</span>
</pre></div>
</div>
<p>因此,56 是保持 <em>J</em> 精度的唯一 <em>N</em> 值。<em>J</em> 最好的近似值是整除的商:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">q</span><span class="p">,</span> <span class="n">r</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="mi">56</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">r</span>
<span class="go">6</span>
</pre></div>
</div>
<p>因为余数大于 10 的一半,最好的近似是取上界:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">q</span><span class="o">+</span><span class="mi">1</span>
<span class="go">7205759403792794</span>
</pre></div>
</div>
<p>因此在 754 双精度中 1/10 最好的近似值是是 2**56,或:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mi">7205759403792794</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">56</span>
</pre></div>
</div>
<p>分子和分母都除以2将小数缩小到:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="mi">3602879701896397</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">55</span>
</pre></div>
</div>
<p>要注意因为我们向上舍入,它其实比 1/10 稍大一点点。如果我们没有向上舍入,它会比 1/10 稍小一点。但是没办法让它 <em>恰好</em> 是 1/10!</p>
<p>所以计算机永远也不 “知道” 1/10:它遇到上面这个小数,给出它所能得到的最佳的 754 双精度实数:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="o">.</span><span class="mi">1</span> <span class="o">*</span> <span class="mi">2</span><span class="o">**</span><span class="mi">55</span>
<span class="go">7205759403792794.0</span>
</pre></div>
</div>
<p>如果我们把这小数乘以 10**55,我们可以看到其55位十进制数的值:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="mi">3602879701896397</span> <span class="o">*</span> <span class="mi">10</span> <span class="o">**</span> <span class="mi">55</span> <span class="o">//</span> <span class="mi">2</span> <span class="o">**</span> <span class="mi">55</span>
<span class="go">1000000000000000055511151231257827021181583404541015625</span>
</pre></div>
</div>
<p>这表示存储在计算机中的实际值近似等于十进制值 0.1000000000000000055511151231257827021181583404541015625。许多语言(包括旧版本的Python)会把结果舍入到 17 位有效数字,而不是显示全部的十进制值:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">format</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="s1">'.17f'</span><span class="p">)</span>
<span class="go">'0.10000000000000001'</span>
</pre></div>
</div>
<p><a class="reference external" href="https://docs.python.org/3/library/fractions.html#module-fractions">fractions</a> 和 <a class="reference external" href="https://docs.python.org/3/library/decimal.html#module-decimal">decimal</a> 模块使得这些计算很简单:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">decimal</span> <span class="k">import</span> <span class="n">Decimal</span>
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">fractions</span> <span class="k">import</span> <span class="n">Fraction</span>
<span class="gp">>>> </span><span class="n">Fraction</span><span class="o">.</span><span class="n">from_float</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="go">Fraction(3602879701896397, 36028797018963968)</span>
<span class="gp">>>> </span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span><span class="o">.</span><span class="n">as_integer_ratio</span><span class="p">()</span>
<span class="go">(3602879701896397, 36028797018963968)</span>
<span class="gp">>>> </span><span class="n">Decimal</span><span class="o">.</span><span class="n">from_float</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="go">Decimal('0.1000000000000000055511151231257827021181583404541015625')</span>
<span class="gp">>>> </span><span class="nb">format</span><span class="p">(</span><span class="n">Decimal</span><span class="o">.</span><span class="n">from_float</span><span class="p">(</span><span class="mf">0.1</span><span class="p">),</span> <span class="s1">'.17'</span><span class="p">)</span>
<span class="go">'0.10000000000000001'</span>
</pre></div>
</div>
</div>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="appendix.html" class="btn btn-neutral float-right" title="16. 附录">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="interactive.html" class="btn btn-neutral" title="14. 交互式输入行编辑历史回溯"><span class="fa fa-arrow-circle-left"></span> Previous</a>
</div>
<hr/>
<div role="contentinfo">
<p>
© Copyright 2013, D.D.
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'./',
VERSION:'3.6.3',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.StickyNav.enable();
});
</script>
</body>
</html>