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


158

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

그러나 나는 이유를 볼 수 없습니다 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')

7
어떤 숫자는 정확하게 표현 될 수 있고 어떤 숫자는 표현할 수 없기 때문입니다.
Morgan Thrapp

58
@MorganThrapp : 아닙니다. OP는 다소 임의적 인 형식의 선택을 요구하고 있습니다. 0.3 또는 0.4는 이진 부동 소수점으로 정확하게 표현 될 수 없습니다.
Bathsheba

42
@BartoszKP는 : 그것은 파이썬이 표시되는 이유를 설명하지 않는 문서를 여러 번 읽는 데 0.30000000000000004440892098500626161694526672363281250.300000000000000040.40000000000000002220446049250313080847263336181640625같은 .4질문에 대답하지 않는, 따라서 그들은 같은 정확성을 가지고있는 것 같습니다에도 불구하고.
Mooing Duck

6
stackoverflow.com/questions/28935257/… 참조 -복제본으로 닫히는 것에 약간 짜증이 나지만 아직 그렇지 않습니다.
Random832

12
다시 열었 습니다 . "부동 소수점 수학이 깨졌습니다"의 복제본으로 닫지 마십시오 .
Antti Haapala

답변:


301

간단한 대답은 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엔진은 약간 명백한 오류가있는 엔진을 표시하도록 선택합니다.


25
이것은 놀랍도록 포괄적 인 답변입니다. 감사합니다. (특히, 보여 주셔서 감사합니다 .hex(); 그것이 존재하는지 몰랐습니다.)
NPE

21
@ supercat : 파이썬 은 원하는 값으로 반올림 하는 가장 짧은 문자열 을 찾으려고 시도 합니다. 분명히 평가 된 값은 0.5ulp 내에 있어야하거나 다른 것으로 반올림해야하지만 모호한 경우 더 많은 숫자가 필요할 수 있습니다. 코드는 매우 나쁘지만
nneonneo

2
@supercat : 항상 0.5ulp 이내의 가장 짧은 문자열. ( 홀수 LSB가있는 플로트를 보는 경우 엄밀히 말하면 , 즉 라운드-투-짝수에서 작동하는 가장 짧은 문자열). 이에 대한 예외는 버그이므로보고해야합니다.
Mark Dickinson

7
@MarkRansom 분명히 그들은 e이미 16 진수이기 때문에 다른 것을 사용했습니다 . 아마 지수 대신 권력p위해 .
Bergi

11
@Bergi : p이 문맥에서 의 사용은 (적어도) C99로 돌아가며 IEEE 754 및 다양한 다른 언어 (Java 포함)로도 나타납니다. 때 float.hexfloat.fromhex구현 된 (내게로 :-), 파이썬은 단지 다음 설정 연습에 의해 무엇 복사했다. 나는 의도가 "파워"에 대한 'p'인지 여부를 모르겠지만 그것에 대해 생각하는 좋은 방법 인 것 같습니다.
Mark Dickinson

75

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')

2
(+1) 좋은 답변입니다. 감사합니다. 3*0.1 == 0.3및 의 결과를 포함하여 "가장 가까운 값이 아님"을 설명 할 가치가 있다고 생각하십니까 4*0.1 == 0.4?
NPE

@ NPE 나는 제안을 주셔서 감사합니다.
Mark Ransom

많은 사람들이 부동 소수점 16 진수를 읽을 수 없기 때문에 가장 가까운 "doubles"의 정확한 십진수 값을 0.1, 0.3 및 0.4에 주목할 가치가 있는지 궁금합니다.
supercat

@ supercat 당신은 좋은 지적을합니다. 그 슈퍼 큰 복식을 텍스트에 넣으면 산만해질 것입니다. 그러나 그것들을 추가하는 방법을 생각했습니다.
Mark Ransom

25

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

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

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

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

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


2
당신의 대답은 3.2, 파이썬 버전에 대한 정확> = strrepr수레에 대해 동일합니다. Python 2.7의 경우 repr식별 할 수있는 속성이 있지만 str훨씬 더 간단합니다. 단순히 12 자리의 유효 숫자를 계산하고이를 기반으로 출력 문자열을 생성합니다. 파이썬 <= 2.6, 모두 reprstr자리수 고정 된 수 (17에 기초 repr대 12 str). (그리고 아무도 파이썬 3.0이나 파이썬 3.1에 관심이 없다 :)
Mark Dickinson

감사합니다 @MarkDickinson! 답변에 귀하의 의견을 포함 시켰습니다.
Aivar

2
쉘의 반올림 repr은 파이썬 2.7의 동작과 동일합니다.
Antti Haapala

5

실제로는 파이썬의 구현에만 국한된 것이 아니라 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)와 정확히 같지 않으면 정밀 손실에 대한 허용 오차가 없습니다.


4
이것은 실제로 기존 답변에 많은 것을 추가하지는 않습니다.
Antti Haapala

1
"사용 된 알고리즘에 따라 중요한 수치만을 기준으로 공통 소수를 추측 할 수 있습니다." <-이것은 순수한 추측처럼 보입니다. 다른 답변은 파이썬이 실제로 하는 일을 설명했습니다 .
Mark Dickinson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.