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




유니티 부동소수점 (6)

UIKit 은 해상도 독립적 인 좌표계 때문에 UIKit 사용합니다.

그러나 예를 들어 frame.origin.x0 인지 확인하려고 할 때마다 그것은 나를 아프게합니다.

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

== , <= , >= , < , > 과 비교할 때 CGFloat 가 false positive에 취약하지 CGFloat ? 그것은 부동 소수점이며 그들은 예를 들어 0.0000000000041 정밀도 문제가 없습니다.

Objective-C 는 이것을 비교할 때 내부적으로 처리합니까? 아니면 0 으로 읽는 origin.x0 과 true를 비교하지 않습니다.


0은 정확히 IEEE754 부동 소수점 숫자로 나타낼 수 있기 때문에 (또는 지금까지 사용해 본 fp 숫자의 다른 구현을 사용하여) 0과 비교하면 안전 할 것입니다. 그러나 프로그램이 (예 : theView.frame.origin.x 와 같은) 값을 계산하여 0이어야한다고 믿을만한 이유가 있지만 계산이 0이 될 수 있다고 보장 할 수없는 경우에는 theView.frame.origin.x 수 있습니다.

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

areal = 0.0

(areal == 0.0)이 true를 리턴하지만 다음과 같은 또 다른 계산을하는 값을 생성하십시오 (언어 또는 시스템이 손상되지 않은 경우).

areal = 1.386 - 2.1*(0.66)

그렇지 않을 수도 있습니다.

계산이 0이라는 값을 생산한다는 것을 확신 할 수 있다면 (0이되어야하는 값을 생산하는 것이 아니라) 그 다음에 fp 값을 0과 비교할 수 있습니다. 필요한 정도까지 확신 할 수 없다면 , '공평한 평등'이라는 평범한 접근 방식에 충실해야한다.

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

Angry Birds의 경우 위험하지는 않습니다.


[ '정답'은 K 를 선택하는 것에 유익합니다. K 선택하면 VISIBLE_SHIFT 를 선택하는 것과 마찬가지로 특별하게 끝나지만 K 를 선택하는 것은 VISIBLE_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 를 선택하는 것보다 직관력이 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");
    }
}
[email protected]$ gcc -o test test.c
[email protected]$ ./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. 제가 제안한 것처럼 '엡실론'의 가치에 대한 엔지니어링 판단 을 사용하여 간단한 엡실론 계산을하십시오. 그래픽을하고 있고 '0'은 시각적 자산 (이미지 등)을 검사하고 어떤 엡실론이 될 수 있는지 판단하는 것보다 '가시적 인 변화'를 의미합니다.
  2. 카고 가드가 아닌 답의 참조를 읽고 (그리고 과정에서 박사 학위를 받았다) 부동 소수점 계산을 시도한 다음 직관적이지 않은 판단을 사용하여 K 를 선택하십시오.

나는 옳은 것은 각 숫자를 객체로 선언 한 다음 그 객체에서 세 가지를 정의하는 것이라고 말하고 싶습니다. 1) 동등 연산자. 2) setAcceptableDifference 메소드 3) 가치 그 자체. 항등 연산자는 두 값의 절대 차이가 허용되는 값보다 작 으면 true를 반환합니다.

문제에 맞게 개체를 하위 클래스로 만들 수 있습니다. 예를 들어, 직경이 0.0001 인치 미만인 경우 1 인치와 2 인치 사이의 둥근 금속 막대는 동일한 직경으로 간주 될 수 있습니다. 따라서 매개 변수 0.0001을 사용하여 setAcceptableDifference를 호출 한 다음 신뢰 연산자를 사용하십시오.


마지막으로 C 표준을 검사했을 때, 두 배의 부동 소수점 연산 (64 비트 총, 53 비트 가수)은 해당 정밀도 이상으로 정확해야한다는 요구 사항이 없었습니다. 그러나 일부 하드웨어는 더 정밀도가 높은 레지스터에서 연산을 수행 할 수 있으며 요구 사항은 레지스터에로드되는 숫자의 정밀도를 넘어서 하위 비트를 지우지 않아도된다는 의미로 해석됩니다. 따라서 여러분은 마지막에 그곳에서 잤던 사람들의 기록에 남겨진 내용에 따라 예기치 않은 비교 결과를 얻을 수 있습니다.

즉, 내가 그것을 볼 때마다 그것을 삭제하려고 노력했지만, 내가 일하는 복장에는 gcc를 사용하여 컴파일되고 리눅스에서 실행되는 많은 C 코드가 있으며, 우리는 매우 오랜 시간에 이러한 예기치 않은 결과를 발견하지 못했다. . gcc가 우리를 위해 하위 비트를 지우고 있거나, 최신 컴퓨터에서 이러한 연산에 80 비트 레지스터를 사용하지 않았거나, 표준이 변경되었거나, 또는 무엇 때문에이를 알지 못합니다. 누구든지 장과 절을 인용 할 수 있는지 알고 싶습니다.


올바른 질문 : 코코아 터치에서 포인트를 비교하는 방법은 무엇입니까?

정답은 CGPointEqualToPoint ()입니다.

다른 질문 : 계산 된 두 값이 같은가요?

대답은 여기에 게시 : 그들은 아닙니다.

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


우선, 부동 소수점 값은 동작에서 "임의"가 아닙니다. 정확한 비교는 실제로 많은 실제 사용법에서 의미가 있으며 가능합니다. 그러나 부동 소수점을 사용하려면 어떻게 작동하는지 알아야합니다. 실제 숫자와 같은 부동 소수점 연산을 가정 할 때 오류가 발생하면 빠르게 깨지는 코드가 생성됩니다. 부동 소수점 결과를 가정 할 때, 그 결과와 같은 큰 무작위 퍼짐이있을 수 있습니다. 처음에는 효과가 있지만 큰 오류와 깨진 모서리의 경우가 있습니다.

우선, 부동 소수점으로 프로그램하기를 원한다면 이것을 읽어야합니다 :

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

예, 모두 읽으십시오. 너무 많은 부담이라면 읽을 때까지 정수 / 고정 소수점을 사용하여 계산해야합니다. :-)

이제, 정확한 부동 소수점 비교와 관련된 가장 큰 문제점은 다음과 같습니다.

  1. 소스에 쓰거나 scanf 또는 strtod 읽어들이는 값이 부동 소수점 값으로 존재하지 않으므로 자동으로 가장 근사치로 변환됩니다. 이것은 demon9733의 대답이 말한 것입니다.

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

  3. 정확한 결과를 위해 무한히 많은 장소가 필요하기 때문에 많은 결과가 반올림된다는 사실. 여기에는 1/3과 같은 합리적인 결과 (무한히 많은 부분을 차지하는 십진법에 익숙 함)뿐만 아니라 1/10 (5는 2의 거듭 제곱이 아니므로 바이너리에서 무한히 많은 부분을 차지함), 완벽한 사각형이 아닌 것의 제곱근과 같은 불합리한 결과도 마찬가지입니다.

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

  5. 초월 함수 ( trig , exp , log 등)는 결과를 정확하게 반올림하지 않습니다. 결과는 정밀도의 최종 위치 (일반적으로 1ulp 라고 함)의 한 단위 내에서 정확하도록 지정됩니다.

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

편집 : 특히, 크기에 상대적인 엡실론 검사는 다음과 같이 보일 것입니다 :

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

여기서 FLT_EPSILONfloat.h 의 상수이고 double s의 경우 LDBL_EPSILON , long double s의 경우 LDBL_EPSILON 대체됩니다. K 는 계산의 누적 오류가 마지막 위치의 K 단위로 확실히 한정되도록 선택하는 상수입니다 ( 오류 결합 계산 권한이 있는지 확실하지 않은 경우 K 계산 결과보다 몇 배 더 크게 만듭니다.)

마지막으로, 이것을 사용하면 FLT_EPSILON 이 비정상 종료에 대해 의미가 없으므로 FLT_EPSILON 근처에서 약간의 특별한주의가 필요할 수 있습니다. 빠른 수정은 다음과 같이 할 수 있습니다.

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

또한 double을 사용하는 경우 DBL_MIN 대체 DBL_MIN .





floating-accuracy