Programing

왜 파이썬 3에서 부동 소수점 값 4 * 0.1이 멋지게 보이지만 3 * 0.1은 그렇지 않습니까?

lottogame 2020. 6. 9. 07:40
반응형

왜 파이썬 3에서 부동 소수점 값 4 * 0.1이 멋지게 보이지만 3 * 0.1은 그렇지 않습니까?


대부분의 소수에는 정확한 부동 소수점 표현이 없습니다 ( 부동 소수점 수학은 깨졌습니까? ).

그러나 나는 이유를 볼 수 없습니다 4*0.1잘으로 인쇄되어 0.4있지만, 3*0.1두 값이 실제로 못생긴 진수 표현을하지 않을 때입니다 :

>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')

간단한 대답은 3*0.1 != 0.3양자화 (반올림) 오류 4*0.1 == 0.4때문입니다 (2의 거듭 제곱은 일반적으로 "정확한"연산이기 때문에).

.hexPython 메소드를 사용하여 숫자의 내부 표현 (기본적으로 10 진 근사가 아닌 정확한 이진 부동 소수점 값)을 볼 수 있습니다. 이것은 후드 아래에서 일어나는 일을 설명하는 데 도움이 될 수 있습니다.

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'

0.1은 0x1.999999999999a 곱하기 2 ^ -4입니다. 끝에있는 "a"는 숫자 10을 의미합니다. 즉, 이진 부동 소수점 의 0.1은 "정확한"값 0.1보다 매우 약간 큽니다 (최종 0x0.99는 0x0.a로 반올림되므로). 이 값에 2의 거듭 제곱 인 4를 곱하면 지수가 2 ^ -4에서 2 ^ -2로 올라가지 만 숫자는 변경되지 않습니다 4*0.1 == 0.4.

그러나 3을 곱하면 0x0.99와 0x0.a0 (0x0.07) 사이의 작은 차이가 0x0.15 오류로 확대되어 마지막 위치에서 한 자리 오류로 표시됩니다. 이로 인해 0.1 * 3 은 반올림 된 값 0.3보다 매우 약간 커집니다.

Python 3의 float repr왕복 가능 하도록 설계되었습니다 . 즉, 표시된 값은 원래 값으로 정확하게 변환 가능해야합니다. 따라서 표시 할 수 없습니다 0.30.1*3동일한 방식, 또는 두 개의 서로 다른 숫자는 라운드 트립 후 같은 끝낼 것입니다. 결과적으로 Python 3의 repr엔진은 약간의 명백한 오류가있는 엔진을 표시하도록 선택합니다.


repr(및 strPython 3에서는) 값을 모호하지 않게 만드는 데 필요한 수만큼 자릿수를 표시합니다. 이 경우 곱셈의 결과는 3*0.10.3에 가장 가까운 값 (16 진수의 0x1.3333333333333p-2)이 아니며 실제로 LSB가 1보다 높으므로 (0x1.3333333333334p-2) 0.3과 구별하기 위해 더 많은 숫자가 필요합니다.

반면에 곱셈 4*0.1 0.4에 가장 가까운 값 (16 진수로 0x1.999999999999ap-2)을 얻으므로 추가 숫자가 필요하지 않습니다.

이것을 쉽게 확인할 수 있습니다.

>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True

위의 16 진수 표기법은 훌륭하고 컴팩트하며 두 값의 비트 차이를 보여주기 때문에 위의 16 진수 표기법을 사용했습니다. 예를 들어이를 사용하여 직접 할 수 있습니다 (3*0.1).hex(). 십진 영광으로 그들을보고 싶다면 여기로 가십시오.

>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')

다른 답변의 간단한 결론은 다음과 같습니다.

파이썬의 커맨드 라인에서 float를 확인하거나 인쇄하면 repr문자열 표현을 생성하는 함수 거치게됩니다 .

버전 3.2을 시작으로, 파이썬 str과는 repr잘 생긴 소수 가능하다면하지만, 수레와 자신의 문자열 표현 사이의 보증 전단 사 (일대일) 매핑이 필요 더 용도 자리를 선호 복잡한 반올림 방식을 사용합니다.

이 체계 repr(float(s))는 부동 소수점으로 정확하게 표현할 수없는 경우에도 (예 : when) 단순 10 진수 모양이 멋지도록합니다 s = "0.1").

동시에 float(repr(x)) == x모든 플로트에 대해 보장합니다.x


실제로는 파이썬의 구현에만 국한된 것이 아니라 float to decimal 문자열 함수에 적용해야합니다.

A floating point number is essentially a binary number, but in scientific notation with a fixed limit of significant figures.

The inverse of any number that has a prime number factor that is not shared with the base will always result in a recurring dot point representation. For example 1/7 has a prime factor, 7, that is not shared with 10, and therefore has a recurring decimal representation, and the same is true for 1/10 with prime factors 2 and 5, the latter not being shared with 2; this means that 0.1 cannot be exactly represented by a finite number of bits after the dot point.

Since 0.1 has no exact representation, a function that converts the approximation to a decimal point string will usually try to approximate certain values so that they don't get unintuitive results like 0.1000000000004121.

Since the floating point is in scientific notation, any multiplication by a power of the base only affects the exponent part of the number. For example 1.231e+2 * 100 = 1.231e+4 for decimal notation, and likewise, 1.00101010e11 * 100 = 1.00101010e101 in binary notation. If I multiply by a non-power of the base, the significant digits will also be affected. For example 1.2e1 * 3 = 3.6e1

Depending on the algorithm used, it may try to guess common decimals based on the significant figures only. Both 0.1 and 0.4 have the same significant figures in binary, because their floats are essentially truncations of (8/5)(2^-4) and (8/5)(2^-6) respectively. If the algorithm identifies the 8/5 sigfig pattern as the decimal 1.6, then it will work on 0.1, 0.2, 0.4, 0.8, etc. It may also have magic sigfig patterns for other combinations, such as the float 3 divided by float 10 and other magic patterns statistically likely to be formed by division by 10.

In the case of 3*0.1, the last few significant figures will likely be different from dividing a float 3 by float 10, causing the algorithm to fail to recognize the magic number for the 0.3 constant depending on its tolerance for precision loss.

Edit: https://docs.python.org/3.1/tutorial/floatingpoint.html

Interestingly, there are many different decimal numbers that share the same nearest approximate binary fraction. For example, the numbers 0.1 and 0.10000000000000001 and 0.1000000000000000055511151231257827021181583404541015625 are all approximated by 3602879701896397 / 2 ** 55. Since all of these decimal values share the same approximation, any one of them could be displayed while still preserving the invariant eval(repr(x)) == x.

There is no tolerance for precision loss, if float x (0.3) is not exactly equal to float y (0.1*3), then repr(x) is not exactly equal to repr(y).

참고URL : https://stackoverflow.com/questions/39618943/why-does-the-floating-point-value-of-40-1-look-nice-in-python-3-but-30-1-doesn

반응형