C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
분야별 포럼
C++빌더
델파이
파이어몽키
C/C++
프리파스칼
파이어버드
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

자유게시판
세상 살아가는 이야기들을 나누는 사랑방입니다.
[19359] Re:C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?)
박지훈.임프 [cbuilder] 4934 읽음    2011-04-05 14:24
다시 한번 찬찬히 읽어보니, 무슨 말씀이신지 알겠습니다. 제가 저번에 2 / 1000.을 예시한 것은, 부동소수점에 있어서는 일반적으로 10진수의 값을 정확히 표현하지 못한다는 것을 예시하고자 한 것입니다.

다시 한번 테스트를 해봤는데요. 박우성님이 테스트하신 코드에서,
int n2 = int( d2 / double( 1000.0 ) );
double dd = d2 / double( 1000.0 );
int n5 = int(dd);
printf("%d, %d\n", n2, n5 );

동일한 나누기 연산을, n2의 경우에는 연산 결과를 곧바로 int로 캐스팅했고, n5의 경우에는 연산 결과를 double 변수인 dd에 한번 담았다가 캐스팅을 했습니다. 그런데 이 결과가 C++빌더에서는 여기서 n2와 n5가 각각 1과 2로 다르게 나오는 것이, 일반적으로는 상식적으로는 이해가 안되죠.

이건 double 사이의 나누기 결과는 double이 아닌 long double로 나오기 때문입니다. 그래서 double 변수에 다시 집어넣으면 캐스팅이 일어나면서 가장 아랫 부분의 값들이 잘리거나 보정됩니다. 예를 들어 위 코드 다음에 다음과 같은 코드를 추가할 경우, OK는 찍히지 않습니다.
if (d2 / (double)( 1000.0 ) == dd)
    printf("OK");

이것을 두고, 어쨌든 잘못된 결과가 나오지 않느냐, 라고 생각할 수도 있습니다. 다음과 같은 경우를 보시죠.

    double d1 = 1000.0;
    double d2 = 1999.99999999999999;

    int n2 = int( d2 / (double)( 1000.0 ) );
    double dd = d2 / double( 1000.0 );
    int n5 = int(dd);

    printf("%d, %d\n", n2, n5 );

박우성님이 테스트하신 코드와 기본적으로 동일한 코드인데, 다만 d2의 값만 2000에서 1999.99999999999999로 바꾸었을 뿐입니다. 그럼 이 결과는 상식적으로는 1이 나와야할텐데 그렇지 않습니다. C++빌더에서 결과는 [1, 2]가 나오고, VC++에서는 [2, 2]가 나옵니다. 즉, 이 경우에는 박우성님이 제기하신 경우와 정반대로 C++빌더가 더 정확한(해보이는) 결과를 보여주는 겁니다.

1999.99999999999999라는 값이 극단적이 아니냐, 혹은 그런 값을 언제 쓰겠냐 하실 수도 있는데, 중요한 것은 1999.99999999999999이든 2000.0이든 부동소수점의 범위에서는 아주아주 미약한 일부의 예일 뿐이고, 이렇게 서로 다른 값이 나오는 예는 부동소수점 전체의 값 범위에서 수도 없이 많이 일어날 수 있다는 겁니다. 어떤 경우에는 VC++이 맞게 보이고 어떤 경우에는 C++빌더가 맞게 보이고 말입니다.

VC++ 컴파일러가 보정을 하는지 않는지는 전 모르겠습니다. 정말로 보정을 한다면 그건 심각한 문제일 것 같군요. 저라면 보정을 하는 C++ 컴파일러라면 믿을 수 없어서 실무에 쓰지 못할 것 같습니다. 단지 VC++이라서가 아니라요. 설마 그런 짓을 할 것 같지는 않은데...

박우성님은 부동소수점 변수에 넣은 값이 "정확"하냐의 문제에 집착하고 계신 것 같은데요. 부동소수점 변수의 값은 2진수이면서 동시에 '부동소수점'이라는 이름 그대로 소숫점을 가지고 있기 때문에 10진수의 관점에서는 절대로 정확할 수가 없습니다. 어쩌다 정수 값과 같은 값이 나오더라도 그런 경우가 우연이고, 그걸 기대하고 코딩을 하면 안됩니다.

부동소수점 변수에 10진수 상수 값을 넣으면, 부동소수점 변수에 넣으면서 한번 오차가 생기고 또 그 값을 10진수로 꺼내오는 과정에서 또 한번 오차가 생깁니다. 정수 혹은 ordinal 타입들이 표현할 수 있는 값의 범위의 차이가 있을 뿐 항상 정확한 데 비해 부동소수점 타입의 값은 항상 근사치일 뿐이고, 그래서 값 범위 외에 얼마나 근사하느냐의 기준인 precision이 있는 겁니다.

결론적으로 중요한 것은, 부동소수점 값은 태생적으로 오차를 안고 있는 존재이기 때문에 부동소수점이 정수와 비교될 수 있는 경우에는 쓰지 말아야 하고, 그런 경우라면 최대한 정수를 써야 합니다. 그리고 억지로 정수와 비교를 해야 한다면 오차를 보정하는 책임은 오직 개발자에게만 있습니다.



박우성 님이 쓰신 글 :
: 제가 처음에 제기했던 부동소수점 문제에 대하여, 박지훈님, 김태선님 등 많은 분들이 의견을 올려 주셨습니다.
:
: 제가 처음 프로그래밍을 배울때, 부동소수점 특성에 대하여 배웠지만,  C/C++을 주 언어로 사용하여 개발자로 살아온지 10년이 넘어가지만, 그런 부분에 대하여 잊어버리고 인식을 못한 채 개발을 하고 있었습니다. 필요한 자리수에서 반올림하여 사용하였으니, 이런 부분에 신경을 안 쓰게 되고, 큰 문제가 없었던 것입니다.
:
: 많은 개발자 들이 저와 비슷할 것으로 생각됩니다.( 나만 그럴수도 ^^ )
: 이번을 계기로  부동소수점에 대하여 공부를 다시 할 수 있게, 자세한 글을 올려 주신 분들께 감사드립니다.
:
: 덕분에, 부동소수점 Binary 저장 방식이 IEEE 754를 따른 다는 것도 알게 되었습니다.
:
: IEEE 754 방식을 사용하여 부동소수점을 저장하거나 연산하게 되면, 오차가 발생하고, 정수형으로 변환하게되면
: 엉퉁한 값이 나올수 있다는 것인 핵심인 것 같습니다.
:
: "그런데, 제가 제기한 처음 소스는 이런 IEEE 754방식에 따른 문제가 아닌 것 같아 다시 테스트를 해 보았습니다."
:
: 먼저, 박지훈님이 제가 올린 소스의 핵심은 2/1000.0이라고 하셨는데, 제가 올림 소스의 핵심은 2000/1000.0입니다.
: 다음 소스를 C++ Builder에서 실행해 보면
:
:     printf("%40.39lf\n", double(2.0 / 1000.0) );
:     printf("%40.39lf\n", double(2000.0 / 1000.0) );
:
: C++ Builder 실행 결과 :
:
: 0.002000000000000000042000000000000000000
: 2.000000000000000000000000000000000000000
:
: 확실히, 2.0/1000.0은 부동소수점 문제가 있을지 몰라도, 2000.0/1000.0은 부동소수점 문제가 발생하면 안 되는 것입니다. 그렇게, C++ Builder도 처리를 했구요.
:
: 다음 소스가 확실하게 부동소수점 문제를 나타내는 것입니다. (수학적으로 결과가 230이 나와야 하죠)
:
:     printf("%40.39lf\n", double(2.3 * 100) );
:
: C++ Builder XE 실행 결과 :  229.9999999999999716000000000000000000000
: VC++ 2010 실행 결과 : 229.999999999999970000000000000000000000000
: GCC 실행 결과 : 229.999999999999971578290569595992565155029
: PHP 실행 결과 : 229.999999999999971578290569595992565155029
:
: ( 누가, JAVA나 C# 도 테스트 해 주시면 감솨함돠^^ )
:
: 위의 결과로 볼때, GCC, PHP는 같은 결과이고, C++ Builder 와 VC++은 조금 다르지만, 부동소수점 문제를 대부분 가지고 있습니다. Java나 C#도 아마 비슷한 결과가 나올 것으로 보여집니다.
:
: 제가 처음에 김태선님의 글을 보고, 컴파일 타임에서는 보정(더 정밀)하고, 런타임에서는 보정없이 부동소수점 문제가 발생할 수 있다고 생각했습니다. 하지만, 위의 소스는 컴파일 타임에 계산되고, 보정이 없는 값으로 출력되는 것으로 보아서 이것도 잘 못 된 생각이었습니다. 그래서, 기존의 소스를 다음과 같은 수정하여 테스트를 해 보았습니다.
:
:     double d1 = 1000.0;
:     double d2 = 2000.0;
:     double k = 1.0;
:
:     int n1 = int( d2 / d1 );
:     int n2 = int( d2 / double( 1000.0 ) );
:     int n3 = int( d2 / double( 1000.0 * k ) );
:     int n4 = int( d2 / ( double( 1000.0 ) * k ) );
:
:     double dd = d2 / double( 1000.0 );
:     int n5 = int(dd);
:
:     printf("%d, %d, %d, %d, %d\n", n1, n2, n3, n4, n5 );
:
: 위의 n1 ~ n5의 값은 모두 실행 시간에 계산됩니다.  실행 결과를 보시죠.
:
: C++ Builder XE 실행 결과 :  2, 1, 2, 2, 2  ( C++Builder 2010에서도 같은 결과)
: VC++ 2010 실행 결과 : 2, 2, 2, 2, 2
: GCC 실행 결과 : 2, 2, 2, 2, 2
: PHP 실행 결과 : 2, 2, 2, 2, 2
: 아마 Java나,  C#도 "2,2,2,2,2"라는 결과가 나올 것으로 예상 됩니다.(누가 테스트 좀 해 주세요)
:
: 확실하게 2.3 * 100과는 다르게, 2000.0/1000.0 계산은 C++Builder를 제외하고, 부동소수점 문제를 발생시키지 않습니다. C++ Builder도 n2와 같이 처리할 때만 문제를 일으킵니다.
:
: "C++ Builder의 처리 방식에 잘 못이 없는 것일까요? "
:
: 이미 이런 문제를 피해가는 방법은 김태선님이 알려주신대로, n5처럼 하면 됩니다. 하지만, 이런 문제에 대하여 정확히 알고 있는 상태에서 생각할 문제라고 보여 집니다.
:
: Embacadero에서 이런 문제는 이미 알고 있고, 기존 소스와 호환성을 위해서 이런 문제를 보완하지 않는 것은 게으른 변명에 지나지 않습니다.  컴파일 옵션에 이 부분에 대한 옵션을 넣어서, 개발자가 기존 소스와 호환성을 택할 것인지, 아니면, 이런 문제가 없게끔 컴파일 할 것이지 선택하게 하면 됩니다.
:
: 실제로 개발하다 보면, 정수사용, 반올림 등 다른 방법으로 처리하기 때문에 문제될 것이 없다는 분도 있는데, 정확하게 문제를 알고 사용하는 것과, 잠제되어 있는 문제점을 모른 상태에서 사용하는 것은 다르다고 봅니다.
:
: 오해가 있을까봐 말씀드립니다. 제가 이런 글을 올리는 이유는 C++ Builder를 폄하하고자함이 아닙니다.
:
: 저는 C++ Builder를 현존하는 최고의 C++개발툴이라고 생각합니다. 그래서, 10년 이상 메인 개발툴로 사용하고 있고, 아마 앞으로도 그럴 것입니다.  내가 사용하고 있는 컴파일러(개발툴)에 대하여 특성을 더 잘 알고, 문제가 있다면, 더 개선되어 버전업되기를 바랄 뿐입니다.
:
: 문제점은 숨기는 것보다, 적극적으로 알려서, C++Builder 개발자들이 스트레스 없이 이런 문제를 피해 갈 수 있어야 한다고 생각합니다.
:
: 저는 Turbo-C 2.0부터 볼랜드(Codegear/Embarcadero) 계열 C++ 컴파일러를 사용했는데, Codegear때가 가장 절망적이었던 같고, Embarcadero에서 인수하면서, 참 많이 좋아 지고 있다고 봅니다. 2009 -> 2010 -> XE로 버전업되면서 많이 개선되고, 상당히 희망적으로 봅니다.
:
: 개인적으로 이런 문제도 Embarcadero에서 보완을 할 것으로 기대하고 있습니다. 이 글을 쓰는 취지는 보안되기 전에 또는 보완이 안 된 C++ Builder를 사용하는 있는 개발자들이 이런 문제점을 정확하게 알고 사용하길 바라는 마음에서 입니다.
박우성 [solgari]   2011-04-05 14:59 X
임프님 답변 감사드립니다.

그런데, 제가 문제를 제시한 요지는 "정확"한 값에 집착한 것이 아닙니다. 이미 글의 앞부분에 언급했듯이 IEEE 754방식을 따르면 부동소수점에서 오차가 발생할 수 있다고 썼습니다. 즉 부동소수점은 정확하지 않은 것을 알고, 인지하고 글을 썼다는 것입니다.

"제가 궁금한 것은 n2의 값이 왜 n1, n3, n4와 다르냐 입니다."
제가 머리가 나빠서 그런 건지는 모르겠는데, 임프님 설명으로는 이 궁금증을 해소할 수는 없군요.

임프님 설명처럼,  d2에 1999.99999999999999를 넣어서 실행해 보았습니다.

    double d1 = 1000.0;
    double d2 = 1999.99999999999999;
    double k = 1.0;

    int n1 = int( d2 / d1 );
    int n2 = int( d2 / double( 1000.0 ) );
    int n3 = int( d2 / double( 1000.0 * k ) );
    int n4 = int( d2 / ( double( 1000.0 ) * k ) );

    double dd = d2 / double( 1000.0 );
    int n5= int(dd);

    printf("%d, %d, %d, %d, %d\n", n1, n2, n3, n4, n5 );

C++ Builder 실행 결과 : 2, 1, 2, 2, 2
VC++ 실행 결과 : 2, 2, 2, 2, 2

VC++ 경우는 앞부분에 언급한 것처럼 부동소수점 방식을 문제를 그대로 가지고 있습니다. 그런데, C++ Builder의 경우는 n2만 1이고 나머지 값들은 2로 부동소수점 문제를 그대로 가지고 있습니다.

제 질문의 요지는 n2만 왜 1이냐는 거죠?   d2의 값을 바꾸어도 왜 n2의 계산값만 다른가 하는 것입니다.

임프님 설명에는 n1, n3, 4에 대한 설명이 빠져있어서, "진짜로" 저는 아직도 잘 모르겠습니다.






박우성 [solgari]   2011-04-05 15:05 X
덧붙여...

다른 컴파일러(VC등)와 실행 결과가 달라서, 자꾸 묻는 것이 아니라, C++Builder만 놓고 봤을 때, n2만 왜 계산값이 달라야 하는지 이해가 안 간다는 것입니다. 다른 컴파일러로 테스느 한 것은, 다른 컴파일러는 일관성있게 결과를 내는지 보기 위한 것입니다.
Lyn [tohnokanna]   2011-04-05 15:24 X
아무래도 상관 없잖습니까? C++표준에 컴파일타임과 런타임의 연산결과가 같아야 된다는 조건이 있는것도 아니고.

잘못된 코드를 가져와서 문제를 제기하는것 부터가 이상하죠.
박우성 [solgari]   2011-04-05 15:34 X
Lyn님 소스와 제글은 읽으시고, 댓글을 다신 것인지요? 제가 만든 샘플 소스는 컴라일 타임과 런타임과 관련이 없습니다.
모두 런타임에 계산이 되며, 제가 문제를 제기한 것은 n2의 결과만 다른가입니다.

그리고, Lyn님은 안 그런지 모르겠지만, 부동소수점을 사용할 경우도 있고, 그 연산 결과를 정수형으로 변화할 일도 있더군요.

"잘못된 코드"라고 하셨는데, 그 근거가 뭔가요?. 정말 잘 못된 코드라면, 컴파일러가 오류는 아니더라도, 경고는 띄워줘야 하는 것 아닌가요.

전혀 위의 소스는 C++ 표준 규정에 어긋나지 않으며, 컴파일러도 오류를 발생시키지 않더군요.
크레브 [kkol]   2011-04-05 15:48 X
Lyn님은 박우성님이 이전에 올린 글 내용을 염두에 두고 얘기 한듯하네요

그리고 거꾸로 말하면 컴파일러 에러나 경고가 뜨지 않는다고 모두 올바른 코드는 아니죠
그렇다면.. 개발자가 컴파일 에러만 잡으면 디버깅 할 필요가 전혀 없게요
논리적인 오류, 잘못된 습관 , 문제 가능성이 있는 코드, 시퀀스의 오류 등등 모두 잘못된 코드입니다.
Lyn [tohnokanna]   2011-04-05 15:49 X
아 =_=a 예전이랑 같은줄 알고 글만읽고 코드는 확인 안했네요. 잘못봤습니다.
크레브 [kkol]   2011-04-05 15:54 X
어쨌든.. 구경하는 입장에서는 재밌군요. ^^
Lyn [tohnokanna]   2011-04-05 16:07 X
빌더가 없어서 확인을 못해보겠네요 흠 =_=; 울회사 빌더좀 사줘요
박우성 [solgari]   2011-04-05 16:08 X
크레브님.. 말씀중에 "문제 가능성 있는 코드" 이 부분에 눈길이 갑니다.

Lyn님.. 컴파일 테스트 중 경고 뜬 것 잘 봤습니다. 근데, 일반적으로 명시적(강제) 형변환은 경고가 뜨지 않던데요.
( 저는 처음 보는 컴파일러네요.^^)

어쨋든, 제가 이런 이슈를 제기하는 하는 것은 원글에서도 언급했듯이, 이런 문제점을 내포하고 있다는 것을 알리고자 함입니다.
많은 개발자들이 보고, 주의를 기울였으면 하는 생각입니다.

data loss가 발생하는 형변환은 늘 주의를 해야 하며, C++ 책을 뒤져 보니까, 이런 형변환의 결과는 "예측할 수 없다"라고 나오는 것으로 보아서는 컴파일러 마음이라는 소리겠죠.

그렇지만, 결과가 1이나, 2로 일관성있게 나와야 하지 않을까 하는 것입니다.
Lyn [tohnokanna]   2011-04-05 16:15 X
박우성 // 처음보는 컴파일러라뇨...?

Visual C++ 2010입니다.
박우성 [solgari]   2011-04-05 16:22 X
엥? 제가 가지고 있는 VC++ 2010 Express하고 좀 다르군요. 색상이 전혀 달라서 그렇게 보인건지..
근데, 저는 VC에서 명시적 형변환은 경고가 안 나오던데요. 옵션 조정하신 건가요?
Lyn [tohnokanna]   2011-04-05 16:30 X
아뇨. 밑에 글에도 썼지만..
명시적 형변환은 경고 안나옵니다. 명시적으로 변환 한다는건 ...

"모든 오차를 감수하고라도 변환 하겠다" 는 프로그래머의 의도를 나타낸거니까요.
크레브 [kkol]   2011-04-05 16:45 X
빌더에서는 경고 안뜹니다.

+ -

관련 글 리스트
19357 C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?) 박우성 7130 2011/04/05
19365     Re:C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?) Nibble 4536 2011/04/06
19359     Re:C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?) 박지훈.임프 4934 2011/04/05
19361         Re:Re:C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?) 박지훈.임프 6376 2011/04/05
19360         Re:Re:C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?) Lyn 5611 2011/04/05
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.