[python] 왜 파이썬 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 문자열 함수에 적용해야합니다.

부동 소수점 숫자는 기본적으로 이진수이지만 과학적 표기법에서는 유효 숫자의 제한이 고정되어 있습니다.

밑수와 공유되지 않는 소수 요소를 가진 숫자의 역수는 항상 반복되는 점 점 표현으로 나타납니다. 예를 들어 1/7에는 10과 공유되지 않는 소수 인 7이 있으며 반복되는 10 진수 표현이 있으며, 1/10에 대해 소수 인 2와 5의 경우도 마찬가지이며 후자는 2와 공유되지 않습니다. ; 즉, 0.1은 도트 포인트 뒤에 유한 한 비트 수로 정확하게 표현 될 수 없습니다.

0.1에는 정확한 표현이 없으므로 근사값을 소수점 문자열로 변환하는 함수는 일반적으로 특정 값을 근사하여 0.1000000000004121과 같은 직관적이지 않은 결과를 얻지 않습니다.

부동 소수점은 과학적 표기법이므로 밑의 거듭 제곱에 의한 곱셈은 숫자의 지수 부분에만 영향을 미칩니다. 예를 들어, 십진 표기법의 경우 1.231e + 2 * 100 = 1.231e + 4이고, 이진 표기법의 경우 1.00101010e11 * 100 = 1.00101010e101입니다. 밑이 아닌 거듭 제곱을 곱하면 유효 숫자도 영향을받습니다. 예를 들어 1.2e1 * 3 = 3.6e1

사용 된 알고리즘에 따라 유효 숫자 만 기반으로 공통 소수를 추측하려고 할 수 있습니다. 0.1과 0.4는 모두 부동 소수가 본질적으로 각각 (8/5) (2 ^ -4)와 (8/5) (2 ^ -6)의 잘림 때문에 이진법에서 유효 숫자가 같습니다 . 알고리즘이 8/5 sigfig 패턴을 소수점 1.6으로 식별하면 0.1, 0.2, 0.4, 0.8 등에서 작동합니다. float 3을 float 10으로 나눈 것과 같은 다른 조합에 대한 매직 sigfig 패턴을 가질 수도 있습니다. 통계적으로 10으로 나눈 다른 매직 패턴.

3 * 0.1의 경우, 마지막 몇 개의 중요한 수치는 float 3을 float 10으로 나누는 것과 다를 수 있으므로 알고리즘은 정밀도 손실에 대한 허용 오차에 따라 0.3 상수의 매직 넘버를 인식하지 못합니다.

편집 :
https://docs.python.org/3.1/tutorial/floatingpoint.html

흥미롭게도, 가장 가까운 대략적인 이진 분수를 공유하는 많은 다른 10 진수가 있습니다. 예를 들어, 0.1 및 0.10000000000000001 및 0.1000000000000000055511151231257827021181583404541015625는 모두 3602879701896397/2 ** 55로 근사됩니다.이 10 진수 값은 모두 동일한 근사값을 공유하므로 불변 eval (repr (x) ) == x.

float x (0.3)가 float y (0.1 * 3)와 정확히 같지 않으면 repr (x)가 repr (y)와 정확히 같지 않으면 정밀 손실에 대한 허용 오차가 없습니다.


답변