실수형 표현시 접미사 f때문에 발생하는 심각한 문제에 대해

dltkddyd의 이미지

실수형을 서로 비교하는 프로그램을 gcc로 컴파일 하다보면 이런 문제가 생깁니다.

double a=45.4;
double b=45.4f;

45.4f와 45.4를 비교연산할 때 같은 것으로 컴파일이 인식하도록 만들려고 별도의 클래스를 만들었습니다. 제가 만든 클래스는 45.4f와 45.4를 == 연산자로 비교하면 참을 반환하도록 만들었습니다. 그런데 문제는 소수점 및으로 길어지면 비교가 제대로 안되는 문제가 발생합니다. 원인을 찾아보니 45.4f를 double로 캐스팅 할 때에는 비트구조상 45.4f의 마지막 32번째 비트에서 그 아래 비트가 반올림되어 표현되기 때문에 정확한 비교가 안되더군요. 차라리 45.4f를 double로 캐스팅해도 마지막 비트가 반올림 되지 않는다면, 사용자가 알아서 클래스를 만들어 비교연산자 ==를 정의할 때, 45.400001과 45.4f를 == 비교시 false가 반환되로록 할 수 있는데, 지금 상태의 gcc가 만들어내는 비트구조로는 그게 안되는 것 같습니다.

45,4f를 double로 캐스팅 할 때 32번째 비트가 반올림되지 않도록 하는 방법이 없나요?

jick의 이미지

실수형은 거의 모든 10진수 소수를 정확히 표현 못하기 때문에 그런 용도로 실수형을 쓰면 반드시 빵꾸가 날 수밖에 없습니다. 비트를 어찌어찌 바꿔서 막는다고 해도 그 순간만 땜빵으로 넘어갈 뿐이죠.

실수끼리 같은지 여부를 비교해야 하는 프로그램 설계에 근본적으로 문제가 있다고 봅니다.

dltkddyd의 이미지

표현시 마지막 비트가 반올림만 되지 않는다면 f와 그냥 실수를 비교할 수 있습니다. 충분히 비교할 수 있는 방법은 있는데, 단지 반올림되는 문제 때문에 이런 문제가 발생합니다.

좀더 많은 사용자에게 자유를

이것이 GNU의 정신이 아닌가요?

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

그게 싫으시다면 GNU libc나 gcc 등의 코드를 직접 수정하셔서 원하는 동작을 구현하시면 됩니다. 그게 진짜 "GNU 정신"입니다. 아, 이러한 코드를 수정하셔서 다른 프로그램을 깨트리는 것은 "책임지지 않습니다."

진짜 심각한 문제라고 생각하신다면 http://floating-point-gui.de/ 사이트에 있는 글도 읽어 보세요.

jick의 이미지

비교는 언제든지 할 수 있죠. 그 비교가 원하시는 결과를 내놓을 건지는 또 다른 문제입니다.

* floating point로 소수를 표현한 다음에, 덧셈/뺄셈/곱셈을 하면 그 담엔 어쩌실라구요?
* 혹시라도 사칙연산을 안할 거면 애당초 왜 floating point를 쓰시는 건데요?

다른 사람들이 전부 "그 방법은 좋은 방법이 아닐텐데요."라고 말하는 데에는 그만한 이유가 있습니다.

* "좀 더 많은 사용자에게 자유를"이라는 건 사용자가 남들이 하지말라는 엉뚱한 짓을 했을 때 손을 맞잡고 해결책을 같이 모색해 준다는 뜻이 아닙니다.

dltkddyd의 이미지

* 실수의 비교를 언제든지 할 수 있다.
천만에 말씀입니다. 물론 비교연산자는 얼마든지 사용할 수 있지만 비교는 제대로 안됩니다.
x.4f와 x.4는 컴퓨터의 비트구조에서는 다르지만 사람에게는 그러한 비트구조가 의미가 있을까요. 서로 다른 구조는 사람과 기계가 사용하는 진법의 차이에서 발생하는 오차에 불과합니다. 비교해서 결과를 내놓을때는, 사람이 이해할 수 있는 방식으로 결과물을 내놔야죠. 사람이 기계를 따라가야 합니까? 기계가 사람을 따라오게 만들어야지.

* 사칙연산을 안할 거면 애당초 왜 floating point를 쓰시는 건데요?
사칙연산 구현하는 것을 질문드린 게 아닙니다. 비교연산에 대한 질문이었죠.

* 엉뚱한 짓?
현재의 gcc가 내놓는 비교연산의 결과가 과연 납득됩니까? f라는 접미사는 단지 기계를 위해 존재하는 것이지 사람을 위한 게 아니라니까요? 기계를 이해시키기 위해 f를 만들었으면, 기계도 사람을 위해 이해할 수 있는 결과물을 내놓는 게 당연한 도리지.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

snowall의 이미지

화내지 마시구요...

gcc구현에 관한 문제니까 그게 맘에 안드신다면 gcc코드를 고치시거나, gcc개발팀에 얘기해서 고치도록 하셔야겠죠. 그것도 아니면 컴파일러를 새로 만드셔야 합니다. 왜냐하면, 다른 사람들은 현재의 gcc가 내놓는 비교연산의 결과를 대충 다 납득한 것 같거든요... 저도 굳이 그런식의 비교를 해야 하는 경우에는 등식이 아니라 부등식을 씁니다.

f가 있는 경우와 없는 경우의 차이가 항상 같다면, 또는 미리 알아낼 수 있다면, 그 차이만큼에 해당하는 오차를 미리 계산해서 =연산자 구현에 반영하시는 정도가 최선인 것 같네요.

피할 수 있을때 즐겨라! http://melotopia.net/b

ifree의 이미지

실수의 정확도와 관련된 문제는 저것 뿐이 아니고, 예를 들어 같은 연산이라도 먼저 곱하고 나누었는지, 나눗셈을 먼저 하고 곱하였는지에 따라서도 결과가 달라질 수 있습니다.
말씀하신 문제만 해결한다고 이런 문제들이 해결되질 않아요.

그래서 현실은 그대로 받아 들이고 이들의 해석을 문제에 맞게 하는 것이 좋을 것입니다.
연산의 결과로 나온 실수들을 그대로 비교하는 것은 위험하고, 마진을 정해서 그 범위에 있으면 같은 것으로 간주한다던가 하는 방법이 있습니다.

익명 사용자의 이미지

The fact that some geniuses were laughed at does not imply that all who are laughed at are geniuses. They laughed at Columbus, they laughed at Fulton, they laughed at the Wright brothers. But they also laughed at Bozo the Clown. - Carl Sagan

몇몇 천재들이 사람들에게 비웃음을 당했다고 해서, 사람들에게 비웃음을 당하는 사람이 다 천재라는 건 아니다. 사람들은 콜롬부스 보고 웃었고, 풀턴을 보고 웃었으며, 라이트 형제를 보고도 웃었다. 하지만 사람들은 어릿광대 보조를 보고도 웃었다.

jick의 이미지

왜 로그인이 풀렸지...;;

dltkddyd의 이미지

바보는 남의 말을 경청하기 전에 자기 말을 먼저 늘어놓기 좋아한다. 남의 말을 자기 멋대로 해석해서 곡해하는 것은 바보가 하는 짓이다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

또 고독한 천재 한명 탄생하게 생겼네요.
가끔 이렇게 KLDP엔 고독한 천재가 한명씩 등장해서
세상 모든 사람들을 바보로 만들곤 하네요.

dltkddyd의 이미지

그쪽 말을 한 게 아닌데, 괜한 오해를 하시는군요. 저 위에 jick님이 쓴 글에 대해 말한 겁니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

dltkddyd의 이미지

예민하게 받아들일 이유는 없는데, 조금 여기 사람들이 편협한 것 같군요. gcc에 대한 불편함을 말한 것 뿐인데, 과민하게 받아들이는 님들의 태도에 조금 문제가 있는 듯 하군요.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

dltkddyd의 이미지

내가 세상 모든 사람들을 바보라고 그랬나? 그런 말 한 적 없는데. 저 위에 바보스런 답변을 한 사람에 대해 하는 말이지. 님이 그럼 jick인가? 그래서 기분이 나쁜거요?

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

dltkddyd의 이미지

참, 동문서답이군.

"손 잡아주세요."

라고 내가 말 했던가? 이렇게 편협하게 말하는 사람은 또 처음이네...

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

snowall의 이미지

꼭 해야 한다면 정해진 epsilon만큼의 차이는 0으로 간주하도록 코드를 고치셔야겠네요.

피할 수 있을때 즐겨라! http://melotopia.net/b

snowall의 이미지

http://kldp.org/node/132646

위 글도 참고하세요

피할 수 있을때 즐겨라! http://melotopia.net/b

익명 사용자의 이미지

컴퓨터가 실수를 표시할 때 내부적으로 2진 부동소수점을 사용하기 때문에 발생하는 문제고
부동소수점 표현방식인 IEEE 754를 따르는 모든 C 컴파일러에 공통적으로 발생하는 문제입니다.
gcc뿐만 아니라 MS의 Visual C/C++를 포함한 거의 모든 상업용 C 컴파일러도 이 문제에 대해서는 마찬가지입니다.

따라서 GNU 정신을 들먹이며 왜 자기가 원하는 대로 동작하지 않는가 하고 흥분하는 것은
남대문에서 뺨맞고 동대문에서 화내는 것과 다름 없습니다.

콜롬부스의 달걀?

매스매티카 같은 경우는 그래서 분수나 소수를 부동소수점으로 처리하지 않고 의미에 따라 처리합니다.
콜롬부스는 이미 옛날에 여럿 나왔으니 혼자 발견한 것처럼 흥분 안하셔도 됩니다.
원하시는 걸 얻고 싶으시다면, 옛 콜롬부스들이 만든 제품을 찾아 보시던가
아니면 직접 옛 콜롬부스들이 했던 방식대로 (의미 기반으로) 코드 직접 짜면 됩니다.

그리고 마지막으로...

hexadecimal floating-point literal라고 들어보셨어요?
정확한 숫자 표현을 원하면 16진으로 직접 써 주세요.

dltkddyd의 이미지

gcc가 아닌 다른 컴파일을 사용하던 중에 이런 문제가 일어났어도 똑같은 문제 제기를 했을 겁니다. 또한 내가 콜럼부스라는 말을 한 적도 없고요.

개구리 올챙이적 기억 못 한다.

라는 말이 왜 떠오르는지.....

주의를 경계하지 않는 개구리는 언제든지 매의 먹이가 될 수 있다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

익명 사용자의 이미지

콜롬버스의 달걀 세우기도 무의미한 짓이었을까? <- 이 리플 쓴사람 대체 누구?

dltkddyd의 이미지

무슨 말을 하고 싶은 건지? 눈은 보라고 있는 것인데. 참, 이런 쓸데없는 댓글이나 올리는 저 아이디 없는 놈은 대체 뭐야?
저 위의 콜롬버스에 대해 언급한 내 말을 듣고 나 스스로가 콜롬버스라고 말하고 있는 것으로 이해했다면, 님은 정말 사오정입니다. 정말 말귀를 못 알아듣고 있네. 은유가 무엇인지도 모릅니까? 국어 시간에 졸았나 보죠?

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

chadr의 이미지

댓글을 보니 가관이네요. 거참.......

http://en.wikipedia.org/wiki/IEEE_754-1985

위 url을 정독 하시고 왜 그럴수 밖에 없는지 학습해보세요.
정독 해도 모르시겠으면 직접 예제로 본인이 하고자 하는 데이터를 위 url에서 설명하는대로
계산해보세요. 그래도 이해가 안가시면 본인이 GNU 정신에 맞게 새로운 표준을 고안해서 공개 하시면 됩니다.

컴퓨터가 사람을 따라오도록 만들었으면 아마 지금쯤은 사람의 뇌를 실시간으로 구동할수 있는 기계가 나왔을겁니다.

왜 컴퓨터가 2진수를 사용하게 되었는지 곰곰히 생각해보시길 부탁드리며 그러므로써 얻을 수 있는 이득을 생각해보세요.

우리가 왜 프로그램을 만드는지도 생각해보세요. 컴퓨터에 일을 시키려고 프로그램을 만드는 것이지 컴퓨터를 사람으로 만들기 위해서 프로그램을 만드는게 아닙니다. 따라서 컴퓨터가 일을 잘 할수 있게 우리가 컴퓨터에 맞춰야지 컴퓨터가 무슨 인공지능 두뇌가 있는것도 아니고 어떻게 사람에 맞추겠나요.

따라서 컴퓨터에 맞춰서 만든 gcc도 그런 한계를 태생적부터 가지고 있습니다.

컴퓨터가 사람을 따라오도록 만들어야한다고 아직도 그렇게 생각하신다면 컴퓨터 태생 자체부터 왜 그따위로 만들었냐고 마이크로 프로세서를 처음 만든 할아버지한테 따지셔야 할것 같습니다.

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

dltkddyd의 이미지

double과 float 비트구조는 이미 숙지하고 있습니다. 여하튼 좋은 자료 감사합니다.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

dltkddyd의 이미지

printf나 scanf와 같은 함수로 출력할 때 45.4f로 입력했으면, 45.4가 화면에 그대로 출력되거나 문자형 포인터 대상체에 그대로 저장된다면, 좋을텐데요. 제 말은 요 부분에 대해 말씀드린 것이지, 전반적인 것이 그러해야 한다는 것은 아니었습니다. 이것 좀 고칠 수 있다면 제가 고쳐보기라도 해볼텐데요.

본인 맞습니다.
인증샷
우헤헤헤... 로 대신합니다.

chadr의 이미지

printf로 실수를 출력할때 float을 출력할때 어떻게 하시나요? double을 출력할때 어떻게 하시나요?
둘다 형식 문자 %f를 쓰실겁니다. %F 이런거 없어요.

사실 printf로 float을 넘기든 double을 넘기든 printf가 받는건 double입니다. 즉, 실수형은 float으로 가변인자로 넘길경우에는 double로 자동 형변환이 됩니다.

  float f = 13.456780000f;
008C183E  fld         dword ptr [__real@41574ef9 (8C57A0h)] 
008C1844  fstp        dword ptr [f] 
  double d = f;
008C1847  fld         dword ptr [f] 
008C184A  fstp        qword ptr [d] 
  int a = 0;
008C184D  mov         dword ptr [a],0 
 
  printf("%f", f);
008C1854  fld         dword ptr [f] 
008C1857  mov         esi,esp 
008C1859  sub         esp,8 
008C185C  fstp        qword ptr [esp] 
008C185F  push        offset string "%f" (8C573Ch) 
008C1864  call        dword ptr [MSVCR90D_NULL_THUNK_DATA (8C82B4h)] 

위 코드를 보면 fld, fstp를 이용해서 실수를 처리하는 것을 볼수 있습니다. 잘 보시면

008C1854  fld         dword ptr [f] 
008C1857  mov         esi,esp 
008C1859  sub         esp,8 
008C185C  fstp        qword ptr [esp] 

에서 float을 dword로 로드 한다음에 sub esp,8을 이용해서 스택을 8바이트만큼 확장을 합니다.
즉 float을 인자로 넘겨주기 위해 double 크기인 8바이트 스택을 할당하는 것입니다.

그리고 fld로 로드한 실수 데이터를 스택에 저장하기 위해서 fstp를 이용해 qword크기 만큼 즉 8바이트만큼 읽어옵니다.

분명 fld를 이용해서 dword 즉, 4바이트로 로드 했는데 어떻게 qword로 읽어올까요?
그건 바로 cpu의 명령어인 fstrp가 자동으로 float을 double로 형변환해서 알아서 가져오기 때문입니다.
이런 형변환을 하면서 오차가 발생하는 것입니다.

즉 float을 double로 형변환 하는것은 printf도 아닌 gnu libary도 아닌 gcc도 아닌 바로 intel cpu가 하는 짓이었습니다.

자 이제 intel사에 연락하여 따져주세요. 왜 그따구로 만들었는지.

위에서 예로 든 13.456780000을 32비트 단정밀도를 가지는 실수 표현방법으로 표현을 해보시고..
이를 다시 64비트 배정밀도를 가지는 실수 표현 방법으로 표현을 해보세요.

손으로 하기 귀찮으시면 http://babbage.cs.qc.cuny.edu/IEEE-754/ 여기를 방문해서 입력해보세요.

비트 배열이 어떻게 나오며 어떻게 표현이 되나요?
그게 바로 왜 저렇게 나오는 이유입니다.

말씀하신 45.4도 동일하게 해보세요.

문제를 제기하신 코드를 한번 봅시다.

  double a=45.4;
013113DE  fld         qword ptr [__real@4046b33333333333 (1315750h)] 
013113E4  fstp        qword ptr [a] 
  double b=45.4f;
013113E7  fld         qword ptr [__real@4046b33340000000 (1315740h)] 
013113ED  fstp        qword ptr [b] 

위에서 컴파일러가 맘대로 45.4를 반올림 해서 근사치로 표현해버렸습니다. 말씀하신대로 소프트웨어적으로 원하는 곳에서 반올림을 했다 칩시다.
그 다음에 cpu의 fld dword, fstrp qword를 실행 했을 경우 다시 발생하는 오차는 어떻게 하실건가요?

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

익명 사용자의 이미지

요즘 정확한 실수 연산을 목적(과학/수학/그래픽 연산 등)으로 하는 경우 향상된 성능/정밀도를 제공하는
GPU를 이용해 연산하는 경우가 많습니다. (Cuda etc.)

위에 많은 분들이 언급하신 것처럼, tunecolor님의 원천적으로 원하시는
문제를 해결하기는 현재 시스템에서 많이 힘들 것 같구요.

조금이라도, 우회하거나 본인이 원하는 기대치에 많는 방식을
찾아서 결과를 내시는게 더 현실적으로 보입니다.

http://developer.nvidia.com/category/zone/cuda-zone

-ps- 질문자는 아마도.. 같이 고민해보고 싶었던 같습니다.
노력이 부족해 보이거나, 뜬금 없는 질문을 할 때도 같이 고민하고
현명한 대답을 한다면 답변자 본인에게 이득이 됨을 인지하셨으면 좋겠습니다.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.