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


85

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

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

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

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

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

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

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


부동 소수점 숫자를 사용할 때이 작업을 안정적으로 수행 할 수있는 방법은 없습니다. 실제로는 같지 않지만 (예 : 1E + 100, 1E + 100 + 1) 컴퓨터에 대한 숫자는 항상 같으며 실제로는 같지만 컴퓨터에 대해서는 같지 않은 계산 결과를 얻을 수 있습니다. nelhage의 답변에 대한 의견 중 하나). 둘 중 덜 원하는 것을 선택해야합니다.
toochin

반면에, 만약 당신이 유리수만을 다루는 경우, 정수를 기반으로 한 유리수 산술을 구현할 수 있으며, 두 숫자 중 하나가 다른 숫자로 취소 될 수 있다면 두 숫자는 동일한 것으로 간주됩니다.
toochin

음, 현재 저는 시뮬레이션을하고 있습니다. 내가 일반적으로 이러한 비교를 수행하는 장소는 가변 시간 단계 (일부 ode 해결 용)와 관련이 있습니다. 한 개체에 대해 주어진 시간 단계가 다른 개체의 시간 단계와 같거나, 작거나, 큰지 확인해야하는 몇 가지 경우가 있습니다.
Mike Bailey


배열을 사용하지 않는 이유는 무엇입니까? stackoverflow.com/questions/28318610/...
아드리안 P.에게

답변:


69

float / double precision 한계의 가장자리에서 바로 작업하지 않는 한 크거나 작은 비교는 실제로 문제가되지 않습니다.

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

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의 비트 패턴을 정수로 변환하고 고정 된 정수 거리 내의 모든 것을 받아들이는 것입니다.

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


1
@toochin : 허용하려는 오차 범위에 따라 다르지만 0에 가장 가까운 비정규 화 된 숫자, 양수 및 음수를 고려할 때 가장 분명하게 문제가됩니다. 그러나 상대적인 오류를 기반으로 한 많은 순진한 구현은 너무 멀리 떨어져 있다고 간주합니다.
Michael Borgwardt

2
흠. 테스트 else if (a * b == 0)가 있지만 같은 줄에 대한 의견은 다음과 같습니다 a or b or both are zero. 하지만이 두 가지가 다르지 않습니까? 예를 들면, 경우 a == 1e-162b == 2e-162다음 조건이 a * b == 0true가됩니다.
Mark Dickinson

1
@toochin : 주로 해당 기능이없는 다른 언어로 코드를 쉽게 이식 할 수 있어야하기 때문입니다 (Java에도 1.5에서만 추가되었습니다).
Michael Borgwardt

1
그 함수가 매우 많이 사용된다면 (예를 들어 비디오 게임의 모든 프레임) 나는 그것을 대대적 인 최적화로 어셈블리로 다시 작성할 것입니다.

1
특히 abs(a-b)<eps여기 에 대한 답변을 고려할 때 훌륭한 가이드와 훌륭한 답변 입니다. 두 가지 질문 : (1) 모든 <s를 <=s 로 변경 하여 정확한 비교와 동일한 "zero-eps"비교를 허용 하는 것이 더 낫지 않을까요 ? (2) (마지막 줄) diff < epsilon * (absA + absB);대신 사용하는 것이 더 낫지 diff / (absA + absB) < epsilon;않습니까-?
Franz D.

41

TL; DR

  • 현재 허용되는 솔루션 대신 다음 기능을 사용하여 특정 제한 상황에서 바람직하지 않은 결과를 방지하면서 잠재적으로 더 효율적입니다.
  • 숫자에 대해 예상되는 부정확성을 알고 그에 따라 비교 함수에 입력하십시오.

그래픽, 제발?

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

첫 번째는이다 상대적인 차이 모드 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 < y1 < y2, 실제로 y2 - x는보다 2000 배 이상 큽니다 y1 - x. 그러나 현재 솔루션을 사용하면

대조적으로, 위에서 제안 된 솔루션에서 스위치 영역은 값을 기반으로하며 |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(주어진 부정확 평등은 더 높은 부정확성에서 평등을 의미한다)

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


1
그것은 훌륭한 대답입니다!
davidhigh

1
C ++ 구현 질문 : (std::abs(a) + std::abs(b))더 클 수 std::numeric_limits<float>::max()있습니까?
anneb

1
@anneb 예, + INF 일 수 있습니다.
Paul Groke

16

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

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


1
필요가 사용하지하는 fabs첫 번째 테스트를 할 경우, 전혀if (A - B < -Epsilon)
fishinear

11

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

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!")

3

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

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)
}

1

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;
            }
        }
    }
}

1

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

예를 들어, 목표가 화면에 그래프를 그리는 것이라면 부동 소수점 값이 화면의 동일한 픽셀에 매핑되면 동일하게 비교되기를 원할 것입니다. 화면 크기가 1000 픽셀이고 숫자가 1e6 범위에 있다면 100은 200과 동일하게 비교하기를 원할 것입니다.

필요한 절대 정확도가 주어지면 알고리즘은 다음과 같습니다.

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;
}

0

표준 조언은 약간의 작은 "엡실론"값 (아마도 응용 프로그램에 따라 선택됨)을 사용하고 서로의 엡실론 내에있는 수레가 같은 것으로 간주하는 것입니다. 예 : 뭔가

더 완전한 대답은 복잡합니다. 부동 소수점 오류는 매우 미묘하고 추론하기 어렵 기 때문입니다. 정확한 의미에서 평등에 대해 정말로 관심이 있다면 부동 소수점을 포함하지 않는 솔루션을 찾고있을 것입니다.


2.3E-15와 같이 정말 작은 부동 소수점 숫자로 작업한다면 어떨까요?
toochin

1
저는 대략 [10E-14, 10E6]의 범위로 작업하고 있습니다. 기계 엡실론은 아니지만 매우 가깝습니다.
Mike Bailey

2
상대적인 오류 로 작업해야한다는 것을 명심한다면 작은 숫자로 작업하는 것은 문제가되지 않습니다 . 상대적으로 큰 오차 허용 오차에 신경 쓰지 않는다면, 위의 조건을 다음과 같이 if ((a - b) < EPSILON/a && (b - a) < EPSILON/a)
바꾸면 괜찮을

2
위에 제공된 코드는 매우 큰 숫자를 처리 할 때도 문제가됩니다. c숫자가 충분히 커지면 EPSILON이의 기계 정밀도보다 작아지기 때문입니다 c. 예를 들어 c = 1E+22; d=c/3; e=d+d+d;. 그러면 e-c1보다 상당히 클 수 있습니다.
toochin

1
예를 들어, try double a = pow(8,20); double b = a/7; double c = b+b+b+b+b+b+b; std::cout<<std::scientific<<a-c;(pnt 및 nelhage에 따라 a와 c가 같지 않음) 또는 double a = pow(10,-14); double b = a/2; std::cout<<std::scientific<<a-b;(pnt 및 nelhage에 따라 a와 b가 같음)
toochin

0

위의 주석을 염두에두고 평등 함수를 작성해 보았습니다. 내가 생각해 낸 것은 다음과 같습니다.

편집 : Math.Max ​​(a, b)에서 Math.Max ​​(Math.Abs ​​(a), Math.Abs ​​(b))로 변경

생각? 나는 여전히 더 큰 것, 작은 것보다 더 많이 운동해야합니다.


epsilon이어야합니다 Math.abs(Math.Max(a, b)) * Double.Epsilon;. 그렇지 않으면 항상 diff음수 ab. 그리고 나는 당신 epsilon이 너무 작다고 생각합니다 . 함수는 ==연산자 와 다른 것을 반환하지 않을 수도 있습니다 . 보다 큼 a < b && !fpEqual(a,b).
toochin

1
두 값이 모두 정확히 0이면 실패하고 Double.Epsilon 및 -Double.Epsilon에서는 실패하고 무한대에서는 실패합니다.
Michael Borgwardt

1
무한의 경우는 내 특정 응용 프로그램에서 문제가 아니지만 적절하게 언급됩니다.
Mike Bailey

-1

잘림 오류는 상대적 오류라는 점을 고려해야합니다. 두 숫자의 차이가 ulp (마지막 위치의 단위)만큼 크면 거의 동일합니다.

그러나 부동 소수점 계산을 수행하면 모든 연산에서 잠재적 인 오류가 증가하므로 (특히 빼기에주의하십시오!) 그에 따라 오류 허용 오차를 늘려야합니다.


-1

같음 / 부등식에 대해 double을 비교하는 가장 좋은 방법은 차이의 절대 값을 취하여 충분히 작은 값 (컨텍스트에 따라 다름)과 비교하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.