Python: 十进制加减法没有给出准确的结果

Python: Decimal addition and subtraction not giving exact result

Python (3.8) 代码:

#!/usr/bin/env python3

from decimal import Decimal
from decimal import getcontext

x = Decimal('0.6666666666666666666666666667')
y = x;
print(getcontext().prec)
print(y)
print(y == x)
y += x; y += x; y += x;
y -= x; y -= x; y -= x;
print(y)
print(y == x)

Python 输出:

28
0.6666666666666666666666666667
True
0.6666666666666666666666666663
False

Java代码:

import java.math.BigDecimal;

public class A
{
    public static void main(String[] args)
    {
        BigDecimal x = new BigDecimal("0.6666666666666666666666666667");
        BigDecimal y = new BigDecimal("0.6666666666666666666666666667");

        System.out.println(x.precision());
        System.out.println(y.precision());

        System.out.println(y);
        System.out.println(y.equals(x));

        y = y.add(x); y = y.add(x); y = y.add(x);
        y = y.subtract(x); y = y.subtract(x); y = y.subtract(x);

        System.out.println(y);
        System.out.println(y.equals(x));
    }
}

Java 输出:

28
28
0.6666666666666666666666666667
true
0.6666666666666666666666666667
true

在 Python 中实现任意精度的方法是什么?通过设置一个非常大的 prec?

来自the Decimal docs

The use of decimal floating point eliminates decimal representation error (making it possible to represent 0.1 exactly); however, some operations can still incur round-off error when non-zero digits exceed the fixed precision.

The effects of round-off error can be amplified by the addition or subtraction of nearly offsetting quantities resulting in loss of significance. Knuth provides two instructive examples where rounded floating point arithmetic with insufficient precision causes the breakdown of the associative and distributive properties of addition:

# Examples from Seminumerical Algorithms, Section 4.2.2.
>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 8

>>> u, v, w = Decimal(11111113), Decimal(-11111111), Decimal('7.51111111')
>>> (u + v) + w 
Decimal('9.5111111')
>>> u + (v + w) 
Decimal('10')

>>> u, v, w = Decimal(20000), Decimal(-6), Decimal('6.0000003')
>>> (u*v) + (u*w) 
Decimal('0.01')
>>> u * (v+w) 
Decimal('0.0060000') 

您可以根据需要将精度设置为非常大的值,如下所示:

getcontext().prec = 4000000000000 

它可以是任何整数值。请注意,您不能将此值设置为 math.inf 或任何无穷大(对于任意精度 - 如果这很重要)因为无穷大不选择 int class 这是设置精度时的检查。所以一定要设置一个你绝对不会超过的值。当然,拥有极高的精度也无妨。对于您的情况,即使标称值为 40 也可以解决问题(实际上任何 29+ 值):

from decimal import Decimal
from decimal import getcontext
getcontext().prec = 40 # here cause its enough
x = Decimal('0.6666666666666666666666666667')
y = x;
print(getcontext().prec)
print(y)
print(y == x)
y += x; y += x; y += x;
y -= x; y -= x; y -= x;
print(y)
print(y == x)

结果:

40
0.6666666666666666666666666667
True
0.6666666666666666666666666667
True

来自Python documentation

The decimal module incorporates a notion of significant places so that 1.30 + 1.20 is 2.50.

此外,还需要考虑以下几点:

The context precision does not affect how many digits are stored. That is determined exclusively by the number of digits in value. For example, Decimal('3.00000') records all five zeros even if the context precision is only three.

Context precision and rounding only come into play during arithmetic operations.

因此:

import decimal
from decimal import Decimal

decimal.getcontext().prec = 4

a = Decimal('1.22222')

#1.22222 
#what you put in is what you get even though the prec was set to 4
print(a)        

b = Decimal('0.22222')

#0.22222
#Same reasoning as above
print(b)

a += 0; b += 0

#a will be printed as 1.222 (4 significant figures)
#b will be printed as 0.2222 (Leading zeroes are not significant!)
print('\n', a, '\n', b, sep='')

您的原始值有 28 位精度,但当与自身相加时,结果 >0 并且有 29 位,但四舍五入为 28 因此它在小数点后失去一位精度。

打印中间结果的原始结果

28
0.6666666666666666666666666667   # 28 digits, leading zeros don't count
True
1.333333333333333333333333333    # note 28 digits, lost the last digit (4)
2.000000000000000000000000000
2.666666666666666666666666667
2.000000000000000000000000000
1.333333333333333333333333333
0.6666666666666666666666666663
False

设置精度为29,正确:

29
0.6666666666666666666666666667
True
1.3333333333333333333333333334
2.0000000000000000000000000001
2.6666666666666666666666666668
2.0000000000000000000000000001
1.3333333333333333333333333334
0.6666666666666666666666666667
True