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


131

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

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

일반적인 경우에는 물론 그렇지 않습니다. 그러나 경우 ab매우 가까운 수 있습니다 (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-308b = 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에게 숫자 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)알고리즘이 맞습니까?" 와 같은 혼합 부동 소수점 및 정수 질문을 할 수도 있습니다 .


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.