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

자유게시판
세상 살아가는 이야기들을 나누는 사랑방입니다.
[19273] 황당한 C++ Builder (버그??)
박우성 [] 5042 읽음    2011-03-24 13:15
조필형님이 질답게시판에 올리신 것을 보고 황당하여 해당 소스를 수정하여 자유게시판에 올려 봅니다.

해당 질문 : http://cbuilder.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_qna&no=63978

다음과 같은 코드를 작성해 보았습니다.

    int n1, n2, n3, n4;
    double d;

    const int c = 2;
    int n = 2;

    n1 = (int)(1000 * 2 / 1000.0 );
    n2 = (int)(1000 * c / 1000.0 );
    n3 = (int)(1000 * n / 1000.0 );
    n4 = (int)(100 * n / 100.0 );

    d = 1000 * n / 1000.0;

    printf("%d, %d, %d, %d, %.0lf", n1, n2, n3, n4 ,d );

* C컴파일러에서도 실행되게 하기 위하여 int에 괄호를 붙였습니다.
* n1은 상수의 연산이므로, 컴파일 타임에 모두 계산될 것입니다.
* n2는 상수형 변수를 사용했는데, 제 생각으로는 이것도 상수로 취급하여 컴파일 타임에 계산 될 것 같습니다.
* n3는 변수가 사용되었으므로, 런타임에 계산될 것입니다.
* n4와 d의 값도 런타임에 계산될 것입니다.

C++ Builder로 컴파일해서 실행해 보면, 제 예상은 2가 5개 출력 될 것이라 예상했습니다.
그런데, 황당하게도 n3만 1로 출력됩니다.

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

다른 컴파일러에서 실행해 보면.....

Visual C++ : 2, 2, 2, 2, 2
Linux(Ubuntu) GCC: 2, 2, 2, 2, 2

지금까지 프로그래밍 해오면서, 저런 형태의 연산을 수없이 사용했는데...
정말, 충격입니다.

조필형님은 C++ Builder 6에서 테스트 하셨고, 저는 C++ Builder XE에서 테스트 했으므로, 아마 모든 버전에서
결과가 같지 않을까 추측해 봅니다.

제가 알고 있는 프로그래밍 상식(?)으로는..
n1과 n2는 상수이거나, 상수형변수이므로, 결과는 같아야 합니다.
n2, n3는 상수 변수이거나, 값이 같은 변수를 사용했으므로, 결과가 같아야 합니다.
n3, n4는 1000이라는 상수와 100이라는 상수만 다르므로, 수학적으로 두 식은 결과가 같아야 합니다.

"제가 제일 황당하다고 생각되는 부분이 n3와 n4의 결과값이 왜 다른가 입니다."

이 문제에 대하여, 고수님들의 의견과, 엠바카데로 측의 의견을 듣고 싶네요.

델파이는 어떨까요?
서비 [suby]   2011-03-24 13:42 X
오호 제 상식에서도 신기한 일이네요;
김태선 [cppbuilder]   2011-03-24 14:09 X
그건 C++Builder의 버그가 아닙니다.
전통적인 컴퓨터의 10진수 저장방식에 따른 문제입니다.

본디 컴은 2진수에 근간한 16진수를 내부에서 쓰고 있기 때문에 10진수로 된 실수를
저장하는데 그게 완벽할 수 없습니다. 그래서 약간의 오차가 발생할수 밖에 없습니다.
float 타입과 double 타입은 같은 실수 타입이지만 저장공간의 차이에서
float 타입의 오차가 큰 것은 그런 이유 때문이며, 정밀한 계산에서는 double 형도 그 오차를 보일수 밖에 없습니다.

컴이 계산하는 것은 정해진 변수형에 따른 최선의 계산 결과일 뿐입니다.
그래서 말씀하신 그런 오차가 나게 되는 경우가 생기는데,
이는 C/C++ 및 전통적인 컴퓨터 언어의 공통된 문제입니다.

다만 이 이슈가 오래된 것이니 만큼
그리 복잡하지 않은 연산에 있어서는
컴파일러가 알아서 보정해주는 기능이 들어갔느냐 안 들었느냐 또는 그 기능을 쓰느냐 그 기능을 껐느냐 하는 차이가
있을 뿐입니다.

빌더를 만든 개발사가 이 문제를 몰라서 방치한 것은 아니고,
그런 이유가 있기 때문에, 이 문제를 프로그램하는 개발자에게 맡긴 것입니다.

오차가 거의 없이 계산하려면
double 형으로 모두 계산하면 됩니다.
정수형으로 변환하는 int를 넣게 되면 컴파일러는 원칙에 의거하여 뒷부분을 잘라버려
정수 오차가 생기게 되지요.

  n3 = (int)(1000 * n / 1000.0 ); 
이 값은 내부에서 1.99999999999999 식으로 계산되는데
int 로 깍아버리니 1이 되는 것이지요
다른 컴파일러는 이 경우 보정해서 2로 만들어 주는 것이고요.
1000이 실수로 계산되어질때 BCD규칙에 의해 다시 십진수로 꺼내보면 999.999999999999 식으로 되는데 따른 문제입니다.
이를 연산에 사용하면 999.9999999999998 식으로 되는 경우가 생기죠.
이 경우 int로 대입할때는 int는 소숫점 뒷자리가 뭐든지 관계 없니 잘라버리므로 오차가 나게 되지요.

그래서 계산은
    double dd = (1000.0 * n / 1000.0 );
    int nn = dd;
식으로 실수로 계산하고 그 값을 int 가 건너 받는 식으로 하면 오차를 최대한 줄이게 됩니다.

그런데 이런 보정이 원래 C/C++ 표준이 아니기 때문에,
빌더에서 정수형이 받을 경우 기본적으로 보정이 되지 않게 한 것으로 알고 있습니다.

int 와 실수형을 혼용 계산하실때는
컴의 이런 특성을 감안하셔야 합니다.
다른 컴파일러가 보정해 준다고 그걸 또 계속 믿고 순진하게 계산하면 안됩니다.
보정이라는건 한계가 있을수 밖에 없기 때문입니다.

박우성 [solgari]   2011-03-24 14:42 X
정확한 이유를 알고 계신 분이 있을 것이라 생각했는데, 김태선님이 명쾌한 답변을 주셨네요.

위의 소스에서 컴파일 타임에 계산되는 n1, n2는 보정을 해 주면서, 런타임 때는 보정을 안해 주니까,
개발자 입장에서는 헷갈릴 수 밖에는 없을 것 같습니다.

실수형과 정수형을 혼용하여 사용할 때는, 실수형으로 결과를 받은 후, 정수형으로 옮겨야 겠군요.
정말, 중요한 것을 배운 것 같습니다.

개인적인 생각으로는, C++ Builder 방식이 마음에 들지는 않습니다.
다른 대부분의 컴파일가 보정을 하는 이유는 개발자의 실수를 최대한 줄이기 위한 것 같은데,
개발툴(컴파일러)은 개발자를 위한 것이 아니었던가요?
박우성 [solgari]   2011-03-24 15:01 X
C/C++의 표준이 어떻게 되는지는 모르겠지만, C++ Builder에서의 문제점은
컴파일 타임의 연산에서는 보정을 하고, 런타임에는 보정을 안 한다는 것이겠네요.

    n2 = (int)(1000 * c / 1000.0 );
    n3 = (int)(1000 * n / 1000.0 );

이 두줄의 코드를 보고, 다른 방식으로 처리(연산) 될 것이라 생각하는 개발자가 과연 얼마나 될까요?

C++ Builder는 보정을 하던지, 안 하던지 처리방식을 하나로 통일할 필요는 있다고 보여 집니다.




Lyn [tohnokanna]   2011-03-24 15:23 X
뭐 보정이 되다 안되다 하더라도...

애초에 "정확한 값" 이 필요하면 부동소수점을 쓰지 않을 것 이고.
평범한 경우엔 그냥 "대략의 값" 이면 충분 하기 때문에 문제는 없을 것 같네요

애초에 부동소수점 연산이 정확하다고 믿는쪽이 잘못이라고 생각. 그나마 가까운 값으로 정수변환을 하려면 반올림을 하면 되겟죠.
아제나 [azena]   2011-03-24 15:32 X
어찌되었건 상수와 변수의 계산 결과가 다른 점은 분명 문제점이라고 보여지는군요.
조필형 [reminis]   2011-03-25 10:08 X
와우 박우성님 김태선님 정말 감사합니다.
박우성님의 체계적인 실험과
김태선님의 명쾌한 해설 "보정은 C/C++의 표준이 아니다"라는 말에 감명받고 갑니다.
주정섭 [jjsverylong]   2011-03-25 11:15 X
참으로 유감스럽지만, 사실을 인정합시다. 코드기어 제품 전체 라인에 걸쳐서 품질 관리에 심각한 문제가 있는 것은 엄연한 사실이며, 특히 시빌더는 너무 관리가 안되어서 현재로서는 가장 낙후된 C++ 컴파일러 중의 하나일 뿐입니다. 시빌더 보다 좋은 다른 C++ 컴파일러를 사용하는 것이 정신 건강에 좋을 듯 합니다.

현재 델파이 품질 관리도 엉망인데, 잘 팔리지도 않는 시빌더 품질 향상을 코드기어한테 기대한다는 것은 거의 불가능할 듯 합니다.
Lyn [tohnokanna]   2011-03-25 11:49 X
뭐 품질이 엉망인건 사실이죠.

근데 엉망이긴 하지만 정상적으로 코딩 하면 문제는 없는것도 사실이죠.
퍼포먼스가 쓰레기 of 쓰레기인건 답이 안나오지만..
김태선 [cppbuilder]   2011-03-25 13:41 X
C++Builder 나 델파이 같은 뛰어난 컴파일러를 폄하하는 것은 좀 아닌 것 같군요.

일반 윈도 어플리케이션 개발에 있어서
MS VC나 다른 개발툴에 비해 월등한 성능을 보이는
빌더나 델파이는 그 만한 가치가 있기에 MS의 막대한 물량 공세와 주도권 속에서도
아직 적지 않은 사용자를 확보하고 있는 것이죠.

위에 언급된 연산 문제를 그냥 두는 것은, 제 생각엔
과거 프로그램과의 호환 문제 때문으로 생각됩니다.
새로 만드는 프로그램은 상관 없지만 과거 프로그램 경우는 위와 같은 특성을 이용한
계산이 적지 않게 있기 때문입니다. 그런데 강제로 보정해 버리면
다른 결과가 나오기 때문에, C++ 빌더가 이를 강제 보정하지 않는다고 탓할 문제는 아니라고 봅니다.

최근에 나온 XE 버전의 빠른 CodeCompletion 성능은 놀라운 수준이더군요.
Lyn [tohnokanna]   2011-03-25 13:49 X
여긴 개발툴 성능을 얘기 하는게 아니니... CodeCompletion은 제쳐 두고... 디자이너도 제쳐 두고...
(뭐... 제가 보기에 아직 빌더의 IDE는 메모장입니다. 델파이에 비하면. 느리고 기능 딸리고.. -> 한번 찍을때마다 딜레이 생기는거 보면 화가 절로 난다는)

그런데.  컴파일러로서의 성능을 비교하면... 빌더는... 최하위권이죠
아웃풋 바이너리도 느리고 컴파일 속도도 느리고..
Lyn [tohnokanna]   2011-03-25 13:50 X
보정문제는 위에서도 말햇지만 정상적으로 코딩하면 어느쪽이던 상관 없으므로 패스.
주정섭 [jjsverylong]   2011-03-25 16:43 X
델파이가 한때는 대단히 우수한 컴파일러이자 개발툴 이었는지 몰라도, 현재도 그러할까요?

VCL은 과거에는 위대했으나 이제는 시대의 변화를 따라가지 못하는 구한말 양반같은 처지이고, 컴파일러는 매우 구리고,  IDE는 버그투성이이며, 새 버전을 낼때마다 QA는 갈수록 엉망이고 ...

내 생각으로는 위의 저 버그는 버그일뿐 시뿔의 표준과는 무관합니다. 개발자는 편하고 좋은 툴을 사용하면 될 뿐, 저런 사소한 버그를 피하기 위해 머리털을 곤두세워 코딩해야 하는 고단한 존재이어야만 일까요? 개발자가 스스로 불편한 툴을 굳이 사용해야 하는 이유는 없다고 봅니다.

델파이에도 당연히 저런 비슷한 버그가 있습니다. 코드기어 델파이 개발진들의 게을러 터짐 때문에, 전혀 개선이 안된 실수 연산 버그일 뿐입니다. 이와 비슷한 유형으로 델파이만의 은행 방식 반올림이라는 기막힌 버그같지 않는 버그도 있습니다.

자신이 사용하는 툴을 자랑하거나 사랑하는 것은 당연할지 몰라도, 개발자로서 논리적으로 현실을 냉정히 돌이켜 보는 것은 매우 중요하다고 봅니다. 한때 열렬한 델파이 광신도 였던 내가 이런말을 하게 될줄은 몰랐습니다만, 모든 객관적 사실들이 델파이의 앞날이 매우 어두울 것임을 명백히 하고 있습니다.

남병철.레조 [lezo]   2011-03-26 13:10 X
"개발자는 편하고 좋은 툴을 사용하면 될 뿐,"

맞는 말이기도 하지만 틀린 말이기도 합니다. 개발자를 도구 수준으로 바라보는 마음이 있다면 맞는 말이고 자의식을 가진 사람이라고 본다면 틀린 말입니다. 그 생각에 동의하는 개발자도 있지만 동의하지 않는 개발자도 있습니다.

또한 구한말 양반이 몰락한 것은 시대의 흐름을 못 읽은 것 보다 기득권이 부패하고 무능했기 때문입니다. 눈을 뜬 선비들은 무조건 신 문물을 배격하지 않았습니다. 제국주의적 식민지화는 그 이전 산업혁명 등으로 인한 엄청난 물질적 불균형 속에서 태어난 세계사적 비극의 결과물입니다. 지금의 기득권들도 비슷한 양상을 보이고 있지만 정보화로 인해 눈치는 그 때보다 빠를거라 생각되어 약간? 희망은 남아 있어 보입니다만...;;

흠... 그리고 재밌는 테스트를 재연하셨네요. ㅎㅎ
실수와 정수 등의 수치 연산에서 서로 섞어쓰는 경우 Implicit Type Casting이 일어나게되는데 직관적인 입장으로 보자면 이건 QA에 알리는게 좋을것 같습니다.
그리고 타입을 섞어서 연산하는건 자신의 계획 범위를 벗어나는 동작들이(Implicit) 일어날 수 있기 때문에 조심해야 합니다.
저도 연산할 때는 최대한 타입을 맞춰 주고 있습니다.

그리고 지금은 변수에 단순한 값을 가지고 테스트 했지만 실무에서 동작하는 프로그램이 저것보다 복잡한 계산식으로 코딩되어 있다면 어떤 컴파일러라도 프로그래머의 의도와 달리 미묘한 차이를 보일 가능성이 있습니다.

돈 계산 같은 특수한 상황이면 Implicit Type Casting도 생각해서 타입을 맞춰주는 프로그래밍 정도는 해야할 것입니다.
크레브 [kkol]   2011-03-28 14:40 X
위와 같은 문제가 문제가 되는 개발자는 계속 신경쓰이고 문제가 되는것이고.. 그렇지 않은 사람은 별 문제 없이 잘 쓰겠죠
개발가 자신의 상황에 따라 툴을 바꾸던지 말던지 선택하면 될 일입니다.

개인적인 입장으로는 당장 엠바카데로가 망해도 지금 버전을 가지고도 수 년간은 크게 문제없이 개발 프로젝트를 진행할 수 있고
문제가 된다면..그 때가서 다른 툴을 검토하기 시작해도 늦지는 않을듯하네요

+ -

관련 글 리스트
19273 황당한 C++ Builder (버그??) 박우성 5042 2011/03/24
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.