[objective-c] 부동 소수점 값을 비교하는 것이 얼마나 위험합니까?

해상도 독립 좌표계로 인해 UIKit사용하는 것을 알고 CGFloat있습니다.

그러나 때마다 나는 예를 들어 있는지 확인하고자 frame.origin.x한다 0그것이 나를 아프게 느낄 수 있습니다 :

if (theView.frame.origin.x == 0) {
    // do important operation
}

하지인가 CGFloat와 비교할 때 잘못된 반응에 취약 ==, <=, >=, <, >? 부동 소수점이며 0.0000000000041예를 들어 정밀도 문제가 있습니다 .

되어 Objective-C비교하거나이 일이 발생할 때 내부적으로 처리 origin.x0으로 읽는는 비교되지 않는 0사실로?



답변

우선, 부동 소수점 값은 동작에서 “임의”가 아닙니다. 정확한 비교는 많은 실제 사용에서 의미가 있습니다. 그러나 부동 소수점을 사용하려면 작동 방식을 알고 있어야합니다. 부동 소수점이 실수처럼 작동한다고 가정하면 실수로 코드가 빠르게 중단됩니다. 부동 소수점 결과에 관련하여 큰 임의의 퍼즈가 있다고 가정하면 (여기에서 대부분의 대답이 제안하는 것처럼) 처음에는 작동하는 것처럼 보이지만 큰 크기의 오류가 발생하고 모서리가 부러진 코드가 생성됩니다.

우선, 부동 소수점으로 프로그래밍하려면 다음을 읽어야합니다.

부동 소수점 산술에 대해 모든 컴퓨터 과학자가 알아야 할 사항

네, 전부 읽어보세요. 그것이 너무 많은 부담이라면, 읽을 시간이 될 때까지 계산에 정수 / 고정 소수점을 사용해야합니다. 🙂

이제는 정확한 부동 소수점 비교의 가장 큰 문제는 다음과 같습니다.

  1. 값이 많이 당신이 소스, 쓰기,로 읽을 수 있다는 사실 scanf하거나 strtod, 존재하지 않는 부동 소수점 값으로하고 자동으로 가장 가까운 근사치로 변환됩니다. 이것이 demon9733의 대답이었습니다.

  2. 실제 결과를 나타내기에 충분한 정밀도가 없기 때문에 많은 결과가 반올림됩니다. 이것을 볼 수있는 쉬운 예는 추가 x = 0x1fffffe하고 y = 1수레입니다. 여기 x에서 가수 (ok)에서 24 비트의 정밀도를 y가지며 1 비트 만 있습니다. 그러나 비트를 추가하면 해당 비트가 겹치는 위치에 있지 않으므로 결과에는 25 비트의 정밀도가 필요합니다. 대신 반올림됩니다 ( 0x2000000기본 반올림 모드에서).

  3. 정확한 값을 얻기 위해 무한히 많은 장소가 필요하기 때문에 많은 결과가 반올림된다는 사실. 여기에는 1/3과 같은 합리적인 결과가 포함됩니다 (무한한 많은 장소를 차지하는 10 진수에 익숙 함)뿐만 아니라 1/10 (5는 2의 거듭 제곱이 아니기 때문에 2 진수로 무한히 많은 장소를 차지합니다), 완벽한 제곱이 아닌 것의 제곱근과 같은 비이성적 인 결과뿐만 아니라.

  4. 이중 반올림. 일부 시스템 (특히 x86)에서 부동 소수점 표현식은 공칭 유형보다 높은 정밀도로 평가됩니다. 즉, 위의 반올림 유형 중 하나가 발생하면 두 가지 반올림 단계가 시작됩니다. 먼저 결과를 고정밀 유형으로 반올림 한 다음 최종 유형으로 반올림합니다. 예를 들어, 1.49를 정수 (1)로 반올림하면 10 진수로 발생하는 것과, 소수점 이하 1 자리 (1.5)로 반올림 한 후 결과를 정수 (2)로 반올림하면 어떻게되는지를 고려하십시오. 이는 컴파일러의 동작 (특히 GCC와 같은 버그가있는 비준수 컴파일러의 경우)을 예측할 수 없기 때문에 실제로 부동 소수점에서 처리 할 수있는 가장 단순한 영역 중 하나입니다.

  5. 초월 함수 ( trig, exp, log등)의 결과 정확하게 원형을 갖도록 지정되지; 결과는 마지막 정밀도 (보통 1ulp ) 에서 한 단위 내에서 정확하도록 지정되었습니다 .

부동 소수점 코드를 작성할 때 결과가 정확하지 않은 숫자로 무엇을하고 있는지를 염두에두고 그에 따라 비교해야합니다. 종종 “엡실론”과 비교하는 것이 합리적이지만, 엡실론은 절대 상수가 아니라 비교하는 숫자크기를 기반으로해야합니다 . (절대 상수 엡실론이 작동하는 경우 부동 소수점이 아닌 고정 소수점이 작업에 적합한 도구임을 강력하게 나타냅니다!)

편집 : 특히 크기 기준 엡실론 검사는 다음과 같아야합니다.

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))

어디 FLT_EPSILON에서 일정 float.h(로 교체 DBL_EPSILON를위한 doubles 또는 LDBL_EPSILON위해 long double들) 및 K당신이 당신의 계산의 누적 오류가 확실히에 의해 제한되도록 선택 상수 K마지막 장소의 단위는 (확실하지 않은 경우에는 오류가 발생했습니다 바운드 계산, K계산이 말한 것보다 몇 배 더 큼).

마지막으로,이 기능을 사용하면 FLT_EPSILON비정규에는 적합하지 않기 때문에 0 근처에서 특별한주의가 필요할 수 있습니다 . 빠른 수정은 다음과 같습니다.

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)

DBL_MIN복식을 사용하는 경우에도 마찬가지 입니다.


답변

0은 IEEE754 부동 소수점 숫자로 정확하게 표현 가능하기 때문에 (또는 내가 작업했던 다른 fp 숫자 구현을 사용하여) 0과 비교하는 것이 안전 할 것입니다. 그러나 프로그램에서과 같은 값 (예 :)을 계산 theView.frame.origin.x해야한다고 생각할만한 가치 가 있지만 계산에서 0을 보장 할 수없는 경우 물릴 수 있습니다 .

조금 명확히하기 위해 다음과 같은 계산이 필요합니다.

areal = 0.0

(언어 나 시스템이 고장 나지 않는 한) (areal == 0.0)이 true를 반환하지만

areal = 1.386 - 2.1*(0.66)

아닐 수도 있습니다.

계산에서 0이어야하는 값을 생성 할뿐 아니라 0이어야하는 값을 생성 할뿐 아니라 fp 값을 0과 비교할 수 있습니다. 필요한 정도로 확신 할 수없는 경우 , ‘공차 평등’의 일반적인 접근 방식을 따르는 것이 가장 좋습니다.

최악의 경우 fp 값을 부주의하게 비교하는 것은 매우 위험 할 수 있습니다. 항공 전자 공학, 무기 안내, 발전소 운영, 차량 내비게이션, 계산이 실제 세계를 충족하는 거의 모든 응용 프로그램을 생각하십시오.

앵그리 버드에게는 그렇게 위험하지 않습니다.


답변

나는 다른 사람들과 약간 다른 대답을하고 싶습니다. 그들은 언급 한대로 귀하의 질문에 대답하기에는 훌륭하지만 아마도 당신이 알아야 할 것이거나 실제 문제가 무엇인지는 아닙니다.

그래픽의 부동 소수점은 괜찮습니다! 그러나 플로트를 직접 비교할 필요는 거의 없습니다. 왜 그렇게해야합니까? 그래픽은 플로트를 사용하여 간격을 정의합니다. 또한 float가 간격으로 정의되는지 여부도 float로 정의하면 항상 잘 정의되며 정확하거나 정확하지 않고 일관성이 있어야합니다! 모든 그래픽이 필요한 픽셀 (간격이기도합니다!)을 할당 할 수있는 한.

따라서 포인트가 [0..width [범위] 밖에 있는지 테스트하려면 괜찮습니다. 포함을 일관되게 정의해야합니다. 예를 들어 항상 내부는 (x> = 0 && x <width)로 정의하십시오. 교차점 또는 적중 테스트도 마찬가지입니다.

그러나 그래픽 좌표를 일종의 플래그로 남용하는 경우 (예를 들어, 윈도우가 도킹되었는지 여부를 확인하는 경우)이를 수행해서는 안됩니다. 대신 그래픽 표현 레이어와 분리 된 부울 플래그를 사용하십시오.


답변

0이 계산 된 값이 아닌 한 (위의 답변에서 언급 한대로) 0과 비교 하는 것이 안전한 작업 일 있습니다. 그 이유는 0은 부동 소수점에서 완벽하게 표현 가능한 숫자이기 때문입니다.

완벽하게 표현 가능한 값을 말하면 2의 제곱 개념 (단일 정밀도)으로 24 비트의 범위를 얻을 수 있습니다. 따라서 1, 2, 4는 .5, .25 및 .125와 같이 완벽하게 표현할 수 있습니다. 모든 중요한 비트가 24 비트 인 한 황금색입니다. 따라서 10.625는 정확하게 회답 ​​될 수 있습니다.

이것은 훌륭하지만 압력을 받으면 빨리 떨어집니다. 두 가지 시나리오가 떠 오릅니다. 1) 계산이 수행되는 경우. sqrt (3) * sqrt (3) == 3임을 믿지 마십시오. 그것은 그렇게되지 않을 것입니다. 그리고 다른 답변 중 일부가 제안하는 것처럼 아마도 엡실론 내에 있지 않을 것입니다. 2) 2의 비 전력 (NPOT)이 관련된 경우. 따라서 이상하게 들릴지 모르지만 0.1은 이진수로 무한한 시리즈이므로 이와 같은 숫자를 사용하는 계산은 처음부터 정확하지 않습니다.

(Oh와 원래 질문은 0에 대한 비교를 언급했습니다. -0.0도 완벽하게 유효한 부동 소수점 값이라는 것을 잊지 마십시오.)


답변

[ ‘올바른 대답’은 선택에 대한 광택 K입니다. 선택 K은 선택 하는 것과 마찬가지로 임시 VISIBLE_SHIFT이지만 선택 KVISIBLE_SHIFT디스플레이 속성에 기반하지 않기 때문에 덜 명확 합니다. 따라서 독을 선택 K하거나 선택하십시오 VISIBLE_SHIFT. 이 답변은 선택을 옹호 VISIBLE_SHIFT한 다음 선택 이 어렵다는 것을 보여줍니다. K]

정확하게 라운드 오류로 인해 논리 연산에 대해 ‘정확한’값 비교를 사용해서는 안됩니다. 시각적 디스플레이의 특정 위치의 경우 위치가 0.0 또는 0.0000000003인지 여부는 중요하지 않습니다. 차이는 눈에 보이지 않습니다. 따라서 논리는 다음과 같아야합니다.

#define VISIBLE_SHIFT    0.0001        // for example
if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ }

그러나 결국 ‘눈에 보이지 않음’은 디스플레이 속성에 따라 다릅니다. 디스플레이를 상한으로 할 수 있다면 (가능해야합니다); 그런 다음 VISIBLE_SHIFT상한의 일부가되도록 선택하십시오 .

이제 ‘올바른 대답’을 바탕으로 K피킹을 살펴 보겠습니다 K. 위의 ‘정답’은 다음과 같이 말합니다.

K는 계산의 누적 오차가 마지막에 K 단위로 확실히 묶 이도록 선택하는 상수입니다 (그리고 오차 한계 계산이 확실하지 않은 경우 K를 계산보다 몇 배 크게 만듭니다. 해야한다고 말해)

그래서 우리는 필요합니다 K. 지고는 경우 K나의 선택보다 직관적 인 더 어렵 VISIBLE_SHIFT당신이 당신을 위해 작동을 결정할 수 있습니다. 이를 찾기 K위해 여러 가지 K값을 확인하는 테스트 프로그램을 작성하여 작동 방식을 확인할 수 있습니다. K‘올바른 대답’을 사용할 수 있다면 을 선택하는 방법을 분명히해야합니다 . 아니?

우리는 ‘올바른 대답’세부 사항으로 사용할 것입니다.

if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)

K의 모든 값을 시도해 봅시다.

#include <math.h>
#include <float.h>
#include <stdio.h>

void main (void)
{
  double x = 1e-13;
  double y = 0.0;

  double K = 1e22;
  int i = 0;

  for (; i < 32; i++, K = K/10.0)
    {
      printf ("K:%40.16lf -> ", K);

      if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
        printf ("YES\n");
      else
        printf ("NO\n");
    }
}
ebg@ebg$ gcc -o test test.c
ebg@ebg$ ./test
K:10000000000000000000000.0000000000000000 -> YES
K: 1000000000000000000000.0000000000000000 -> YES
K:  100000000000000000000.0000000000000000 -> YES
K:   10000000000000000000.0000000000000000 -> YES
K:    1000000000000000000.0000000000000000 -> YES
K:     100000000000000000.0000000000000000 -> YES
K:      10000000000000000.0000000000000000 -> YES
K:       1000000000000000.0000000000000000 -> NO
K:        100000000000000.0000000000000000 -> NO
K:         10000000000000.0000000000000000 -> NO
K:          1000000000000.0000000000000000 -> NO
K:           100000000000.0000000000000000 -> NO
K:            10000000000.0000000000000000 -> NO
K:             1000000000.0000000000000000 -> NO
K:              100000000.0000000000000000 -> NO
K:               10000000.0000000000000000 -> NO
K:                1000000.0000000000000000 -> NO
K:                 100000.0000000000000000 -> NO
K:                  10000.0000000000000000 -> NO
K:                   1000.0000000000000000 -> NO
K:                    100.0000000000000000 -> NO
K:                     10.0000000000000000 -> NO
K:                      1.0000000000000000 -> NO
K:                      0.1000000000000000 -> NO
K:                      0.0100000000000000 -> NO
K:                      0.0010000000000000 -> NO
K:                      0.0001000000000000 -> NO
K:                      0.0000100000000000 -> NO
K:                      0.0000010000000000 -> NO
K:                      0.0000001000000000 -> NO
K:                      0.0000000100000000 -> NO
K:                      0.0000000010000000 -> NO

아, 1e-13을 ‘0’으로 설정하려면 K가 1e16 이상이어야합니다.

따라서 두 가지 옵션이 있습니다.

  1. 내가 제안한 것처럼 ‘엡실론’의 가치에 대한 엔지니어링 판단 을 사용하여 간단한 엡실론 계산을 수행하십시오 . 그래픽을하고 있고 ‘제로’는 시각적 자산 (이미지 등)을 검사하고 엡실론이 무엇인지 판단하는 것보다 ‘눈에 띄는 변화’를 의미합니다.
  2. 비화물 숭배 답변의 참조를 읽고 프로세스에서 박사 학위를받을 때까지 부동 소수점 계산을 시도하지 말고 직관적이지 않은 판단을 사용하여를 선택하십시오 K.

답변

정답은 : Cocoa Touch에서 포인트를 어떻게 비교합니까?

정답 : CGPointEqualToPoint ().

다른 질문 : 두 개의 계산 된 값이 동일합니까?

대답은 여기에 게시되었습니다.

그들이 가까이 있는지 확인하는 방법? 그들이 가까운 지 확인하려면 CGPointEqualToPoint ()를 사용하지 마십시오. 그러나 그들이 가까이 있는지 확인하지 마십시오. 점이 선을 넘어서 있는지 또는 점이 구 안에 있는지 확인하는 것과 같이 현실 세계에서 의미있는 것을 수행하십시오.


답변

C 표준을 마지막으로 확인했을 때 두 배의 부동 소수점 연산 (총 64 비트, 53 비트 가수)이 정밀도보다 정확해야 할 필요는 없었습니다. 그러나 일부 하드웨어는 레지스터에서 더 정밀한 연산을 수행 할 수 있으며, 요구 사항은 레지스터에로드되는 숫자의 정밀도를 넘어 하위 비트를 지우는 요구 사항을 의미하지 않는 것으로 해석되었습니다. 따라서 마지막으로 잠든 사람의 레지스터에 남은 내용에 따라 이와 같은 비교 결과가 예기치 않게 나타날 수 있습니다.

즉, 내가 볼 때마다 그것을 제거하려는 노력에도 불구하고, 내가 일하는 복장에는 gcc를 사용하여 컴파일되어 Linux에서 실행되는 많은 C 코드가 있으며, 우리는 이러한 예기치 않은 결과를 오랫동안 발견하지 못했습니다. . 나는 이것이 gcc가 우리를 위해 하위 비트를 지우고 있는지, 80 비트 레지스터가 현대 컴퓨터에서 이러한 작업에 사용되지 않거나, 표준이 변경되었는지, 또는 무엇인지 알지 못합니다. 누구나 챕터와 구절을 인용 할 수 있는지 알고 싶습니다.