SoFunction
Updated on 2024-11-17

Python floating-point number rounding problem analysis and solutions

concern

I ran into a problem yesterday with a calculation using the round() function when 6.6045 retains three decimals, and we wanted to get 6.605, however:

>>> round(6.6045, 3)
6.604

Some people on the Internet say that because decimals are imprecise in computers, for example, 1.115 is actually 1.1149999999999999999991182 in computers, so when you are accurate to two decimal places on this decimal, the third decimal place is actually 4, so rounding up results in 1.11.

That statement, is half right.

Because not all decimals are imprecise in computers. For example, the decimal 0.125 is exact in a computer, it is 0.125, there is no omission of the value after it, there is no approximation, it is indeed 0.125.

But if we run it in Python:

>>> round(0.125, 2)
0.12

Why are we rounding up here?

And even stranger, another decimal that can be represented exactly inside a computer 0.375, let's see what it is to two decimal places:

>>> round(0.375, 2)
0.38

Why are you five in again here?

analyze

That's because inside Python3, round uses rounding five to two for decimal precision.

If you've ever written a lab report on college physics, then you'll remember your teacher talking about how using rounding directly can end up with results that are on the high side, so you need to use the odd-in-even-out treatment.

For example, for a floating point number , which needs to be accurate to two decimal places, then look at the third decimal place:

  • If d is less than 5, it is simply discarded.
  • if d is greater than 5, direct rounding.
  • If d equals 5:
    d is not followed by data and c is even, then no rounding is done and c is retained.
    There is no data after d and c is odd, then round up and c becomes (c + 1)
  • If d is followed by a non-zero number, e.g., if it is actually a decimal number, then it must be rounded up, and c becomes (c + 1).

For those interested in odd-in-even rounding, search Wikipedia for these two terms: numerical modification and odd-in-even rounding.

So, if round gives a different result than envisioned, then there are two reasons to consider:
Can this decimal number of yours be stored accurately in a computer? If it can't, then it probably doesn't meet the criteria for rounding, e.g. 1.115, which actually has 4 as the third decimal place, will of course be rounded off.
If you have a decimal that can be represented exactly in a computer, round uses a rounding mechanism that rounds odd to even, so it depends on the bit you want to keep, whether it's odd or even, and whether there's any data after its next bit.

Regarding odd-in-even rounding, those interested can search for these two terms: numerical modification and odd-in-even rounding.

Going back to the original question, for the floating point number 6.6045, let's look at its exact form in Scheme:

> (exact 6.6045)
3718002967371055/562949953421312

That means it can't be stored exactly, and probably behaves in the form of 6.6044999999999999..., so rounding gives 6.604.

How to Round Correctly

If you want to do mathematical rounding, then you need to use the decimal module, as described in the official documentation:/zh-cn...。
The quantize function prototype and documentation mention that the rounding method can be determined by specifying the rounding parameter. If the rounding parameter is not specified, then the rounding method provided by the context is used by default.

Now let's see what the feed is in the default context:

>>> from decimal import getcontext
>>> getcontext().rounding
'ROUND_HALF_EVEN'

ROUND_HALF_EVEN is actually odd rounding, if we want to specify true rounding then we need to specify the rounding method as ROUND_HALF_UP in quantize:

>>> from decimal import Decimal, ROUND_HALF_UP
>>> Decimal('0.125').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')

Everything looks normal now.

One might pursue this further by asking what happens if Decimal receives a floating-point number as a parameter instead of a string.
to experiment with it:

>>> Decimal(0.125)
Decimal('0.125')

Does that mean that you can pass a floating point number as the first argument to Decimal?

Let's change the number and test it:

>>> Decimal(11.245)
Decimal('11.2449999999999992184029906638897955417633056640625')

The floating point number 11.245 is not the same as the string '11.245'.

We continue to look for answers in the documentation:


The official documentation makes it clear that if you pass in a floating-point number that can't be stored exactly on the computer, it will first be converted to an inexact binary value, and then the inexact binary value will be converted to its decimal equivalent. For decimals that can't be represented exactly, when you pass them in, Python converts the number to an inexact number before it gets to it. So although the argument is 11.245, what Python gets is actually 11.2449999999999...

summarize

The above is the entire content of this article, I hope that the content of this article for your study or work has a certain reference learning value, thank you for your support.