문제

IEEE float와 double이 동일한지 비교하는 가장 좋은 방법은 무엇입니까?여러 가지 방법에 대해 들어봤지만 커뮤니티에서는 어떻게 생각하는지 알고 싶었습니다.

도움이 되었습니까?

해결책

제가 생각하는 가장 좋은 방법은 비교하는 것입니다. ULP.

bool is_nan(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}

bool is_finite(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1

// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
    if(is_nan(lhs) || is_nan(rhs))
    {
        return false;
    }
#endif
#ifdef INFINITE_INFINITIES
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
    {
        return false;
    }
#endif
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(left < 0)
    {
        left = 0x80000000 - left;
    }
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(right < 0)
    {
        right = 0x80000000 - right;
    }
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
    {
        return true;
    }
    return false;
}

비슷한 기술을 복식에도 사용할 수 있습니다.비결은 부동 소수점을 변환하여 (정수처럼) 정렬된 다음 얼마나 다른지 확인하는 것입니다.

왜 이 빌어먹을 일이 내 밑줄을 망치고 있는지 전혀 모르겠습니다.편집하다:아, 어쩌면 그것은 단지 예고편의 인공물일지도 모릅니다.그렇다면 괜찮습니다.

다른 팁

현재 제가 사용하고 있는 버전은 이것입니다

bool is_equals(float A, float B,
               float maxRelativeError, float maxAbsoluteError)
{

  if (fabs(A - B) < maxAbsoluteError)
    return true;

  float relativeError;
  if (fabs(B) > fabs(A))
    relativeError = fabs((A - B) / B);
  else
    relativeError = fabs((A - B) / A);

  if (relativeError <= maxRelativeError)
    return true;

  return false;
}

이는 상대 및 절대 오류 허용 오차를 결합하여 대부분의 문제를 해결하는 것으로 보입니다.ULP 접근 방식이 더 나은가요?그렇다면 왜 그렇습니까?

@DrPizza:나는 성능 전문가는 아니지만 (대부분의 경우) 고정 소수점 연산이 부동 소수점 연산보다 더 빠를 것으로 기대합니다.

오히려 당신이 그들과 함께 무엇을 하고 있는지에 달려 있습니다.IEEE float와 동일한 범위를 가진 고정 소수점 유형은 몇 배 더 느리고 몇 배 더 큽니다.

수레에 적합한 것들:

3D 그래픽, 물리/공학, 시뮬레이션, 기후 시뮬레이션....

수치 소프트웨어에서는 두 개의 부동 소수점 숫자가 서로 일치하는지 테스트하려는 경우가 많습니다. 정확히 동일한.LAPACK에는 그러한 사례가 가득합니다.물론, 가장 일반적인 경우는 부동 소수점 숫자가 "0", "1", "2", "반"과 같은지 테스트하려는 경우입니다.누군가 관심이 있다면 몇 가지 알고리즘을 선택하여 더 자세히 설명할 수 있습니다.

또한 BLAS에서는 부동 소수점 숫자가 정확히 0인지 1인지 확인하려는 경우가 많습니다.예를 들어, dgemv 루틴은 다음 형식의 연산을 계산할 수 있습니다.

  • y = 베타*y + 알파*A*x
  • y = 베타*y + 알파*A^T*x
  • y = 베타*y + 알파*A^H*x

따라서 베타가 1이면 "플러스 할당"이 되고 베타가 0이면 "단순 할당"이 됩니다.따라서 이러한 (일반적인) 사례를 특별하게 처리하면 계산 비용을 확실히 줄일 수 있습니다.

물론, 정확한 비교를 피할 수 있는 방식으로 BLAS 루틴을 설계할 수 있습니다(예:일부 플래그 사용).그러나 LAPACK에는 이것이 불가능한 사례가 가득합니다.

추신.:

  • "정확히 같음"을 확인하고 싶지 않은 경우가 확실히 많습니다.많은 사람들에게 이것은 심지어 그들이 처리해야 하는 유일한 경우일 수도 있습니다.제가 지적하고 싶은 것은 다른 경우도 있다는 것입니다.

  • LAPACK은 Fortran으로 작성되었지만 수치 소프트웨어에 다른 프로그래밍 언어를 사용하는 경우 논리는 동일합니다.

맙소사, P6 이하 버전에서 실행하지 않는 이상 부동 소수점 비트를 정수로 해석하지 마세요.

메모리를 통해 벡터 레지스터에서 정수 레지스터로 복사하게 되더라도 파이프라인이 중단되더라도 표면적으로 가장 강력한 비교를 제공한다는 점에서 내가 본 것 중 가장 좋은 방법입니다. 부동 소수점 오류.

즉.그것은 지불할 가치가 있는 가격이다.

이는 상대 및 절대 오류 허용 오차를 결합하여 대부분의 문제를 해결하는 것으로 보입니다.ULP 접근 방식이 더 나은가요?그렇다면 왜 그렇습니까?

ULP는 두 부동 소수점 숫자 사이의 "거리"를 직접적으로 측정한 것입니다.즉, 상대 및 절대 오류 값을 떠올릴 필요가 없으며 해당 값을 "올바른" 값으로 얻을 필요도 없습니다.ULP를 사용하면 숫자가 얼마나 가까워지기를 원하는지 직접 표현할 수 있으며, 동일한 임계값은 큰 값과 작은 값 모두에 대해 잘 작동합니다.

부동 소수점 오류가 있으면 이보다 더 많은 문제가 있습니다.개인적인 관점에 달려 있다고 생각하지만.

오류 누적을 최소화하기 위해 수치 분석을 수행하더라도 이를 제거할 수 없으며 (실수로 계산한 경우) 동일해야 하지만 (실수로 계산할 수 없기 때문에) 다른 결과가 남을 수 있습니다.

두 개의 수레가 동일해야 한다고 생각한다면 두 수레는 동일해야 한다고 생각합니다.부동 소수점 반올림 문제에 직면한 경우 고정 소수점 표현이 문제에 더 적합할 수 있습니다.

두 개의 수레가 동일해야 한다고 생각한다면 두 수레는 동일해야 한다고 생각합니다.부동 소수점 반올림 문제에 직면한 경우 고정 소수점 표현이 문제에 더 적합할 수 있습니다.

아마도 우리는 그러한 접근 방식으로 인해 발생할 수 있는 범위나 성능의 손실을 감당할 수 없을 것입니다.

@DrPizza:나는 성능 전문가는 아니지만 (대부분의 경우) 고정 소수점 연산이 부동 소수점 연산보다 더 빠를 것으로 기대합니다.

@크레이그 H:확신하는.나는 그것을 인쇄하는 데 완전히 괜찮습니다.a 또는 b가 돈을 저장하는 경우 고정 소수점으로 표시되어야 합니다.나는 그러한 논리가 부동 소수점과 결합되어야 하는 실제 사례를 생각하는 데 어려움을 겪고 있습니다.수레에 적합한 것들:

  • 가중치
  • 계급
  • 거리
  • 실제 값(ADC와 같은)

이러한 모든 것에 대해 숫자를 매기고 인간의 해석을 위해 결과를 사용자에게 단순히 제시하거나 비교 진술을 합니다(이러한 진술이 "이것은 다른 것의 0.001 내에 있습니다"라고 하더라도).나와 같은 비교 설명은 알고리즘의 맥락에서만 유용합니다."0.001 이내" 부분은 무엇에 따라 달라집니다. 물리적 당신이 묻는 질문.내 0.02.아니면 2/100이라고 해야 할까요?

오히려 당신이 그들과 함께하는 일에 달려 있습니다.IEEE 플로트와 동일한 범위를 가진 고정점 유형은 여러 배나 느리게 (그리고 몇 배 더 큽니다).

좋습니다. 하지만 아주 작은 비트 해상도를 원한다면 원래 지점으로 돌아갑니다.== 및 !=는 이러한 문제의 맥락에서는 의미가 없습니다.

int를 사용하면 범위에 관계없이 ~10^9개의 값을 표현할 수 있는데, 이는 두 값이 동일해야 하는 모든 상황에 충분해 보입니다.충분하지 않은 경우 64비트 OS를 사용하면 약 10^19개의 고유 값을 얻을 수 있습니다.

예를 들어 int로 0에서 10^200 범위의 값을 표현할 수 있습니다. 문제는 비트 해상도뿐입니다(해상도는 1보다 크지만, 다시 말하지만, 그런 종류의 범위를 갖는 응용 프로그램도 없습니다). 그런 종류의 해상도로).

요약하자면, 모든 경우에 하나는 != 및 ==가 관련이 없는 값의 연속체를 나타내거나, 하나는 int(또는 다른 고정 값)에 매핑될 수 있는 고정된 값 집합을 나타내는 것이라고 생각합니다. -정밀형).

int를 사용하면 ~ 10^9 값 (범위에 관계없이)을 표현할 수 있습니다.그리고 충분하지 않은 경우 64 비트 OS를 사용하면 약 10^19 개의 별개의 값이 있습니다.

사실 한계에 부딪혔는데...나는 쉽게 10^10 사이클에 도달하는 시뮬레이션에서 ps 단위의 시간과 클럭 사이클의 시간을 저글링하려고 했습니다.내가 무엇을 하든 64비트 정수의 보잘것없는 범위가 매우 빠르게 오버플로되었습니다...10^19는 당신이 생각하는 것만큼 많지 않습니다. 이제 128비트 컴퓨팅을 해보세요!

Floats를 사용하면 값이 낮은 쪽 끝에서 0으로 넘쳐나기 때문에 수학적 문제에 대한 해결책을 얻을 수 있었습니다.따라서 기본적으로 정밀도 손실 없이 숫자에 부동 소수점을 가졌습니다(64비트 int와 비교하여 float의 가수에 허용되는 더 제한된 고유한 값 수를 원할 수 있지만 절실히 범위가 필요했습니다!) ).

그런 다음 비교를 위해 다시 정수로 변환되었습니다.

짜증나서 결국 전체 시도를 취소하고 플로트와 < 및 >에만 의존하여 작업을 완료했습니다.완벽하지는 않지만 구상된 사용 사례에 적합합니다.

두 개의 수레가 동일해야 한다고 생각한다면 두 수레는 동일해야 한다고 생각합니다.부동 소수점 반올림 문제에 직면한 경우 고정 소수점 표현이 문제에 더 적합할 수 있습니다.

아마도 문제를 더 잘 설명해야 할 것 같습니다.C++에서 다음 코드는 다음과 같습니다.

#include <iostream>

using namespace std;


int main()
{
  float a = 1.0;
  float b = 0.0;

  for(int i=0;i<10;++i)
  {
    b+=0.1;
  }

  if(a != b)
  {
    cout << "Something is wrong" << endl;
  }

  return 1;
}

"뭔가 잘못되었습니다"라는 문구를 인쇄합니다.그래야 한다고 말하는 건가요?

맙소사, P6 이하 버전에서 실행하지 않는 이상 부동 소수점 비트를 정수로 해석하지 마세요.

부동 소수점 오류에도 불구하고 가장 강력한 비교를 제공하는 한 이것은 내가 본 것 중 가장 좋은 방법입니다.

부동 소수점 오류가 있으면 이보다 더 많은 문제가 있습니다.개인적인 관점에 달려 있다고 생각하지만.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top