2010-02-11 6 views
46

저는 최근에 IEEE 754와 x87 아키텍처에서 꽤 많이 읽었습니다. 내가 노력하고있는 일부 수치 계산 코드에서 "누락 된 값"으로 NaN을 사용하려고 생각하고 있었고 신호로 NaN을 사용하면 내가 아닌 곳에서 부동 소수점 예외를 잡을 수 있었으면 좋겠다. "누락 된 값"으로 진행하고 싶습니다. 반대로, 을 사용하면 "누락 된 값"이 계산을 통해 전파되도록 허용하기 위해 NaN을 사용합니다. 그러나 시그널링 NaN은 그들이 존재하는 (매우 제한적인) 문서를 기반으로한다고 생각했기 때문에 작동하지 않습니다. 여기 신호 NaN의 유용성?

내가 알고있는 것을 요약 한 것입니다 (이 사용하는 x87 및 VC++의 모든) :

  • _EM_INVALID합니다 (IEEE "무효"예외) 경우 NaN을
  • 가 발생 x87의 동작을 제어 _EM_INVALID는 마스크 처리됩니다 (예외는 무효). 예외는 생성되지 않고, 오퍼레이션은 조용한 NaN를 돌려 줄 수가 있습니다. NaN 시그널을 포함하는 조작은 이 아니고,가 예외를 Throw하지만, NaN에 변환됩니다.
  • _EM_INVALID가 마스크 해제 된 경우 (예외 사용) 잘못된 조작 (예 : sqrt (-1))으로 인해 잘못된 예외가 발생합니다.
  • x87 결코은 신호 NaN을 생성합니다.
  • _EM_INVALID가 마스크 해제 된 경우 신호 NaN을 사용하면 (심지어 변수를 초기화해도) 잘못된 예외가 발생합니다.

    std::numeric_limits<double>::signaling_NaN(); 
    

    std::numeric_limits<double>::quiet_NaN(); 
    

    문제는 내가 신호 NaN이에 대해 어떠한 사용을 볼 수 없다는 것입니다 :

는 표준 라이브러리는 NaN의 값에 액세스 할 수있는 방법을 제공합니다. _EM_INVALID가 마스크되어 있으면 조용한 NaN과 똑같이 동작합니다. NaN은 다른 NaN과 비교할 수 없기 때문에 논리적 인 차이가 없습니다.

_EM_INVALID 인 경우 하지 마스크 (예외가 활성화) 후 하나도 시그널링 NaN를 가진 변수를 초기화 할 수있다 :이 (시그널링 NaN의 가치가 x87에로드를 저장하는 레지스터는 예외가 발생하기 때문에 double dVal = std::numeric_limits<double>::signaling_NaN();을 메모리 주소로). 내가 그랬던 것처럼

는 다음을 생각할 수 있습니다 :

  1. 마스크 _EM_INVALID을.
  2. 신호 NaN을 사용하여 변수를 초기화하십시오.
  3. Unmask_EM_INVALID.

그러나, 2 단계는 너무 연속적으로 사용하기가 하지 원인 예외가 던져 질 것이라고 신호 NaN의 조용한 NaN이로 변환됩니다! 그래서 WTF?!

신호용 NaN에는 어떤 유틸리티 나 목적이 있습니까? 원래의 의도 중 하나는 단위 화 된 부동 소수점 값의 사용을 포착 할 수 있도록 메모리를 초기화하는 것이라는 것을 알고 있습니다.

내가 여기에 뭔가 빠졌는지 누군가가 말해 줄 수 있습니까?


는 편집 :

데이터 (복식)의 벡터에서 수학 연산을 수행하는 것이 좋습니다 :

더 내가 할 희망이 있었는지 설명하기 위해, 여기에 예입니다. 일부 작업의 경우 벡터에 "누락 된 값"(예 : 스프레드 시트 열에 해당하는 것처럼 보이지만 일부 셀에는 값이 없지만 그 존재는 중요 함)이 포함될 수 있습니다. 일부 작업의 경우 이 아니며은 벡터에 "누락 값"이 포함되도록 허용하려고합니다. 아마 "실종 된 가치"가 집합에 존재한다면 아마도 다른 행동을 취하고 싶을 것입니다. (아마도 이것은 잘못된 상태가 아닙니다.)

이 원래의 코드는 다음과 같이 보일 것입니다 :

const double MISSING_VALUE = 1.3579246e123; 
using std::vector; 

vector<double> missingAllowed(1000000, MISSING_VALUE); 
vector<double> missingNotAllowed(1000000, MISSING_VALUE); 

// ... populate missingAllowed and missingNotAllowed with (user) data... 

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) { 
    if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation 
} 

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) { 
    if (*it != MISSING_VALUE) *it = sqrt(*it); 
    else *it = 0; 
} 

주를 "없는 가치"에 대한 검사가 마다 루프 반복 수행되어야합니다. 대부분의 경우, sqrt 함수 (또는 다른 수학 연산)가이 수표를 가릴 가능성이 있음을 알고 있지만, 연산이 최소 (아마도 그냥 추가)이며 수표가 비싸다고하는 경우가 있습니다. "누락 된 값"이 합법적 인 입력 값을 사용하지 않고 계산이 합법적으로 해당 값에 도달하면 버그가 발생할 수 있음은 물론입니다 (가능성은 거의 없음). 또한 기술적으로 정확하려면 사용자 입력 데이터를 해당 값과 비교하여 적절한 조치 과정을 거쳐야합니다. 이 솔루션은 성능이 좋지 않고 최적화되지 않은 솔루션입니다. 이것은 성능에 중요한 코드이며, 병렬 데이터 구조 나 데이터 요소 객체의 고급 스러움이 없습니다.

NaN이 버전은 다음과 같을 것이다 :

using std::vector; 

vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN()); 
vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN()); 

// ... populate missingAllowed and missingNotAllowed with (user) data... 

for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) { 
    *it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN 
} 

for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) { 
    try { 
     *it = sqrt(*it); 
    } catch (FPInvalidException&) { // assuming _seh_translator set up 
     *it = 0; 
    } 
} 

이제 명시 적 검사가 제거되고 성능이 향상되어야한다. FPU 레지스터를 건드리지 않고 벡터를 초기화 할 수 있다면이 모든 것이 효과가 있다고 생각합니다.

또한 자발적인 sqrt 구현이 NaN을 검사하고 NaN을 즉시 반환한다고 생각합니다.

+6

좋은 질문을하면 포인터 캐스팅을 통해 원하는 위치 당신은 그것을 쓸 수 있습니다. 불행히도 지금까지 NaN 신호를 보았던 유일한 용도는 토요일 오후 9시 30 분에 휴대 전화로 전화를 걸 수 있습니다. –

답변

8

I 알고있는 것처럼, NaN이 시그널링의 목적은 C에서 초기화하여 트리거링은 NaN이 초기화의 일부로서 부동 소수점 레지스터에로드 갖는 위험을 실행 과정 런타임, 데이터 구조를 초기화하는 것이지만 왜냐하면 컴파일러는이 float 값을 정수 레지스터를 사용하여 복사해야한다는 것을 알지 못하기 때문입니다.

신호 NaN을 사용하여 static 값을 초기화 할 수도 있지만 컴파일러가 조용한 NaN으로 변환하지 않도록 특별한 처리가 필요하기를 바랍니다. 초기화하는 동안 플로팅 값으로 처리하지 않으려면 캐스팅 마법을 약간 사용할 수 있습니다.

ASM으로 작성하는 경우 문제가되지 않습니다. 하지만 C와 특히 C++에서는 NaN으로 변수를 초기화하기 위해 타입 시스템을 파괴해야한다고 생각합니다. memcpy을 사용하는 것이 좋습니다.

+1

예, 저는 이것이 합리적인 가정 일 수 있다고 생각합니다. 언어의 일부분 일 필요가 있다고 생각합니다. 라이브러리의 일부가 아닌, 언어로되어 있어야합니다. –

1

특수 값 (심지어 NULL)을 사용하면 데이터가 훨씬 더 진해지고 코드가 훨씬 복잡해질 수 있습니다. QNaN 결과와 QNaN "특수"값을 구별하는 것은 불가능합니다.

유효성을 추적하기 위해 병렬 데이터 구조를 유지하는 것이 더 좋을 수 있습니다. 또는 유효한 데이터 만 유지하기 위해 다른 (희소) 데이터 구조의 FP 데이터를 사용하는 것이 더 좋을 수 있습니다.

이것은 매우 일반적인 조언입니다. 특정 값은 특정 상황에서 매우 유용합니다 (예 : 메모리 또는 성능 제약 조건이 매우 빡빡 할 때). 컨텍스트가 커질수록 더 큰 어려움을 겪을 수 있습니다.

+0

그래도 좋은 질문입니다. 나는이 대답을 경험이 적은 여행자에게 겸손한 충고로만 제안합니다. "그건 깔끔한 트릭이 될 것입니다!" :-) –

+1

Gotcha. 분명히 내가 본 적이없는 코드에 대해서는 논평하기가 어렵고, 당신이 변경하지 말라고 말하는 큰 추악한 코드베이스에서 어떻게 작동 하는지를 쓰라린 경험에서 알지만, 논리를 분리하려고 시도 할 기회가 주어진다. 함수에서 값을 확인하십시오 (매크로를 체크해도 인라인되지 않은 함수 호출이 디버그 빌드를 느리게하는 지 확인하십시오). 다른 방법으로는 더 명확 해지며, 더 나은 해결책을 찾으면 기꺼이 재구성 할 수있는 기회를 제공합니다. –

+0

좋아, 이제 훨씬 더 의미가있다. 귀하의 예에서는 QNaN을 사용하고 테스트를 피하는 것이 훨씬 더 SIMD가되고 캐시가 친숙합니다. 원래의 대답에서 말했듯이 타이트한 메모리/성능 제약은 특별한 값을 사용하지 않는 일반적인 규칙의 예외입니다 .-) 감사합니다. STingRaySC. 나는 당신의 문제에 대한 답을 갖고 있지 않아서 미안합니다. –

1

비트가 시그널 nan의 비트로 설정된 const uint64_t를 사용할 수 없습니까? 정수형으로 취급하는 한, 신호 nan은 다른 정수와 다르지 않습니다.

Const uint64_t sNan = 0xfff0000000000000; 
Double[] myData; 
... 
Uint64* copier = (uint64_t*) &myData[index]; 
*copier=sNan | myErrorFlags; 

를 설정하는 비트에 대한 정보를 들면 다음과 같습니다 : https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html