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

자유게시판
세상 살아가는 이야기들을 나누는 사랑방입니다.
[19357] C++ Builder 부동소수점 문제, 아직 끝나지 않은 문제점(?)
박우성 [solgari] 7125 읽음    2011-04-05 12:05
제가 처음에 제기했던 부동소수점 문제에 대하여, 박지훈님, 김태선님 등 많은 분들이 의견을 올려 주셨습니다.

제가 처음 프로그래밍을 배울때, 부동소수점 특성에 대하여 배웠지만,  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를 사용하는 있는 개발자들이 이런 문제점을 정확하게 알고 사용하길 바라는 마음에서 입니다.
Nibble [gameover]   2011-04-05 13:57 X
printf 함수들은 서로 내부 구동이 조금씩 다릅니다. 바이너리로 직접 비교해보심이 좋을듯 하군요.
Lyn [tohnokanna]   2011-04-05 14:06 X
잠재되어있는 문제점은 "컴파일러"가 가지고 있는게 아니라, int 로 강제캐스팅한 "프로그래머" 가 가지고있는겁니다.
Nibble [gameover]   2011-04-05 14:07 X
아울러, 일관성이 없는건 좀 옥의티긴 하지만
2.000000000001 이냐 1.999999999999 냐 같은 LSB 자리올림 내림 문제들을, int로 trucation 해서 만들어낸 문제를 놓고
마치 대단한 오차가 있는것 처럼 생각하시면 안됩니다.
캐스팅이 편하긴 하지만 소수점은 그렇게 다루라고 만들어진 녀석이 아니니까요.
부동소수점은 부동소수점끼리, 정수는 정수끼리, 고정소수점은 고정소수점 끼리 연산을 하되,
변환을 해야 되는 경우엔 정밀하게 따져야 합니다. 다른 어떤 플랫폼도 이로부터 자유로울순 없어요.
미시적으로 보면, 서로 맘에 안드는게 너무나 많습니다.
미리 말씀드렸듯 알고 쓰는게 무척이나! 중요하구요.
일일이 시스템별로 테스트하는건, 한번쯤 해 볼 좋은 경험인진 모르겠지만,
생산성은 별로 없어 보이네요.
또한, 윗 글에 더해
sprintf 는 부동소수 출력을 지원하지만 wsprintf 는 지원하지 않습니다. printf 를 믿지 마세요.
라스코니 [chouoo]   2011-04-05 15:07 X
바이너리로 출력한 결과를 보여주시면 좋겠네요. %#x 로 찍으면 나오는게 바이너리겠죠?
저도 이 주제에 관심이 좀 많은데 어자피 컴퓨터의 연산 유닛은 똑같은 IEEE 754 규격의 데이터를 돌려줄테고 이것을 핸들링하는 컴파일러에 따라서 구현이 조금씩 다른 것 같네요. 아마 이런 동작은 C90 등에서 따로 정의같은 것을 하지는 않았나 보네요.
박우성 [solgari]   2011-04-05 15:14 X
제가 오차가 생긴다고 이런 것이 아닙니다. 글의 앞부분에 이미 부동소수점의 문제점에 대하여 언급했습니다.

다른 컴파일러를 다 제쳐두고서라도, C++ Builder만 놓고 봤을 때, 왜 n2의 값만 다른가하는 것입니다.

n2가 다른 값이 출력되는 이유라도 있나요? 최소한 n3, n4를 놔두고라도, n1과 n2는 값이 같아야 하지 않나요?

다시한번 말씀드리지만, 부동소수점 정확하지 않습니다. 하지만, 부동소수점 문제를 떠나서, n1과 n2의 값은 같아야 한다는 것이 저의 생각입니다.
정영훈 [allinux]   2011-04-06 10:04 X
gcc에서 컴파일된 파이썬에서 테스트 해봤는데 전부 2 로 나왔습니다.
php나 자바나 c#이나 어떤 c컴파일러로 결과를 냈느냐에 따라서 결과가 다를 수 있지 않을까 생각해 봅니다.
정영훈 [allinux]   2011-04-06 10:15 X
모든 컴파일러(인터프리터포함)를 사용해서 테스트는 못해봤지만 메이저 언어들은 보정(?)을 한다고 여겨야 하지 않을까 싶습니다.

많은 경우 인터프리터들은 플랫폼이 posix 인 경우는 gcc로 win32/64인 경우 vc로 빌드를 하기 때문에 기본적인 속성을 물려받지 않을까 싶습니다.
박우성 [solgari]   2011-04-06 10:36 X
정영훈님.. 테스트 하신 것 감사드리구요.

부동소수점만의 문제는 아니었으며, 부동소수점의 문제에 C++Builder만의 옵티마이즈 기능 때문에 그런 것이었습니다.

자세한 내용은, 자세한 분석을 해 주신 임프님 글을 참조해 주시기 바랍니다.

http://www.borlandforum.com/impboard/impboard.dll?action=read&db=free&no=19361
나그네 [kdaek]   2014-08-05 09:45 X
자바 에서의 결과 입니다. 229.99999999999997
참고로 자바에서 1.2 * 3 하면 3.5999999999999996 이 나옵니다.

C#에서는 1.2 * 3 하면 3.6으로 올바르게 나오더군요.. 소수로 계산하지 않고 정수로 계산하고 가릿수 맟춰서 소수점을 찍는듯합니다.
참고로 C/C++/Java는 값형(int, double 등)은 단순한 값형이지만.. 닷넷(C#, VB.NET)은 좀 다릅니다. 자바를 벤치마킹해서 그런지 C/C++ 에서의 구조체 변수가 닷넷에서는 경량 객체가 됩니다.(객체 취급 받는 변수로 객체의 일부 기능은 사용 불가) 즉 구조체가 메소드를 가질 수 있는데.. String 클래스처럼 자동으로 숫자를 처리하는게 있는듯 합니다. int나 double 등도 각각에 매칭되는 구조체가 있는지 점 찍으면 필드명이나 메서드가 나옵니다.
나그네 [kdaek]   2014-08-05 10:50 X
닷넷 (C#)에서의 2.3 * 100 결과 입니다.. 제 예상대로 230이라고 정상적으로 나오네요..
나그네 [kdaek]   2014-08-12 13:13 X
VC++ 2008 에서 해보니.. 230.0000 이라고 잘 나오는거 같네요.. 프로젝트를 만들때 설정차인지.. 컴파일 속성 차이인지 모르겠네요..
전 그냥 이렇게 했습니다.

printf("%f", 2.3 * 100);

언뜻 2.5 * 100이나 2.6 * 100으로 해봤던거 같기도 하고..
나그네 [kdaek]   2014-08-12 13:50 X
다시 해보니.. 아래 3가지의 결과에 대애 다음과 같이 나오네요..
테스트는 자바로 했습니다. (C#에서도 C++ 방식 출력을 사용 할 수 있으면 동일할 듯 합니다)
        System.out.printf("%f\n", 2.3 * 100);
        System.out.println(2.3 * 100);
        System.out.printf("%40.39f\n", 2.3 *100);
        System.out.printtln(new DecimalFormat().format(2.3 * 100));
이 각각
230.000000
229.99999999999997
229.999999999999970000000000000000000000000
230

따로 자릿수를 지정해주지 않으면 결과가 올바르게 나오고 자릿수를 지정하는 순간 229.999999999 로 나오네요..

+ -

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