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


131

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

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

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

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


49
나는 복식의 모든 조합을 시도해야 할 것이다, 그것은 시간이 걸릴 것이다 :)
— Thirler

3
@Thirler는 JUnit Testing을 사용하는 시간처럼 들립니다!
— 매트 클라크

7
@ bluebrain, 내 추측은 리터럴 숫자 2.000 등이 부동 소수점으로 표현하기 위해 많은 소수를 포함한다는 것입니다. 따라서 마지막 숫자는 비교에서 실제 사용한 숫자로 표시되지 않습니다.
— Thirler

4
아마 @Thirler. '실제로 float 또는 double에 할당 한 숫자가 정확한지 보장 할 수 없습니다'
— guness

4
이 경우 0을 반환하면 디버그하기 모호성이 생길 수 있으므로 예외를 던지거나 NaN을 반환하는 대신 0을 반환해야합니다.
— m0skit0

답변:


132

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

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

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

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

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


4
그러나 OP는 2 / (ab)에 대해 알고 싶어합니다. 이것이 유한함을 보장 할 수 있습니까?
— Taemyr

답변 주셔서 감사합니다, 나는 표준화되지 않은 숫자의 설명을 위해 위키 백과에 대한 링크를 추가했습니다.
— Thirler

3
@Taemyr 내 편집 내용을 참조하십시오. 부서가 실제로 오버플로 될 수 있습니다.
— nwellnhof

@Taemyr (a,b) = (3,1)=> 2/(a-b) = 2/(3-1) = 2/2 = 1이 IEEE 부동 소수점와 사실 여부, 나도 몰라
— 콜 존슨

1
@DrewDormann IEEE 754는 C99에도 옵션입니다. 표준의 부록 F를 참조하십시오.
— nwellnhof

50

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

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

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


6
문제를 피하고 테스트를 한 번에 단순화하십시오. 나도 좋아
— Joshua

11
-1이면을 a=b반환하지 않아야합니다 0. 0IEEE 754로 나누면 예외가 아닌 무한대를 얻을 수 있습니다. 문제를 피하고 있으므로 반환 0은 버그가 발생하기를 기다리는 것입니다. 고려하십시오 1/x + 1. 인 경우 올바른 값이 아닌 x=0결과가됩니다 1: 무한대.
— Cole Johnson

5
@ColeJohnson 정답도 무한대가 아닙니다 (한도의 측면을 지정하지 않는 한 오른쪽 = + inf, 왼쪽 = -inf, 지정되지 않음 = undefined 또는 NaN).
— Nick T

12
@ChrisHayes :이 질문은 XY의 문제가 될 수 있음을 인식 질문에 올바른 대답은 : meta.stackexchange.com/questions/66377/what-is-the-xy-problem은
— slebetman

17
@ColeJohnson Returning 0은 실제로 문제가 아닙니다. 이것이 OP가 문제에서하는 일입니다. 블록의 해당 부분에서 예외 또는 상황에 적합한 것을 넣을 수 있습니다. 을 반환하지 않으 0려면 질문에 대한 비판이 필요합니다. 확실히, OP처럼 행동한다고해서 그 답에 대한 공감대가 보장되지는 않습니다. 이 질문은 주어진 함수가 완료된 후 추가 계산과 관련이 없습니다. 아시다시피 프로그램의 요구 사항은을 반환해야 0합니다.
— jpmc26

25

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으로 나누어되지 않습니다.


11
에 란; 엄격하게 사실이 아닙니다. "숫자가 비교되지 않음"은 거짓이지만 비트 패턴은 동일합니다. 또한 -0.0은 +0.0과 true를 비교하도록 정의되며 비트 패턴이 다릅니다.
— Bathsheba 2019

1
@Bathsheba 나는이 특별한 경우를 고려하지 않았습니다. 의견 주셔서 감사합니다.
— Eran

2
@Eran, 0으로 나누면 부동 소수점에서 무한대가 반환됩니다. 질문에 추가했습니다.
— Thirler

2
@Prashant이지만 a == b가 true를 반환하기 때문에이 경우 분할이 발생하지 않습니다.
— Eran

3
실제로 0으로 나누기 위해 FP 예외를 얻을 수 있습니다 . IEEE-754 표준에 의해 정의 된 옵션이지만 대부분의 사람들이 "예외"로 의미하는 것은 아닙니다.)
— Voo

17

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

SMT 해결사 Z3는 소수점 연산 부동 정확한 IEEE을 지원합니다. Z3에게 숫자 a와 b같은 것을 찾도록 요청합시다 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)알고리즘이 맞습니까?" 와 같은 혼합 부동 소수점 및 정수 질문을 할 수도 있습니다 .


SMT Solver Z3에 대한 링크와 온라인 통역에 대한 링크를 추가 할 수 있습니까? 이 답변은 완전히 합법적 인 것처럼 보이지만 누군가는 이러한 결과가 잘못되었다고 생각할 수 있습니다.
— AL

12

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

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이 아닌 경우에도 무한대가 반환됩니다.


6

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

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

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


6

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를 보장하지 않습니다.

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


2

네가 생각할 수있는 사건을 생각할 수있어 이 이것을 일으킬 수 있습니다. 기본 10의 유사한 샘플이 있습니다. 실제로 이것은 기본 2에서 발생합니다.

부동 소수점 숫자는 과학적 표기법으로 다소 저장됩니다. 즉, 35.2를 보는 대신 저장되는 숫자는 3.52e2와 비슷합니다.

편의상 우리가 기본 10에서 작동하고 3 자리의 정확도를 갖는 부동 소수점 단위를 가지고 있다고 상상해보십시오. 10.0에서 9.99를 빼면 어떻게 되나요?

1.00e2-9.99e1

각 값에 동일한 지수를 제공하도록 이동

1.00e2-0.999e2

3 자리로 반올림

1.00e2-1.00e2

어 오!

이것이 일어날 수 있는지 여부는 FPU 디자인에 달려 있습니다. 두 배의 지수 범위가 매우 크기 때문에 하드웨어는 어느 시점에서 내부적으로 반올림해야하지만 위의 경우 내부적으로 1 자리 숫자 만 있으면 문제가 발생하지 않습니다.


1
감산을 위해 정렬 된 피연산자를 보유하는 레지스터는이 상황을 처리하기 위해 "가드 비트"라고하는 추가 2 비트를 보유해야합니다. 빼기가 가장 중요한 비트에서 차용을 야기하는 시나리오에서, 작은 피연산자의 크기는 큰 피연산자의 크기의 절반을 초과해야합니다 (단 하나의 추가 비트 만 가질 수 있음을 의미). 그렇지 않으면 결과는 최소한 더 작은 피연산자의 크기의 절반 (올바른 반올림을 보장하기에 충분한 정보와 하나 이상의 비트 만 필요함을 의미 함).
— supercat

1
“이것이 궁극적으로 FPU 설계에 따라 달라질 수 있는가?”아니요, Java 정의가 할 수 없다고 말했기 때문에 일어날 수 없습니다. FPU 디자인은 이와 관련이 없습니다.
— Pascal Cuoq

@PascalCuoq : 틀렸지 만 strictfp활성화되지 않은 경우 수정 하여 너무 작지만 double확장 정밀도 부동 소수점 값에 맞는 값을 계산할 수 있습니다.
— supercat

@supercat의 부재는 strictfp"중간 결과"의 값에만 영향을 미치며 docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4 에서 인용하고 있습니다 . a하고 b있는 double이들 값은 배정 밀도 값 그래서 변수 아닌 중간 결과는 따라서 2 ^ -1074의 배수이다. 이 두 배정 밀도 값의 빼는 결과적으로 2 ^ -1074의 배수이므로 지수 범위가 넓을수록 차이가 0 iff a == b 인 속성을 변경합니다.
— 파스칼 쿠 오크

@supercat 이것은 의미가 있습니다-이 작업을 수행하려면 추가 비트가 하나만 있으면됩니다.
— Keldor314

1

플로트 또는 배가 평등을 비교하지 않아야합니다. float 또는 double에 할당 한 숫자가 정확한지를 실제로 보장 할 수는 없습니다.

부동 소수점이 평등한지 비교하려면 값이 같은 값에 "충분한 지"확인해야합니다.

if ((first >= second - error) || (first <= second + error)

6
"Should n't have"는 약간 강력하지만 일반적으로 이것은 좋은 조언입니다.
— Mark Pattison 10

1
당신은 사실이지만 abs(first - second) < error(또는 <= error) 더 쉽고 간결하다.
— glglgl

3
대부분의 경우에 해당하지만 ( 모두 는 아님) 실제로 질문에 대답하지는 않습니다.
— milleniumbug

4
부동 소수점 숫자가 같은지 테스트하는 것이 종종 유용합니다. 신중하게 선택되지 않은 엡실론과 비교할 때 제정신이 없으며 평등을 테스트 할 때 엡실론과 비교할 때 제정신이 없습니다.
— tmyklebu

1
부동 소수점 키에서 배열을 정렬하면 부동 소수점 숫자와 엡실론을 비교하는 트릭을 사용하려고하면 코드가 작동하지 않을 수 있습니다. a == b 및 b == c라는 보장은 a == c가 더 이상 존재하지 않기 때문입니다. 해시 테이블의 경우에도 동일한 문제입니다. 평등이 전 이적이지 않으면 알고리즘이 중단됩니다.
— gnasher729 19

1

양수의 한계는 무한대 인 경향이 있으므로 음수의 한계는 음의 무한대 인 경향이 있기 때문에 0으로 나누기는 정의되지 않습니다.

언어 태그가 없으므로 이것이 C ++인지 Java인지 확실하지 않습니다.

double calculation(double a, double b)
{
     if (a == b)
     {
         return nan(""); // C++

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}

1

핵심 문제는 "너무 많은"소수 자릿수가있을 때, 예를 들어 숫자 값으로 쓸 수없는 double을 다룰 때 double (일명 float 또는 수학 언어의 실수)의 컴퓨터 표시가 잘못된다는 것입니다 ( pi 또는 1/3의 결과).

따라서 a == b는 a와 b의 이중 값으로 수행 할 수 없습니다 .a = 0.333 및 b = 1 / 3 일 때 a == b를 처리하는 방법은 무엇입니까? OS 대 FPU 대 숫자 대 언어 대 0 이후 3의 수에 따라 true 또는 false가됩니다.

어쨌든 컴퓨터에서 "이중 값 계산"을 수행하는 a==b경우 정확도를 다루어야하므로을 수행하는 대신을 수행 해야합니다.absolute_value(a-b)<epsilon 하며 엡실론은 알고리즘에서 당시 모델링 한 것과 관련이 있습니다. 모든 이중 비교에 엡실론 값을 가질 수는 없습니다.

간단히 말해 a == b를 입력하면 컴퓨터에서 부동 소수점 숫자로 변환 할 수없는 수학식이 있습니다.

추신 : 흠, 내가 여기에 대답하는 모든 것은 다른 사람들의 대답과 의견에 여전히 많거나 적습니다.


1

@malarres 응답과 @Taemyr 의견을 바탕으로 내 작은 기여는 다음과 같습니다.

public double calculation(double a, double b)
{
     double c = 2 / (a - b);

     // Should not have a big cost.
     if (isnan(c) || isinf(c))
     {
         return 0; // A 'whatever' value.
     }
     else
     {
         return c;
     }
}

내 요점은 말합니다 : 부서의 결과가 nan인지 inf인지 알 수있는 가장 쉬운 방법은 실제로 부서를 수행하는 것입니다.

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