[floating-point] 두 개의 같지 않은 부동 소수점 숫자를 빼서 0을 얻을 수 있습니까?

다음 예제에서 0 (또는 무한대)으로 나눌 수 있습니까?

public double calculation(double a, double b)
{
     if (a == b)
     {
         return 0;
     }
     else
     {
         return 2 / (a - b);
     }
}

일반적인 경우에는 물론 그렇지 않습니다. 그러나 경우 ab매우 가까운 수 있습니다 (a-b)되는 결과 0계산의 정밀도 때문에?

이 질문은 Java에 관한 것이지만 대부분의 프로그래밍 언어에 적용될 것이라고 생각합니다.



답변

Java에서는 if와 a - b같지 않습니다 . Java는 비정규 화 된 숫자를 지원하는 IEEE 754 부동 소수점 연산을 요구하기 때문입니다. 로부터 사양 :0a != b

특히, Java 프로그래밍 언어는 IEEE 754 비정규 부동 소수점 숫자 및 점진적 언더 플로우를 지원해야하므로 특정 숫자 알고리즘의 바람직한 특성을 쉽게 입증 할 수 있습니다. 계산 결과가 비정규 화 된 숫자 인 경우 부동 소수점 연산은 “0으로 플러시”되지 않습니다.

만약에 FPU비정규 화 된 숫자 와 함께 작동 다른 숫자를 빼면 절대 곱셈과 달리 절대 0이 될 수 없습니다 . 이 질문 도 참조하십시오 .

다른 언어의 경우 다릅니다. 예를 들어 C 또는 C ++에서 IEEE 754 지원은 선택 사항입니다.

즉, 상기 것이 가능 표현식 2 / (a - b)으로 예를 들면, 오버 플로우 a = 5e-308b = 4e-308 .


답변

해결 방법으로 다음은 어떻습니까?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}

그렇게하면 어떤 언어로든 IEEE 지원에 의존하지 않습니다.


답변

a - b부동 소수점을 0으로 나누면 예외가 발생하지 않으므로 의 값에 관계없이 0으로 나누기를 얻지 못합니다. 무한대를 반환합니다.

이제 a == btrue를 반환 하는 유일한 방법 은 정확히 동일한 비트를 포함 a하고 b포함하는 것입니다. 최하위 비트 만 다르면 그 차이는 0이 아닙니다.

편집하다 :

Bathsheba가 올바르게 언급했듯이 몇 가지 예외가 있습니다.

  1. “숫자가 비교되지 않음”은 거짓이지만 비트 패턴은 동일합니다.

  2. -0.0은 +0.0과 true를 비교하도록 정의되며 비트 패턴이 다릅니다.

모두 그래서 만약 a하고 b있다 Double.NaN, 당신은 다른 절에 도달하지만, 이후 NaN - NaN도 반환 NaN, 당신은 0으로 나누어되지 않습니다.


답변

여기서 0으로 나누기가 발생할 수있는 경우는 없습니다.

SMT 해결사 Z3는 소수점 연산 부동 정확한 IEEE을 지원합니다. Z3에게 숫자 ab같은 것을 찾도록 요청합시다 a != b && (a - b) == 0.

(set-info :status unknown)
(set-logic QF_FP)
(declare-fun b () (FloatingPoint 8 24))
(declare-fun a () (FloatingPoint 8 24))
(declare-fun rm () RoundingMode)
(assert
(and (not (fp.eq a b)) (fp.eq (fp.sub rm a b) +zero) true))
(check-sat)

결과는 UNSAT입니다. 그런 숫자는 없습니다.

위의 SMTLIB 문자열은 또한 Z3가 임의의 반올림 모드 ( rm) 를 선택할 수 있도록합니다 . 즉, 결과는 가능한 모든 반올림 모드 (5 개가 있음)에 적용됩니다. 그 결과 재생중인 변수가 NaN무한대 일 수도 있습니다 .

a == b로 구현됩니다 fp.eq있도록 품질 +0f-0f동등 비교합니다. 0과의 비교도 사용 fp.eq됩니다. 문제는 0으로 나누기를 피하는 것을 목표로하기 때문에 이것은 적절한 비교입니다.

평등 테스트는 비트 평등를 사용하여 구현 된, 경우 +0f-0f만드는 방법이었을 것 a - b제로. 이 답변의 잘못된 이전 버전에는 궁금한 점에 대한 모드 세부 정보가 포함되어 있습니다.

Z3 온라인 은 아직 FPA 이론을 지원하지 않습니다. 이 결과는 최신 불안정한 분기를 사용하여 얻었습니다. 다음과 같이 .NET 바인딩을 사용하여 재생할 수 있습니다.

var fpSort = context.MkFPSort32();
var aExpr = (FPExpr)context.MkConst("a", fpSort);
var bExpr = (FPExpr)context.MkConst("b", fpSort);
var rmExpr = (FPRMExpr)context.MkConst("rm", context.MkFPRoundingModeSort());
var fpZero = context.MkFP(0f, fpSort);
var subExpr = context.MkFPSub(rmExpr, aExpr, bExpr);
var constraintExpr = context.MkAnd(
        context.MkNot(context.MkFPEq(aExpr, bExpr)),
        context.MkFPEq(subExpr, fpZero),
        context.MkTrue()
    );

var smtlibString = context.BenchmarkToSMTString(null, "QF_FP", null, null, new BoolExpr[0], constraintExpr);

var solver = context.MkSimpleSolver();
solver.Assert(constraintExpr);

var status = solver.Check();
Console.WriteLine(status);

(예 : 사례를 간과 어렵 기 때문에 IEEE 부동의 질문에 대답 Z3를 사용하는 것은 좋은 NaN, -0f, +-inf) 당신이 임의의 질문을 할 수 있습니다. 사양을 해석하고 인용 할 필요가 없습니다. “이 특정 int log2(float)알고리즘이 맞습니까?” 와 같은 혼합 부동 소수점 및 정수 질문을 할 수도 있습니다 .


답변

제공된 함수는 실제로 무한대를 반환 할 수 있습니다.

public class Test {
    public static double calculation(double a, double b)
    {
         if (a == b)
         {
             return 0;
         }
         else
         {
             return 2 / (a - b);
         }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        double d1 = Double.MIN_VALUE;
        double d2 = 2.0 * Double.MIN_VALUE;
        System.out.println("Result: " + calculation(d1, d2));
    }
}

출력은 Result: -Infinity입니다.

나누기의 결과가 두 배로 커질 때 분모가 0이 아닌 경우에도 무한대가 반환됩니다.


답변

IEEE-754를 따르는 부동 소수점 구현에서 각 부동 소수점 유형은 두 가지 형식으로 숫자를 보유 할 수 있습니다. 대부분의 부동 소수점 값에는 하나 ( “정규화 된”)가 사용되지만, 표시 할 수있는 두 번째로 작은 숫자는 가장 작은 것보다 약간 작기 때문에 그 차이는 동일한 형식으로 표현할 수 없습니다. 다른 ( “비정규 화 된”) 형식은 첫 번째 형식으로 표현할 수없는 매우 작은 숫자에만 사용됩니다.

비정규 화 된 부동 소수점 형식을 효율적으로 처리하는 회로는 비싸며 모든 프로세서에 포함되지는 않습니다. 일부 프로세서는 실제로 작은 숫자 에 대한 연산이 다른 값에 대한 연산보다 훨씬 느리거나 정규화 형식에 비해 너무 작은 숫자를 0으로 간주하는 것 중에서 선택할 수 있습니다.

Java 스펙은 구현이 비정규 화 된 형식을 지원해야 함을 나타내며, 그렇게하면 코드 실행 속도가 느려질 수 있습니다. 반면에 일부 구현에서는 값이 너무 작아서 값이 너무 작은 경우 값이 약간 느슨하게 처리되는 대신 약간 느슨하게 값을 처리하는 대신 코드를 더 빠르게 실행할 수있는 옵션을 제공 할 수 있습니다. 계산이 중요한 계산보다 10 배나 오래 걸리므로 성 가실 수 있으므로 많은 실제 상황에서 0으로 플러시는 느리지 만 정확한 산술보다 유용합니다.


답변

IEEE 754 이전의 예전에는 a! = b가 ab! = 0을 의미하지 않았으며 그 반대도 가능했습니다. 이것이 처음에 IEEE 754를 만드는 이유 중 하나였습니다.

IEEE 754에서는 거의 보장됩니다. C 또는 C ++ 컴파일러는 필요한 것보다 높은 정밀도로 작업을 수행 할 수 있습니다. 따라서 a와 b가 변수가 아니라 표현식 인 경우, a + b는 더 높은 정밀도로 한 번 계산 될 수 있기 때문에 (a + b)! = c는 (a + b)-c! = 0을 의미하지 않습니다. 더 높은 정밀도.

많은 FPU는 비정규 화 된 숫자를 반환하지 않고 0으로 대체하는 모드로 전환 할 수 있습니다.이 모드에서 a와 b가 작은 정규화 된 숫자 인 경우 차이가 가장 작은 정규화 된 숫자보다 작지만 0보다 큰 경우 a ! = b 또한 a == b를 보장하지 않습니다.

“부동 소수점 수를 절대로 비교하지 마십시오”는화물 컬트 프로그래밍입니다. “엡실론이 필요하다”라는 만트라를 가진 사람들 중에서 대부분은 엡실론을 올바르게 선택하는 방법을 모른다.