development

부동 소수점 비교는 어떻게해야합니까?

big-blog 2020. 10. 27. 22:47
반응형

부동 소수점 비교는 어떻게해야합니까?


현재 다음과 같은 코드를 작성하고 있습니다.

double a = SomeCalculation1();
double b = SomeCalculation2();

if (a < b)
    DoSomething2();
else if (a > b)
    DoSomething3();

그리고 다른 곳에서는 평등을해야 할 수도 있습니다.

double a = SomeCalculation3();
double b = SomeCalculation4();

if (a == 0.0)
   DoSomethingUseful(1 / a);
if (b == 0.0)
   return 0; // or something else here

요컨대, 많은 부동 소수점 수학이 진행되고 있으며 조건에 대한 다양한 비교를 수행해야합니다. 이 문맥에서 그런 것은 의미가 없기 때문에 정수 수학으로 변환 할 수 없습니다.

나는 다음과 같은 일이 계속 될 수 있기 때문에 부동 소수점 비교가 신뢰할 수 없다는 것을 전에 읽었습니다.

double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
    Console.WriteLine("Oh no!");

간단히 말해서 알고 싶습니다. 부동 소수점 숫자 (보다 작음,보다 큼, 같음)를 어떻게 안정적으로 비교할 수 있습니까?

내가 사용하는 숫자 범위는 대략 10E-14에서 10E6까지이므로 작은 숫자와 큰 숫자로 작업해야합니다.

내가 사용하는 언어에 관계없이이를 수행 할 수있는 방법에 관심이 있기 때문에 이것을 언어 불가지론 적 태그로 지정했습니다.


부동 / 배 정밀도 한계의 가장자리에서 바로 작업하지 않는 한 크거나 작은 비교는 실제로 문제가되지 않습니다.

"퍼지 같음"비교의 경우,이 (자바 코드는 쉽게 적용 할 수 있어야 함) 많은 작업과 많은 비판을 고려한 후 부동 소수점 가이드 를 위해 고안 한 것입니다.

public static boolean nearlyEqual(float a, float b, float epsilon) {
    final float absA = Math.abs(a);
    final float absB = Math.abs(b);
    final float diff = Math.abs(a - b);

    if (a == b) { // shortcut, handles infinities
        return true;
    } else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
        // a or b is zero or both are extremely close to it
        // relative error is less meaningful here
        return diff < (epsilon * Float.MIN_NORMAL);
    } else { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}

테스트 스위트와 함께 제공됩니다. 하나의 값 0, 0에 반대되는 두 개의 매우 작은 값 또는 무한대를 갖는 것과 같은 일부 엣지 케이스에서 사실상 실패가 보장되기 때문에 그렇지 않은 솔루션을 즉시 제거해야합니다.

대안 (자세한 내용은 위의 링크 참조)은 float의 비트 패턴을 정수로 변환하고 고정 된 정수 거리 내에서 모든 것을 받아들이는 것입니다.

어쨌든 모든 응용 프로그램에 완벽한 솔루션은 없을 것입니다. 이상적으로는 실제 사용 사례를 다루는 테스트 스위트를 사용하여 직접 개발 / 적응하는 것이 좋습니다.


나는 부동 소수점 숫자를 비교하는 문제를 가지고 A < BA > B여기에 작동하는 것 같다 것입니다 :

if(A - B < Epsilon) && (fabs(A-B) > Epsilon)
{
    printf("A is less than B");
}

if (A - B > Epsilon) && (fabs(A-B) > Epsilon)
{
    printf("A is greater than B");
}

팹 (절대 가치)은 본질적으로 동일한 지 여부를 처리합니다.


TL; DR

  • 현재 허용되는 솔루션 대신 다음 함수를 사용하여 특정 제한 사례에서 바람직하지 않은 결과를 방지하면서 잠재적으로 더 효율적입니다.
  • 숫자에 대해 예상되는 부정확성을 알고 그에 따라 비교 함수에 입력하십시오.
bool nearly_equal(
  float a, float b,
  float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
  // those defaults are arbitrary and could be removed
{
  assert(std::numeric_limits<float>::epsilon() <= epsilon);
  assert(epsilon < 1.f);

  if (a == b) return true;

  auto diff = std::abs(a-b);
  auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
  return diff < std::max(relth, epsilon * norm);
}

그래픽, 제발?

부동 소수점 수를 비교할 때 두 가지 "모드"가 있습니다.

첫 번째는이다 상대적인 차이 모드 x와는 y그 진폭이 상대적으로 간주된다 |x| + |y|. 2D로 플롯하면 다음 프로필이 제공됩니다. 여기서 녹색은 xy. (나는 epsilon예시를 위해 0.5를 취했다 ).

여기에 이미지 설명 입력

상대 모드는 "정상"또는 "충분히 큰"부동 소수점 값에 사용되는 것입니다. (나중에 더 자세히 설명합니다).

두 번째는 절대 모드로, 단순히 그 차이를 고정 된 숫자와 비교할 때입니다. 다음 프로필을 제공합니다 (다시 설명을 위해 epsilon0.5 및 relth1).

여기에 이미지 설명 입력

이 절대 비교 모드는 "작은"부동 소수점 값에 사용됩니다.

이제 문제는 우리가이 두 가지 응답 패턴을 어떻게 연결 하는가입니다.

Michael Borgwardt의 답변에서 스위치는의 값을 기반으로하며 ( 그의 답변에서) diff아래에 있어야합니다 . 이 스위치 영역은 아래 그래프에서 빗금으로 표시됩니다.relthFloat.MIN_NORMAL

여기에 이미지 설명 입력

때문에 relth * epsilon것이 작은 relth녹색 패치가 차례로 솔루션을 나쁜 속성을 제공하는, 단결하지 않는다 : 우리는 숫자 같은 그 세 쌍둥이 찾을 수 있습니다 x < y_1 < y_2아직 x == y2하지만를 x != y1.

여기에 이미지 설명 입력

이 놀라운 예를 들어보십시오.

x  = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32

우리는 x < y1 < y2, 사실 y2 - x보다 2000 배 이상 큽니다 y1 - x. 그러나 현재 솔루션을 사용하면

nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True

대조적으로, 위에서 제안 된 솔루션에서 스위치 영역은 |x| + |y|아래의 빗금 친 사각형으로 표시되는의 값을 기반으로합니다 . 두 영역이 모두 정상적으로 연결되도록합니다.

여기에 이미지 설명 입력

또한 위의 코드에는 분기가 없으므로 더 효율적일 수 있습니다. 그와 같은 작업을 고려 max하고 abs, 사전 종종 전용 조립 지침을 가지고, 필요 분기를. 이러한 이유로이 접근 방식은 nearlyEqual스위치를에서 diff < relth변경하여 Michael의 문제를 해결하는 다른 솔루션보다 우수하다고 생각합니다. diff < eps * relth그러면 기본적으로 동일한 응답 패턴이 생성됩니다.

상대 비교와 절대 비교 사이를 전환 할 위치는 어디입니까?

이러한 모드 사이의 전환 은 허용되는 답변 relth에서와 같이 사용 FLT_MIN됩니다. 이 선택은의 표현이 float32부동 소수점 숫자의 정밀도를 제한 한다는 것을 의미합니다 .

이것은 항상 의미가있는 것은 아닙니다. 예를 들어 비교 한 숫자가 빼기의 결과 인 경우 범위 내의 어떤 FLT_EPSILON것이 더 의미가있을 수 있습니다. 뺀 숫자의 제곱근이면 수치 부정확성이 훨씬 더 높아질 수 있습니다.

부동 소수점을 0. 여기서는 상대 비교가 실패 |x - 0| / (|x| + 0) = 1합니다. 따라서 x계산이 정확하지 않은 순서 일 때 비교는 절대 모드로 전환해야 하며 드물게는 FLT_MIN.

이것이 relth매개 변수를 도입 한 이유입니다 .

또한, 곱하지 않음으로써 relth으로 epsilon,이 매개 변수의 해석은 우리가 그 숫자에 기대하는 수치의 정밀도 수준으로 간단하고 대응이다.

수학적 울림

(대부분 내 즐거움을 위해 여기에 보관)

더 일반적으로 나는 잘 작동하는 부동 소수점 비교 연산자 =~가 몇 가지 기본 속성을 가져야 한다고 가정합니다 .

다음은 다소 분명합니다.

  • 자기 평등 : a =~ a
  • 대칭 : a =~ b의미b =~ a
  • 반대에 의한 불변성 : a =~ b암시-a =~ -b

(우리는이없는 a =~ bb =~ c의미 a =~ c, =~등가 관계 없음).

부동 소수점 비교에 더 구체적인 다음 속성을 추가합니다.

  • 이면 a < b < c다음을 a =~ c의미합니다 a =~ b(가까운 값도 동일해야 함).
  • 만약 a, b, m >= 0다음 a =~ b의미 a + m =~ b + m(동일한 값이 큰 차이는 동일해야한다)
  • 경우 0 <= λ < 1다음 a =~ b을 의미한다 λa =~ λb(아마 덜 분명 인수에 대한).

이러한 속성은 이미 가능한 거의 같음 함수에 강력한 제약을줍니다. 위에서 제안한 기능이이를 검증합니다. 하나 또는 여러 개의 명백한 속성이 누락되었을 수 있습니다.

및로 매개 변수화 된 =~평등 관계의 가족 이라고 생각하면 다음 을 추가 할 수도 있습니다.=~[Ɛ,t]Ɛrelth

  • 경우 Ɛ1 < Ɛ2다음 a =~[Ɛ1,t] b을 의미한다 a =~[Ɛ2,t] b(주어진 허용 오차에 대한 평등은 더 높은 허용 오차에서 평등을 의미한다)
  • 경우 t1 < t2다음 a =~[Ɛ,t1] b을 의미한다 a =~[Ɛ,t2] b(주어진 부정확 평등은 더 높은 부정확성에서 평등을 의미한다)

제안 된 솔루션은 또한이를 확인합니다.


부동 숫자를 비교하려면 허용 오차 수준을 선택해야합니다. 예를 들면

final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
    Console.WriteLine("Oh yes!");

하나의 메모. 귀하의 예는 다소 재미 있습니다.

double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
    Console.WriteLine("Oh no!");

여기에 몇 가지 수학

a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.

1/3 != 1

어 그래..

의미합니까

if (b != 1)
    Console.WriteLine("Oh no!")

신속한 부동 소수점 비교에 대한 아이디어

infix operator ~= {}

func ~= (a: Float, b: Float) -> Bool {
    return fabsf(a - b) < Float(FLT_EPSILON)
}

func ~= (a: CGFloat, b: CGFloat) -> Bool {
    return fabs(a - b) < CGFloat(FLT_EPSILON)
}

func ~= (a: Double, b: Double) -> Bool {
    return fabs(a - b) < Double(FLT_EPSILON)
}

Michael Borgwardt & bosonix의 답변에서 PHP에 대한 적응 :

class Comparison
{
    const MIN_NORMAL = 1.17549435E-38;  //from Java Specs

    // from http://floating-point-gui.de/errors/comparison/
    public function nearlyEqual($a, $b, $epsilon = 0.000001)
    {
        $absA = abs($a);
        $absB = abs($b);
        $diff = abs($a - $b);

        if ($a == $b) {
            return true;
        } else {
            if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) {
                return $diff < ($epsilon * self::MIN_NORMAL);
            } else {
                return $diff / ($absA + $absB) < $epsilon;
            }
        }
    }
}

왜 숫자를 비교하는지 스스로에게 물어봐야합니다. 비교의 목적을 알고 있다면 필요한 숫자의 정확성도 알아야합니다. 이는 각 상황과 각 애플리케이션 컨텍스트에서 다릅니다. 그러나 거의 모든 실제 경우에는 절대적인 정확성 이 필요 합니다. 상대적 정확도를 적용 할 수있는 경우는 거의 없습니다.

To give an example: if your goal is to draw a graph on the screen, then you likely want floating point values to compare equal if they map to the same pixel on the screen. If the size of your screen is 1000 pixels, and your numbers are in the 1e6 range, then you likely will want 100 to compare equal to 200.

Given the required absolute accuracy, then the algorithm becomes:

public static ComparisonResult compare(float a, float b, float accuracy) 
{
    if (isnan(a) || isnan(b))   // if NaN needs to be supported
        return UNORDERED;    
    if (a == b)                 // short-cut and takes care of infinities
        return EQUAL;           
    if (abs(a-b) < accuracy)    // comparison wrt. the accuracy
        return EQUAL;
    if (a < b)                  // larger / smaller
        return SMALLER;
    else
        return LARGER;
}

I tried writing an equality function with the above comments in mind. Here's what I came up with:

Edit: Change from Math.Max(a, b) to Math.Max(Math.Abs(a), Math.Abs(b))

static bool fpEqual(double a, double b)
{
    double diff = Math.Abs(a - b);
    double epsilon = Math.Max(Math.Abs(a), Math.Abs(b)) * Double.Epsilon;
    return (diff < epsilon);
}

Thoughts? I still need to work out a greater than, and a less than as well.


You need to take into account that the truncation error is a relative one. Two numbers are about equal if their difference is about as large as their ulp (Unit in the last place).

However, if you do floating point calculations, your error potential goes up with every operation (esp. careful with subtractions!), so your error tolerance needs to increase accordingly.


The standard advice is to use some small "epsilon" value (chosen depending on your application, probably), and consider floats that are within epsilon of each other to be equal. e.g. something like

#define EPSILON 0.00000001

if ((a - b) < EPSILON && (b - a) < EPSILON) {
  printf("a and b are about equal\n");
}

A more complete answer is complicated, because floating point error is extremely subtle and confusing to reason about. If you really care about equality in any precise sense, you're probably seeking a solution that doesn't involve floating point.


The best way to compare doubles for equality/inequality is by taking the absolute value of their difference and comparing it to a small enough (depending on your context) value.

double eps = 0.000000001; //for instance

double a = someCalc1();
double b = someCalc2();

double diff = Math.abs(a - b);
if (diff < eps) {
    //equal
}

참고 URL : https://stackoverflow.com/questions/4915462/how-should-i-do-floating-point-comparison

반응형