Maison > Article > développement back-end > Python 为什么不解决四舍五入(round)的“bug”?
<code class="language-python"><span class="o">>>></span> <span class="k">def</span> <span class="nf">show</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="o">...</span> <span class="s">"""打印一个数,20 位精度"""</span>
<span class="o">...</span> <span class="k">print</span><span class="p">(</span><span class="s">'{:.20f}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
<span class="o">...</span>
<span class="o">>>></span> <span class="n">show</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="mf">1.50000000000000000000</span>
<span class="o">>>></span> <span class="n">show</span><span class="p">(</span><span class="mf">1.25</span><span class="p">)</span>
<span class="mf">1.25000000000000000000</span>
<span class="o">>>></span> <span class="n">show</span><span class="p">(</span><span class="mf">1.245</span><span class="p">)</span>
<span class="mf">1.24500000000000010658</span>
<span class="o">>>></span> <span class="n">show</span><span class="p">(</span><span class="mf">1.45</span><span class="p">)</span>
<span class="mf">1.44999999999999995559</span>
<span class="o">>>></span> <span class="n">show</span><span class="p">(</span><span class="mf">1.415</span><span class="p">)</span>
<span class="mf">1.41500000000000003553</span>
</code>
四舍五入是基于十进制的,在二进制无法精确表示的时候是会有误差的。<code class="language-python"><span class="o">>>></span> <span class="n">Decimal</span><span class="p">(</span><span class="mf">1.45</span><span class="p">)</span>
<span class="n">Decimal</span><span class="p">(</span><span class="s">'1.4499999999999999555910790149937383830547332763671875'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">Decimal</span><span class="p">(</span><span class="s">'1.45'</span><span class="p">)</span>
<span class="n">Decimal</span><span class="p">(</span><span class="s">'1.45'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">Context</span><span class="p">(</span><span class="n">prec</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">rounding</span><span class="o">=</span><span class="n">ROUND_HALF_UP</span><span class="p">)</span><span class="o">.</span><span class="n">create_decimal</span><span class="p">(</span><span class="s">'1.45'</span><span class="p">)</span>
<span class="n">Decimal</span><span class="p">(</span><span class="s">'1.5'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">Decimal</span><span class="p">(</span><span class="s">'1.45'</span><span class="p">)</span><span class="o">.</span><span class="n">normalize</span><span class="p">(</span><span class="n">Context</span><span class="p">(</span><span class="n">prec</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">rounding</span><span class="o">=</span><span class="n">ROUND_HALF_UP</span><span class="p">))</span>
<span class="n">Decimal</span><span class="p">(</span><span class="s">'1.5'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">Decimal</span><span class="p">(</span><span class="n">Decimal</span><span class="p">(</span><span class="s">'1.45'</span><span class="p">)</span><span class="o">.</span><span class="n">quantize</span><span class="p">(</span><span class="n">Decimal</span><span class="p">(</span><span class="s">'.1'</span><span class="p">),</span> <span class="n">rounding</span><span class="o">=</span><span class="n">ROUND_HALF_UP</span><span class="p">))</span>
<span class="n">Decimal</span><span class="p">(</span><span class="s">'1.5'</span><span class="p">)</span>
</code>
Note that this is in the very nature of binary floating-point: this is not a bug in Python, and it is not a bug in your code either. You’ll see the same kind of thing in all languages that support your hardware’s floating-point arithmetic (although some languages may not display the difference by default, or in all output modes).
round(number[, ndigits])
Return the floating point value number rounded to ndigits digits after the decimal point. If ndigits is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0.
<code class="language-text">round(0.5) is 1.0 and round(-0.5) is -1.0)
</code>
题主测试的和那个bug无关。round是四舍六入五成双的。
python 的官方实现确实有问题,至于为什么作者不改进,原作者可能是什么心态,可以参见刘海洋的答案。大致心态也许是:反正这个错误不在我,所以虽然有方法,但我也不会去解决这个问题。<code class="language-c"><span class="cp">#include <stdio.h></stdio.h></span>
<span class="kt">float</span> <span class="nf">my_round</span><span class="p">(</span><span class="kt">float</span> <span class="n">src</span><span class="p">,</span> <span class="kt">int</span> <span class="n">idx</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="n">idx</span><span class="p">;</span><span class="n">i</span><span class="o">--</span><span class="p">;)</span>
<span class="n">src</span> <span class="o">*=</span><span class="mi">10</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">dest</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">src</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">src</span> <span class="o">>=</span> <span class="n">dest</span><span class="o">+</span><span class="mf">0.5</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="n">idx</span><span class="p">;</span><span class="n">i</span><span class="o">--</span><span class="p">;)</span>
<span class="n">dest</span> <span class="o">/=</span><span class="mi">10</span><span class="p">;</span>
<span class="k">return</span> <span class="n">dest</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"result=%f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">my_round</span><span class="p">(</span><span class="mf">1.5</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"result=%f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">my_round</span><span class="p">(</span><span class="mf">1.25</span><span class="p">,</span> <span class="mi">1</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"result=%f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">my_round</span><span class="p">(</span><span class="mf">1.245</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"result=%f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">my_round</span><span class="p">(</span><span class="mf">1.45</span><span class="p">,</span> <span class="mi">1</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"result=%f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">my_round</span><span class="p">(</span><span class="mf">1.415</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"result=%f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">my_round</span><span class="p">(</span><span class="mf">2.675</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
<span class="p">}</span>
</code>
两个问题,一个是<code class="language-text">expect: 1.0
actual: 1
expect: 1.1
actual: 1.100000000000000088817841970012523233890533447265625
expect: 1.2
actual: 1.1999999999999999555910790149937383830547332763671875
expect: 1.3
actual: 1.3000000000000000444089209850062616169452667236328125
expect: 1.4
actual: 1.399999999999999911182158029987476766109466552734375
expect: 1.5
actual: 1.5
expect: 1.6
actual: 1.600000000000000088817841970012523233890533447265625
expect: 1.7
actual: 1.6999999999999999555910790149937383830547332763671875
expect: 1.8
actual: 1.8000000000000000444089209850062616169452667236328125
expect: 1.9
actual: 1.899999999999999911182158029987476766109466552734375
</code>
很简单,因为2.675在表示的时候可能是2.6749,所以round以后还是2.67了
你的需求本质是:精确小数运算。然而,float不是为了满足这一需求而设计的,decimal才是。所以,为float单独定制一个round,不符合float的设计意图,也很难实现。以你的函数为例,temp*10这个操作在float下不是精确的。