assert의 버그?

프로그래밍 일반에 관한 포럼입니다.

Moderator: 류광

Locked
과객

assert의 버그?

Post by 과객 »

Code: Select all

#include <iostream>
#include <crtdbg.h>
#include <assert.h>
using namespace std;

inline float Sqrt( float x )
{
	return sqrtf(x);
}


void main()
{
	float a = Sqrt( 10 );
	float b = Sqrt( 10 );

	if ( a == b )
	{
		cout << "same!" << endl;
	}

	assert( a == b );
	_ASSERTE( a == b );
	

	assert( Sqrt(10) == Sqrt(10) );		// assert 실패! (???)
	_ASSERTE( Sqrt(10) == Sqrt(10) );	// assert 실패! (???)
}
이 코드 한번 돌려보세요..
왜.. assert와 _ASSERTE가 실패하는지 모르겠네요...
Guest

Post by Guest »

float 값은 디버그 창에선 같게 보여도 실제론
다를 수 있습니다. float 값 비교에는
어느 정도의 오류를 허용하는 식으로 비교해야
원하시는 결과를 얻으 실 수 있으실 것 같습니다.
과객

흠.. 그런문제가 아닌것 같은데요.

Post by 과객 »

값은 동일합니다. 코드에 나와있잖아요.
아마 다른 문제일껍니다.
Gamza
Posts: 610
Joined: 2001-10-11 09:00
Contact:

Post by Gamza »

assert 문제는 아니군요.

Code: Select all

#include <math.h>
#include <stdio.h>
void main() 
{ 
   float a = sqrtf( 10 ); 
   float b = sqrtf( 10 ); 

   if ( a == b ) 
   { 
      printf( "same!\n" );
   } 

   if ( a == sqrtf(10) ) 
   {
      printf( "same!\n" );
   }
} 
무슨 문제인지 두번째 비교문이 실패하네요
과객

흠.. assert문제가 아니었군요..

Post by 과객 »

어쨌든 상식적이지 않네요.
무슨 문제인지 모르겠네요.
ljh131
Posts: 131
Joined: 2002-10-03 16:07

음;;

Post by ljh131 »

dev-c++에선

Code: Select all

   float a = sqrt(10);

   if(a == sqrt(10))
      cout << "a == sqrt(10)";
      
   if(a == float(sqrt(10)))
      cout << "a == float(sqrt(10))";
      
   if(double(a) == (sqrt(10)))
      cout << "double(a) == sqrt(10)";
출력결과는

Code: Select all

a == float(sqrt(10))
입니다

근데 vc에선 아무것도 안나오네요...
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

형변환에 관련된 것 같아요

Post by 류광 »

다음처럼 double로 하면 두 if 모두 참이 됩니다.

Code: Select all

#include <math.h> 
#include <stdio.h> 
#include <stdlib.h>
int main() 
{ 
   double a = sqrtf( 10 ); 
   double b = sqrtf( 10 ); 

   //float a = sqrtf( 10 ); 
   //float b = sqrtf( 10 ); 


   if ( a == b ) 
   { 
      printf( "a == b\n" ); 
   } 

   if ( a == sqrt(10) ) 
   { 
      printf( "a == sqrt(10)\n" ); 
   } 
   system("PAUSE");
   return 0;
} 
sqrtf()가 이름과는 달리 double을 돌려주는 것 같습니다. float a = sqrtf( 10 ); 에서 a에는 뒤가 잘린 값이 들어가고, 그래서 a == sqrtf(10)은 실패가 되는거죠...

sqrtf()의 f가 자료형 float을 의미하는 게 아니라 그냥 일반적인 의미로서의 부동소수점을 뜻하는 것 같네요(즉 정수가 아님을 강조하는 뜻으로).

실제로 gcc의 경우에는 sqrtf()가 아예 없구요. sqrt는 템플릿 함수이고 매개변수에 따라 적절한 버전이 호출되는데, float과 double 모두 double 버전이 호출됩니다.

ps. 엇 동시 답글이네요.
soaringhs
Posts: 230
Joined: 2001-07-30 09:00

Post by soaringhs »

vc6++(sp5,platformsdk2001aug)에서 테스트 해봤는데 type casting에 문제가 있는듯...
3.16227769851684570000e+000 f
3.16227769851684570000e+000 (double)f
3.16227766016837950000e+000 d
3.16227766016837950000e+000 (float)d
3.16227766016837950000e+000 sqrtf(10)
3.16227766016837950000e+000 sqrt(10)

Code: Select all

#include <math.h>
#include <stdio.h>
#include <assert.h>

#define PRINT(e)	printf("%.20e %s\n", (e), #e);

int main(int argc, char* argv[])
{
   float  f = sqrt( 10 );
   double d = sqrt( 10 );

   PRINT(f);
   PRINT((double)f);
   PRINT(d);
   PRINT((float)d);
   PRINT(sqrtf(10));
   PRINT(sqrt(10));

   return 0;
}
kwanny
Posts: 50
Joined: 2002-06-01 23:18
Location: Ntreev Soft
Contact:

VC6++의 컴파일 옵티마이징 옵션중에...

Post by kwanny »

VC6++의 컴파일 옵티마이징 옵션중에 /Op (Improve Float Consistency)를 켜보시면 원하시는 결과를 얻을 수 있을 겁니다.

알고 있어도 자주 알 수 없는 버그의 원인이 되곤 하는 문제죠.


위와 같은 옵션은 속도와 실행 파일 크기가 문제가 되기 때문에 아래와 같이 오차 한계를 두고 처리 하는 방법이 제일 속편합니다. (물론 float로 ==, != 오퍼레이터의 결과가 안정적으로 나오길 바랄 때만 사용 하시면 될듯...)

Code: Select all

#include <math.h>
#include <stdio.h>

#define EPSILON 0.0001   // Define your own tolerance
#define FLOAT_EQ(x,v) (((v - EPSILON) < x) && (x <( v + EPSILON)))


int main(int argc, char* argv[])
{
	float a = sqrtf( 10 ); 
	float b = sqrtf( 10 ); 

	if ( a == sqrtf(10) ) 
		printf( "same a==sqrtf(10)\n" ); 

	if ( a == b ) 
		printf( "same a==b\n" ); 

	if ( FLOAT_EQ(a, sqrtf(10)) )
		printf("same FLOAT_EQ(a, sqrtf(10))\n");

	return 0;
}

자세한 내용은 MSDN의 "색인(N)"메뉴에서 "/Op"를 쳐보시면 아주 친절하게 잘 나와 있습니다. (물론 영문으로 나와 있기 때문에 전부 읽어 보고 싶지는 않더군요.^^;)

MSDN에서 몇줄 가져와 보면...(해석은 알아서...한 마디로 "별로 좋지 않으니 피하자"라고 이해 하고 있습니다.)
The Improve Float Consistency (/Op[–]) option improves the consistency of floating-point tests for equality and inequality by disabling optimizations that could change the precision of floating-point calculations. (To find this option in the development environment, click Settings on the Project menu. Then click the C/C++ tab, and click Optimizations in the Category box. Under Optimizations, click Customize.)

By default, the compiler uses the coprocessor’s 80-bit registers to hold the intermediate results of floating-point calculations. This increases program speed and decreases program size. However, because the calculation involves floating-point data types that are represented in memory by less than 80 bits, carrying the extra bits of precision (80 bits minus the number of bits in a smaller floating-point type) through a lengthy calculation can produce inconsistent results.

With /Op, the compiler loads data from memory prior to each floating-point operation and, if assignment occurs, writes the results back to memory upon completion. Loading the data prior to each operation guarantees that the data does not retain any significance greater than the capacity of its type.
류광
Posts: 3805
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

앞에서
sqrtf()가 이름과는 달리 double을 돌려주는 것 같습니다
라고 했는데 아니었습니다. VC++에서 sqrtf()는 항상 float을 돌려주네요.

Code: Select all

#include <cmath>
#include <iostream>
#include <cstdlib>

void f1( float )
{
    std::cout << "\nf1_float";
}
void f1( double )
{
    std::cout << "\nf1_double";
}
void f1( long double )
{
    std::cout << "\nf1_longdouble";
}
 
int main()
{
    f1( sqrtf(123.4f) );
    f1( sqrtf(123.4) );
    f1( sqrtf(123.4L) );
    f1( sqrtf(123) );

	return 0;
}
출력은 모두 f1_float 입니다.

따라서 C++ 언어 차원의 문제는 아니고 kwanny, makefile 님이 언급한 것처럼 최적화와 부동소수점 처리에 관련된 문제 같습니다.
Zho
Posts: 328
Joined: 2001-08-02 09:00
Location: 취미게임개발자
Contact:

Re: VC6++의 컴파일 옵티마이징 옵션중에...

Post by Zho »

위의 kwanny님의 말씀이 맞습니다. 부연 설명하자면,

sqrtf()와 같은 실수 연산에는 내부적으로 FPU 레지스터 스택이 사용되는데, 이 레지스터는 80비트짜리로, float 형(32비트)보다 훨씬 더 정밀한 값을 가집니다. 그리고, 대입이나 비교 등이 필요할 때에는 이 FPU 스택의 값을 메모리로 읽어들이거나 메모리의 값과 비교하게 되죠.

비주얼씨에는 sqrtf()가 다음과 같은 인라인 함수로 되어 있는데

inline float sqrtf(float _X) {return ((float)sqrt((double)_X)); }

/Op를 사용하지 않고(디폴트) 컴파일하면, sqrtf() 함수에서는 sqrt()의 결과를 메모리(float)로 옮기기는 하지만, 계산된 값이 들어있는 80비트짜리 FPU 레지스터 스택의 내용을 건드리지 않고 그대로 남겨 놓습니다. 그리고, 리턴한 다음, 비교식에서는 FPU 스택의 값과 직접 비교하게 됩니다. 즉, 32비트짜리를 80비트짜리와 비교하는 셈이죠. 따라서, 원치 않는 결과를 초래할 수 있습니다.

하지만, /Op를 사용한다면, sqrtf()가 리턴하기 전에 FPU 스택의 값을 메모리로 옮기면서, FPU 스택의 값을 제거한 다음, 다시 메모리의 값을 스택에 집어 넣습니다. 즉, 80비트짜리를 32비트로 변환한 다음 그 값을 다시 80비트로 저장하는 셈이죠. 따라서, 비교식에서는 실질적으로는 32비트짜리들을 비교하는 셈이 됩니다.
Locked