2014-11-01 4 views
1

현재 부동 소수점 표현에 대해 자세히 배우려고 노력 중이므로 조금만 놀았습니다. 그렇게하는 동안 나는 이상한 행동을 발견했다. 무슨 일이 일어나고 있는지 정말 잘 모르겠습니다. 저는 통찰력에 대해 매우 감사 할 것입니다. 죄송합니다 답변이 있다면, 나는 구글에 꽤 어려워!캐스트 플롯을 0으로 비교할 때 이상한 동작이 발생했습니다.

#include <iostream> 
#include <cmath> 
using namespace std; 

int main(){ 

  float minVal = pow(2,-149); // set to smallest float possible 
   
  float nextCheck = static_cast<float>(minVal/2.0f); // divide by two 
  bool isZero = (static_cast<float>(minVal/2.0f) == 0.0f); // this evaluates to false 
  bool isZero2 = (nextCheck == 0.0f); // this evaluates to true 
  cout << nextCheck << " " << isZero << " " << isZero2 << endl; 
  // this outputs 0 0 1 
   
  return 0; 

} 

은 본질적으로 어떤 일어나고있는 것은 :

  • 내가 공을 양보해야 단 정밀도
  • 나누 2로를 사용하여 표현 될 수있는 가장 작은 플로트로 MINVAL를 설정 - 우리는에있어 최소
  • 실제로 isZero2는 true를 반환하지만 isZero는 false를 반환합니다.

무슨 일이 벌어지고있는 것입니까? 나는 그것들이 동일하다고 생각했을 것입니다. 컴파일러는 어떤 숫자를 나눌 경우 0이 될 수 없다고 말하는가?

도움 주셔서 감사합니다.

+1

'pow (2, -149)'가 가능한 가장 작은 플로트를 반환하는지 어떻게 확인합니까? 계산 부정확성 문제가 많이 있습니다. – deepmax

+0

작동하는 것 같습니다. 이진 표현은 실제로 가능한 가장 작은 float 인 1을 반환합니다. Sign = 0; 지수 = 0; Mantissa = 1이므로 2^(- 23) * 2^(- 126) = 2^(- 149) – noctilux

+0

g ++ 4.8.2에서 예상되는 결과를 얻습니다. '0 1 1'. –

답변

5

이유는 isZeroisZero2은 다른 값으로 평가할 수 있으며 isZero은 false 일 수 있습니다. C++ 컴파일러는 표현식의 유형보다 더 정확한 중간 부동 소수점 연산을 구현할 수 있지만 추가 할당시 정밀도를 떨어 뜨려야합니다.

일반적으로 387 히스토리 FPU에 대한 코드를 생성 할 때 생성 된 명령어는 80 비트 확장 정밀도 유형에서 작동하거나 FPU가 53 비트 유효 숫자 (예 : Windows)로 설정된 경우 53 비트의 significand와 15 비트의 지수로 구성된 이상한 부동 소수점 타입.

어느 쪽이든 minVal/2.0f은 지수 범위에서 표현할 수 있기 때문에 정확하게 평가되지만 nextCheck에 할당하면 0으로 반올림됩니다.

GCC를 사용하는 경우 -fexcess-precision=standard이 C++ 프런트 엔드 용으로 아직 구현되지 않았기 때문에 g ++에서 생성 된 코드가 표준에서 권장하는 코드를 정확히 구현하지 못한다는 추가적인 문제가 있습니다.

+0

고맙습니다. 지금 거의 이해하고있는 것 같습니다. 그냥 명확히하기 위해 : 내 Windows 컴파일러 (GCC)에서 -fexcess-precision = 표준이 구현되지 않았으므로 플로트로 캐스팅하더라도 이상한 내부 53 비트 표현을 사용하고있을 수 있습니다.반면 우분투의 g ++에서는 캐스팅을 수행 한 다음 예상대로 float로 처리합니다. 그게 맞습니까? – noctilux

+1

@noctilux 우분투에서는 SSE2 부동 소수점 명령어를 사용하고 있으며 예상 정확도로 예상 결과를 생성하는 'minVal/2.0f'에 대한 단 정밀도 나누기를 생성합니다. GCC에'-S' 옵션을 사용하여 읽기 가능한 형태로 어셈블리 코드를 생성함으로써 문제를 해결할 수 있습니다. 'f'로 시작하는 명령어는 역사적인 387 명령어입니다. 'divss'는 컴파일러가 SSE2 코드를 생성하는 경우 다른 쪽에서 볼 수있는 명령어입니다. –

+0

파스칼, 당신은 절대적으로 맞습니다. 매우 통찰력있는 답변에 감사드립니다. – noctilux

관련 문제