파이썬에서 소수점 계산시 0과의 비교가 제대로 안되는 문제
글쓴이: 나빌레라 / 작성시간: 화, 2012/04/24 - 4:40오후
혼자서 아무리 고민해봐도 답이 안나와서 질문 드립니다.
def zeroTest(p, n): while True: p -= n print p if p == 0: break if p < 0 : break
코드는 아주 단순합니다.
이 코드를 실행해보겠습니다.
파이썬 버전은 2.7.2입니다. 그런데 2.6.x에서도 비슷하게 이상한 결과가 나오더라구요.
>>> zeroTest(1, 0.05) 0.95 0.9 0.85 0.8 0.75 0.7 0.65 0.6 0.55 0.5 0.45 0.4 0.35 0.3 0.25 0.2 0.15 0.1 0.05 -3.1918911958e-16
문제는 가장 아래 두 줄입니다.
p가 0.05인 상태에서 n인 0.05를 또 빼면 0.0이 나와야 하는데 뭐 이상한 값이 나옵니다. 아마도 float 최대값이 나오는 것 처럼 보입니다.
저는 이 문제가 원인이 무엇인지 정말 모르겠습니다.
이런 저런 꼼수(소수점 숫자에 일괄적으로 10000 정도를 곱해서 정수로 만든다든지)를 써서 당장의 문제를 해결하긴 했지만 위의 현상 자체는 이해가 안됩니다.
그래서 혹시나 싶어 아래와 같은 간단한 계산을 파이썬 shell 상에서 해봤습니다.
>>> a = 0.05 >>> b = 0.05 >>> c = a-b >>> c 0.0
흠.. 근데 이번엔 0.0이 나오네요...
혹시 이 현상에 대한 원인을 아시는 분 계시면 가르침 부탁드립니다.
Forums:
gilgil.net
10진수로 간단하게 표기할 수 있는 실수가 2진수에서는 정확히 표현되지 않을 수가 있습니다.
http://www.gilgil.net/11340
www.gilgil.net
부동수숫점 문서가 도움이 될 것 같습니다.
알고 있는 내용인데, 가끔 잊어버리고 코딩하는 경우가 있는것 같습니다.
http://docs.python.org/tutorial/floatingpoint.html
예제를 첨부 합니다.
부가적인 설명할 실력이 없어서 예제만 올립니다. -.-;;
--덧붙이는 글--
글 수정기능을 못찾아서 다시 올립니다.
"아마도 float 최대값이 나오는 것 처럼
"아마도 float 최대값이 나오는 것 처럼 보입니다."
이부분을 보니 잠깐 잘못보신거 같은데,
-3.1918911958e-16 면 -0.000000000000000319죠. 2진 실수과 10진 실수의 변환 과정중에 납득할만한 오차인것 같습니다.
실수 연산...
정수 연산에서는 동등비교(==)가 말이 되지만 실수 연산에서는 의미가 없습니다.
즉, "어제 회의 참석인원과 오늘 회의 참석 인원이 같느냐?" 하고 물었을때 어제 10명 오늘 11명이면 false,
어제 10명, 오늘 11명이면 true 겠지만
"왼쪽 컵의 물의 양과 오른쪽 컵의 물의 양이 같느냐?" 하고 물었을 때, 왼쪽 컵에는 34.34928mL가 있고 오른쪽 컵에는 34.34927mL가 있을 때 어떻게 대답해야 되는가의 문제 입니다. 즉 이럴 경우 정확하게 같다 다르다는 큰 의미가 없고, 두 값이 얼마만큼의 오차 범위내에 있는 냐는 것이 의미가 있을 것입니다.
위에 분이 말씀한 것처럼 맨 아래 값은 -3.19... * 10**(-16) 으로 0에 거의 근접한 값입니다.
즉 실수 a와 b를 비교하기 위해서는 "a == b"를 사용해야 되는 것이 아니라 오차범위 e 내에서 같은지 판단하기 위해서는 "abs(a-b) < e"를 사용해야 더 타당합니다.
찾아보니까, 어느 정도 납득 가능한 부분을 찾을 수
찾아보니까, 어느 정도 납득 가능한 부분을 찾을 수 있었네요.
첫번째 줄은 0.05를 3번 더한 후 hex 값을 나타낸 표현입니다. 두번째 줄은 0.15 자체를 hex 값으로 표현한 것입니다.
실제 수학에서는 같은 값이지만 hex로 표현하니 다른 값을 표현하네요. 그리고 세번째 줄은 hex 값을 직접 뺀 후 다시 float 표현으로 변경해본 것 입니다.
즉, 오차가 생김을 알 수 있습니다. 그리고 파이썬에서 지원하는 정확한 소수 계산 모듈인 Decimal을 사용해서 빼보면 그 값이 같음을 알 수 있습니다.
그런데 0.1 - 0.05 - 0.05의 경우 정확히 0과 같은데, hex 값으로 표현해보면 0.1의 hex와 0.05 + 0.05의 hex 값이 같습니다. 그래서 정확한 계산이 되는 것처럼 보입니다.
따라서 0.05 - 0.05 도 같은 hex 값을 없애는 것이므로 당연히 0이 나온다고 볼 수 있겠습니다.
그리고 만일 정확한 소수 계산을 한다면 아래와 같이 사용하면 0 과 같게 제대로 나옵니다.
덕분에 재미 있는 부분을 알게 되었네요. 감사합니다.
/*** Signature ******************
* blog: http://blog.bluekyu.me/ *
********************************/
약간 다른 코드가 들어갔네요... >>> 0.15 -
약간 다른 코드가 들어갔네요...
이 부분도 포함되어야 할 것 같습니다... 정말 신기하긴 하네요...
/*** Signature ******************
* blog: http://blog.bluekyu.me/ *
********************************/
정확하게 설명하셨구요, 좀더 간단하게 설명하자면,
정확하게 설명하셨구요, 좀더 간단하게 설명하자면, "오차가 누적되어서 그렇다" 입니다.
파이썬 쉘에서도 1.0에서 0.05를 20번 빼면 똑같이 오차가 누적되어서 같은 값이 나올겁니다.
유효숫자 관련해서 파이선뿐만 아니로 C 계열에서도
유효숫자 관련해서 파이선뿐만 아니로 C 계열에서도 비슷한 문제가 발생할 수 있습니다. 때문에
if(result > 0.0)
보다는
if(result > 0.0 - margin_of_error)
로 써야 하는 경우가 있죠.
정수 연산을 활용해서 누적 오차를 줄여보세요.
정수 연산을 활용해서 누적 오차를 줄여보세요.
저같은 경우는
코딩시 저렇게 오차가 누적될만한 코드에는 연산량이 조금 많아지더라도
1 - (0.05 * n) 으로 계산하는 것을 선호합니다.
이러면 기준값에서 N을 곱한 후 뺄셈을 하기때문에 오차가 누적되는게 줄더군요.
.
def zeroTest2(p,n):
i = 0;
while True:
print p - n * i
if (p - n *i ) == 0:
break
if (p - n * i ) < 0:
break
i = i + 1
이렇게요.
댓글 달기