파이썬에서 소수점 계산시 0과의 비교가 제대로 안되는 문제

나빌레라의 이미지

혼자서 아무리 고민해봐도 답이 안나와서 질문 드립니다.

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이 나오네요...

혹시 이 현상에 대한 원인을 아시는 분 계시면 가르침 부탁드립니다.

gilgil의 이미지

10진수로 간단하게 표기할 수 있는 실수가 2진수에서는 정확히 표현되지 않을 수가 있습니다.

http://www.gilgil.net/11340

lovewar의 이미지

알고 있는 내용인데, 가끔 잊어버리고 코딩하는 경우가 있는것 같습니다.

http://docs.python.org/tutorial/floatingpoint.html

lovewar의 이미지

부가적인 설명할 실력이 없어서 예제만 올립니다. -.-;;

>>> b = 1 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 - 0.05 
>>> b
-3.191891195797325e-16

--덧붙이는 글--
글 수정기능을 못찾아서 다시 올립니다.

planetarium의 이미지

"아마도 float 최대값이 나오는 것 처럼 보입니다."
이부분을 보니 잠깐 잘못보신거 같은데,
-3.1918911958e-16 면 -0.000000000000000319죠. 2진 실수과 10진 실수의 변환 과정중에 납득할만한 오차인것 같습니다.

prolinko의 이미지


정수 연산에서는 동등비교(==)가 말이 되지만 실수 연산에서는 의미가 없습니다.

즉, "어제 회의 참석인원과 오늘 회의 참석 인원이 같느냐?" 하고 물었을때 어제 10명 오늘 11명이면 false,
어제 10명, 오늘 11명이면 true 겠지만

"왼쪽 컵의 물의 양과 오른쪽 컵의 물의 양이 같느냐?" 하고 물었을 때, 왼쪽 컵에는 34.34928mL가 있고 오른쪽 컵에는 34.34927mL가 있을 때 어떻게 대답해야 되는가의 문제 입니다. 즉 이럴 경우 정확하게 같다 다르다는 큰 의미가 없고, 두 값이 얼마만큼의 오차 범위내에 있는 냐는 것이 의미가 있을 것입니다.

위에 분이 말씀한 것처럼 맨 아래 값은 -3.19... * 10**(-16) 으로 0에 거의 근접한 값입니다.

즉 실수 a와 b를 비교하기 위해서는 "a == b"를 사용해야 되는 것이 아니라 오차범위 e 내에서 같은지 판단하기 위해서는 "abs(a-b) < e"를 사용해야 더 타당합니다.

bluekyu의 이미지

찾아보니까, 어느 정도 납득 가능한 부분을 찾을 수 있었네요.

>>> (0.05 + 0.05 + 0.05).hex()
'0x1.3333333333334p-3'
>>> (0.15).hex()
'0x1.3333333333333p-3'
>>> float.fromhex('0x0.0000000000001p-3')
2.7755575615628914e-17
>>> Decimal(0.15) - Decimal(0.05) - Decimal(0.05) - Decimal(0.05)
Decimal('-1.387778780781289135105907917E-17')
>>> Decimal(0.15) - Decimal(0.05 + 0.05 + 0.05)
Decimal('-2.775557561562891351059079170E-17')

첫번째 줄은 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 과 같게 제대로 나옵니다.

zeroTest(Decimal(1), Decimal(5)/Decimal(100))

덕분에 재미 있는 부분을 알게 되었네요. 감사합니다.

/*** Signature ******************
* blog: http://blog.bluekyu.me/ *
********************************/

bluekyu의 이미지

약간 다른 코드가 들어갔네요...

>>> 0.15 - 0.05 - 0.05 - 0.05
-1.3877787807814457e-17
>>> 0.15 - (0.05 + 0.05 + 0.05)
-2.7755575615628914e-17

이 부분도 포함되어야 할 것 같습니다... 정말 신기하긴 하네요...

/*** Signature ******************
* blog: http://blog.bluekyu.me/ *
********************************/

익명 사용자의 이미지

정확하게 설명하셨구요, 좀더 간단하게 설명하자면, "오차가 누적되어서 그렇다" 입니다.
파이썬 쉘에서도 1.0에서 0.05를 20번 빼면 똑같이 오차가 누적되어서 같은 값이 나올겁니다.

slee0303의 이미지

유효숫자 관련해서 파이선뿐만 아니로 C 계열에서도 비슷한 문제가 발생할 수 있습니다. 때문에

if(result > 0.0)

보다는

if(result > 0.0 - margin_of_error)

로 써야 하는 경우가 있죠.

kwchun의 이미지

정수 연산을 활용해서 누적 오차를 줄여보세요.

def zeroTest(p, n):
    count = 0
    while True:
        count += 1
        zero = p - count * n
        print zero
        if zero == 0 or zero < 0:
            break
helpbygrace의 이미지

코딩시 저렇게 오차가 누적될만한 코드에는 연산량이 조금 많아지더라도

1 - (0.05 * n) 으로 계산하는 것을 선호합니다.
이러면 기준값에서 N을 곱한 후 뺄셈을 하기때문에 오차가 누적되는게 줄더군요.

helpbygrace의 이미지

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

이렇게요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.