<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>浮点数 on On My Way</title>
    <link>https://tuanzi-wow.cn/tags/%E6%B5%AE%E7%82%B9%E6%95%B0/</link>
    <description>Recent content in 浮点数 on On My Way</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 04 Sep 2026 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://tuanzi-wow.cn/tags/%E6%B5%AE%E7%82%B9%E6%95%B0/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>15. 浮点算术：问题和限制</title>
      <link>https://tuanzi-wow.cn/python/python-15-float/</link>
      <pubDate>Fri, 04 Sep 2026 00:00:00 +0800</pubDate>
      <guid>https://tuanzi-wow.cn/python/python-15-float/</guid>
      <description>二进制浮点数为什么表示不了某些小数，以及舍入误差的处理思路。</description>
      <content:encoded><![CDATA[<p>情景：
0.1永远无法用以2为基底的小数，他是一个无限循环小数
0.0001100110011001100110011001100110011001100110011&hellip;
你只能得到一个近似值
比较有意思的一个点：
1/10 = 0.1 貌似是存储的精确值，但是实际上只是存储的最接近0.1的计算机可表示的二进制分数
于此同时，有许多额十进制数共享该最接近的二进制分数
比如： <code>0.1</code> 、 <code>0.10000000000000001</code> 、 <code>0.1000000000000000055511151231257827021181583404541015625</code> 全都近似于 <code>3602879701896397 / 2 ** 55</code>
如果想要美观的输出，可以通过字符串格式化限定输出的位数</p>
<p>还有一个有趣的现象
0.1 + 0.1 + 0.1 == 0.3为False
这个原理就是上述
但是捏
如果一定要进行不精确表达，可以使用math.isclose()</p>
<h3 id="but">BUT！</h3>
<p>正如那篇文章的结尾所言，“对此问题并无简单的答案。” 但是也不必过于担心浮点数的问题！ Python 浮点运算中的错误是从浮点运算硬件继承而来，而在大多数机器上每次浮点运算的误差不超过 2**53 分之一。 这对大多数任务来说都已足够，但你确实需要记住它并非十进制算术，且每次浮点运算都可能会导致新的舍入错误。</p>
<p>而如果想要进行精确的十进制模块的计算，尝试使用decimal模块或者fractions模块
还有很多模块，但是我懒得赘述了
自己看看吧</p>
<h2 id="151表示性错误">15.1.表示性错误</h2>
<p>定义：指的是某些十进制小数无法以二进制精确表示这一事实造成的失误本小节将详细解释 &ldquo;0.1&rdquo; 的例子，并说明你可以怎样亲自对此类情况进行精确分析。 假定前提是已基本熟悉二进制浮点表示法。</p>
<p><em>表示性错误</em> 是指某些（其实是大多数）十进制小数无法以二进制（以 2 为基数的计数制）精确表示这一事实造成的错误。 这就是为什么 Python（或者 Perl、C、C++、Java、Fortran 以及许多其他语言）经常不会显示你所期待的精确十进制数值的主要原因。</p>
<p>为什么会这样？ 1/10 是无法用二进制小数精确表示的。 至少从 2000 年起，几乎所有机器都使用 IEEE 754 二进制浮点运算标准，而几乎所有系统平台都将 Python 浮点数映射为 IEEE 754 binary64 &ldquo;双精度&rdquo; 值。 IEEE 754 binary64 值包含 53 位精度，因此在输入时计算机会尽量将 0.1 转换为以 <em>J</em>/2**<em>N</em> 形式所能表示的最接近的小数，其中 <em>J</em> 为恰好包含 53 比特位的整数。 重新将</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">1 / 10 ~= J / (2**N)
</span></span></code></pre></td></tr></table>
</div>
</div><p>写为</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">J ~= 2**N / 10
</span></span></code></pre></td></tr></table>
</div>
</div><p>并且由于 <em>J</em> 恰好有 53 位 (即 <code>&gt;= 2**52</code> 但 <code>&lt; 2**53</code>)，<em>N</em> 的最佳值为 56:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">2**52 &lt;=  2**56 // 10  &lt; 2**53
</span></span><span class="line"><span class="cl">True
</span></span></code></pre></td></tr></table>
</div>
</div><p>也就是说，56 是唯一能使 <em>J</em> 恰好有 53 位的 <em>N</em> 值。 这样 <em>J</em> 可能的最佳就是舍入之后的商:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">q, r = divmod(2**56, 10)
</span></span><span class="line"><span class="cl">r
</span></span><span class="line"><span class="cl">6
</span></span></code></pre></td></tr></table>
</div>
</div><p>由于余数超过 10 的一半，所以最佳近似值可通过向上舍入获得:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">q+1
</span></span><span class="line"><span class="cl">7205759403792794
</span></span></code></pre></td></tr></table>
</div>
</div><p>因此在 IEEE 754 双精度下可能达到的 1/10 的最佳近似值为:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">7205759403792794 / 2 ** 56
</span></span></code></pre></td></tr></table>
</div>
</div><p>分子和分母都除以二则结果小数为:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">3602879701896397 / 2 ** 55
</span></span></code></pre></td></tr></table>
</div>
</div><p>请注意由于我们做了向上舍入，这个结果实际上略大于 1/10；如果我们没有向上舍入，则商将会略小于 1/10。 但无论如何它都不会是 <em>精确的</em> 1/10！</p>
<p>因此计算机永远不会 &ldquo;看到&rdquo; 1/10: 它实际看到的就是上面所给出的小数，即它能达到的最佳 IEEE 754 双精度近似值:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">0.1 * 2 ** 55
</span></span><span class="line"><span class="cl">3602879701896397.0
</span></span></code></pre></td></tr></table>
</div>
</div><p>如果我们将该小数乘以 10**55，我们可以看到该值输出 55 个十进制数位:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">3602879701896397 * 10 ** 55 // 2 ** 55
</span></span><span class="line"><span class="cl">1000000000000000055511151231257827021181583404541015625
</span></span></code></pre></td></tr></table>
</div>
</div><p>这意味着存储在计算机中的确切数字等于十进制数值 0.1000000000000000055511151231257827021181583404541015625。 许多语言（包括较旧版本的 Python）都不会显示这个完整的十进制数值，而是将结果舍入到 17 位有效数字:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">format(0.1, &#39;.17f&#39;)
</span></span><span class="line"><span class="cl">&#39;0.10000000000000001&#39;
</span></span></code></pre></td></tr></table>
</div>
</div><p><a href="https://docs.python.org/zh-cn/3/library/fractions.html#module-fractions"><code>fractions</code></a> 和 <a href="https://docs.python.org/zh-cn/3/library/decimal.html#module-decimal"><code>decimal</code></a> 模块使得这样的计算更为容易:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">from decimal import Decimal
</span></span><span class="line"><span class="cl">from fractions import Fraction
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fraction.from_float(0.1)
</span></span><span class="line"><span class="cl">Fraction(3602879701896397, 36028797018963968)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">(0.1).as_integer_ratio()
</span></span><span class="line"><span class="cl">(3602879701896397, 36028797018963968)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Decimal.from_float(0.1)
</span></span><span class="line"><span class="cl">Decimal(&#39;0.1000000000000000055511151231257827021181583404541015625&#39;)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">format(Decimal.from_float(0.1), &#39;.17&#39;)
</span></span><span class="line"><span class="cl">&#39;0.10000000000000001&#39;
</span></span></code></pre></td></tr></table>
</div>
</div><p>自己看看吧，讲得还是挺清楚的</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
