Python 将浮点数限制为两位小数

我希望 a 舍入到 13.95

>>> a
13.949999999999999
>>> round(a, 2)
13.949999999999999

round 函数不 按照我的预期工作。

You are running into the old problem with floating point numbers that not all numbers can be represented exactly. The command line is just showing you the full floating point form from memory.

With floating point representation, your rounded version is the same number. Since computers are binary, they store floating point numbers as an integer and then divide it by a power of two so 13.95 will be represented in a similar fashion to 125650429603636838/(2**53).

Double precision numbers have 53 bits (16 digits) of precision and regular floats have 24 bits (8 digits) of precision. The floating point type in Python uses double precision to store the values.

For example,

>>> 125650429603636838/(2**53)
13.949999999999999

>>> 234042163/(2**24)
13.949999988079071

>>> a = 13.946
>>> print(a)
13.946
>>> print(“%.2f” % a)
13.95
>>> round(a,2)
13.949999999999999
>>> print(“%.2f” % round(a, 2))
13.95
>>> print(“{:.2f}”.format(a))
13.95
>>> print(“{:.2f}”.format(round(a, 2)))
13.95
>>> print(“{:.15f}”.format(round(a, 2)))
13.949999999999999

If you are after only two decimal places (to display a currency value, for example), then you have a couple of better choices:

  1. Use integers and store values in cents, not dollars and then divide by 100 to convert to dollars.
  2. Or use a fixed point number like decimal.

There are new format specifications, String Format Specification Mini-Language:

You can do the same as:

"{:.2f}".format(13.949999999999999)

Note 1: the above returns a string. In order to get as float, simply wrap with float(...):

float("{:.2f}".format(13.949999999999999))

Note 2: wrapping with float() doesn't change anything:

>>> x = 13.949999999999999999
>>> x
13.95
>>> g = float("{:.2f}".format(x))
>>> g
13.95
>>> x == g
True
>>> h = round(x, 2)
>>> h
13.95
>>> x == h
True
</div>

I feel that the simplest approach is to use the format() function.

For example:

a = 13.949999999999999
format(a, '.2f')

13.95

This produces a float number as a string rounded to two decimal points.

Use

print"{:.2f}".format(a)

instead of

print"{0:.2f}".format(a)

Because the latter may lead to output errors when trying to output multiple variables (see comments).

TLDR ;)

The rounding problem of input and output has been solved definitively by Python 3.1 and the fix is backported also to Python 2.7.0.

Rounded numbers can be reversibly converted between float and string back and forth:
str -> float() -> repr() -> float() ... or Decimal -> float -> str -> Decimal

>>> 0.3
0.3
>>> float(repr(0.3)) == 0.3
True

A Decimal type is not necessary for storage anymore.

Results of arithmetic operations must be rounded again because rounding errors could accumulate more inaccuracy than that is possible after parsing one number. That is not fixed by the improved repr() algorithm (Python >= 3.1, >= 2.7.0):

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1, 0.2, 0.3
(0.1, 0.2, 0.3)

The output string function str(float(...)) was rounded to 12 valid digits in Python < 2.7x and < 3.1, to prevent excessive invalid digits similar to unfixed repr() output. That was still insufficientl after subtraction of very similar numbers and it was too much rounded after other operations. Python 2.7 and 3.1 use the same length of str() although the repr() is fixed. Some old versions of Numpy had also excessive invalid digits, even with fixed Python. The current Numpy is fixed. Python versions >= 3.2 have the same results of str() and repr() function and also output of similar functions in Numpy.


Test

import random
from decimal import Decimal
for _ in range(1000000):
    x = random.random()
    assert x == float(repr(x)) == float(Decimal(repr(x)))  # Reversible repr()
    assert str(x) == repr(x)
    assert len(repr(round(x, 12))) <= 14         # no excessive decimal places.

Documentation

See the Release notes Python 2.7 - Other Language Changes the fourth paragraph:

Conversions between floating-point numbers and strings are now correctly rounded on most platforms. These conversions occur in many different places: str() on floats and complex numbers; the float and complex constructors; numeric formatting; serializing and de-serializing floats and complex numbers using the marshal, pickle and json modules; parsing of float and imaginary literals in Python code; and Decimal-to-float conversion.

Related to this, the repr() of a floating-point number x now returns a result based on the shortest decimal string that’s guaranteed to round back to x under correct rounding (with round-half-to-even rounding mode). Previously it gave a string based on rounding x to 17 decimal digits.

The related issue


More information: The formatting of float before Python 2.7 was similar to the current numpy.float64. Both types use the same 64 bit IEEE 754 double precision with 52 bit mantissa. A big difference is that np.float64.__repr__ is formatted frequently with an excessive decimal number so that no bit can be lost, but no valid IEEE 754 number exists between 13.949999999999999 and 13.950000000000001. The result is not nice and the conversion repr(float(number_as_string)) is not reversible with numpy. On the other hand: float.__repr__ is formatted so that every digit is important; the sequence is without gaps and the conversion is reversible. Simply: If you perhaps have a numpy.float64 number, convert it to normal float in order to be formatted for humans, not for numeric processors, otherwise nothing more is necessary with Python 2.7+.

You can modify the output format:

>>> a = 13.95
>>> a
13.949999999999999
>>> print "%.2f" % a
13.95
</div>

The Python tutorial has an appendix called Floating Point Arithmetic: Issues and Limitations. Read it. It explains what is happening and why Python is doing its best. It has even an example that matches yours. Let me quote a bit:

>>> 0.1
0.10000000000000001

you may be tempted to use the round() function to chop it back to the single digit you expect. But that makes no difference:

>>> round(0.1, 1)
0.10000000000000001

The problem is that the binary floating-point value stored for “0.1” was already the best possible binary approximation to 1/10, so trying to round it again can’t make it better: it was already as good as it gets.

Another consequence is that since 0.1 is not exactly 1/10, summing ten values of 0.1 may not yield exactly 1.0, either:

>>> sum = 0.0
>>> for i in range(10):
...     sum += 0.1
...
>>> sum
0.99999999999999989

One alternative and solution to your problems would be using the decimal module.