[java] Java에서 float을 비교하기 위해 ==를 사용하면 무엇이 문제입니까?

이 java.sun 페이지 에 따르면 ==Java의 부동 소수점 숫자에 대한 동등 비교 연산자가 있습니다.

그러나이 코드를 입력하면 :

if(sectionID == currentSectionID)

내 편집기로 정적 분석을 실행하면 “JAVA0078 부동 소수점 값 ==”

==부동 소수점 값을 비교 하는 데 어떤 문제가 있습니까? 올바른 방법은 무엇입니까? 



답변

‘평등’에 대한 수레를 테스트하는 올바른 방법은 다음과 같습니다.

if(Math.abs(sectionID - currentSectionID) < epsilon)

여기서 엡실론은 원하는 정밀도에 따라 0.00000001과 같은 매우 작은 수입니다.


답변

부동 소수점 값은 약간 씩 벗어날 수 있으므로 정확하게 같은 것으로보고되지 않을 수 있습니다. 예를 들어, float를 “6.1”로 설정 한 다음 다시 인쇄하면 “6.099999904632568359375”와 같은 값이보고 될 수 있습니다. 이것은 수레가 작동하는 방식의 기본입니다. 따라서 등식을 사용하여 비교하지 않고 범위 내에서 비교합니다. 즉, 부동 소수점의 숫자가 비교하려는 숫자와의 차이가 특정 절대 값보다 작은 경우.

Register에 관한 기사는 이것이 왜 그런지에 대한 좋은 개요를 제공합니다. 유용하고 흥미로운 독서.


답변

다른 사람들이 말하는 내용의 이유를 설명하기 위해서입니다.

플로트의 이진 표현은 일종의 성가신입니다.

이진에서 대부분의 프로그래머는 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d의 상관 관계를 알고 있습니다.

다른 방법으로도 작동합니다.

.1b = .5d, .01b = .25d, .001b = .125, …

문제는 .1, .2, .3 등과 같이 대부분의 십진수를 나타내는 정확한 방법이 없다는 것입니다. 당신이 할 수있는 모든 것은 이진법으로 대략적인 것입니다. 숫자가 인쇄 될 때 시스템은 약간의 퍼지 반올림을 수행하여 .10000000000001 또는 .999999999999 대신 0.1을 표시합니다.

의견 편집 : 이것이 문제인 이유는 우리의 기대입니다. 우리는 .7 또는 .67 또는 .666667과 같이 소수점으로 변환 할 때 2/3가 퍼지 될 것으로 예상합니다. 그러나 .1이 2/3과 같은 방식으로 반올림되는 것을 자동으로 기대하지는 않습니다. 그리고 그것은 정확히 일어나고있는 일입니다.

그건 그렇고, 당신이 내부에 저장하는 숫자가 궁금하다면 이진 “과학 표기법”을 사용하는 순수한 이진 표현입니다. 따라서 10.75d를 저장하도록 지시하면 10은 1010b, 10은 .11b를 저장합니다. 따라서 101011을 저장하고 마지막에 몇 비트를 저장합니다. 소수점을 네 자리 오른쪽으로 이동합니다.

(기술적으로는 더 이상 소수점이 아니지만 이제는 이진 점이지만 해당 용어는 어떤 용도로든이 답변을 찾는 대부분의 사람들에게 상황을 더 잘 이해할 수있게하지 못했습니다.)


답변

부동 소수점 값을 비교하기 위해 ==를 사용하면 무엇이 잘못됩니까?

사실이 아니기 때문에 0.1 + 0.2 == 0.3


답변

플로트 (및 복식) 주위에 많은 혼란이 있다고 생각합니다. 정리하는 것이 좋습니다.

  1. 표준 호환 JVM [*] 에서 float를 ID로 사용하는 데 본질적으로 잘못된 것은 없습니다 . float ID를 단순히 x로 설정하고 아무 것도하지 않고 (즉, 산술을하지 않음) 나중에 y == x를 테스트하면 괜찮을 것입니다. 또한 HashMap에서 키로 사용하는 데 아무런 문제가 없습니다. 당신이 할 수없는 것은 x == (x - y) + y등의 등식을 가정하는 것입니다 . 사람들은 일반적으로 정수 유형을 ID로 사용하며 여기에있는 대부분의 사람들 이이 코드에 의해 벗어난 것을 볼 수 있으므로 실제적인 이유로 규칙을 따르는 것이 좋습니다 . double길이 만큼 많은 값이 values있으므로를 사용하면 아무 것도 얻지 못합니다 double. 또한 “사용 가능한 다음 ID”생성은 두 배로 까다로울 수 있으며 부동 소수점 산술에 대한 지식이 필요합니다. 문제의 가치가 없습니다.

  2. 반면에 두 개의 수학적으로 동등한 계산 결과의 수치 적 동등성에 의존하는 것은 위험합니다. 이는 10 진수에서 이진 표현으로 변환 할 때 반올림 오류 및 정밀도 손실로 인한 것입니다. 이것은 SO에 대해 논의되었습니다.

[*] “표준 호환 JVM”이라고 말하면 특정 뇌 손상 JVM 구현을 제외하고 싶었습니다. 참조 .


답변

이것은 Java에만 국한되지 않는 문제입니다. ==를 사용하여 두 개의 float / doubles / 십진수 유형 번호를 비교하면 저장 방식으로 인해 잠재적으로 문제가 발생할 수 있습니다. 단 정밀도 부동 소수점 (IEEE 표준 754에 따라)에는 32 비트가 있으며 다음과 같이 배포됩니다.

1 비트-부호 (0 = 양수, 1 = 음수)
8 비트-지수 (2 ^ x에서 x의 특수 (bias-127) 표현)
23 비트-Mantisa. 저장된 실제 숫자입니다.

만티 사는 문제의 원인입니다. 그것은 과학적 표기법과 비슷합니다.베이스 2 (이진)의 숫자 만 1.110011 x 2 ^ 5 또는 이와 비슷한 것으로 보입니다. 그러나 바이너리에서 첫 번째 1은 항상 1입니다 (0의 표현 제외)

따라서, 약간의 메모리 공간을 절약하기 위해 (pun 의도 된) IEEE는 1을 가정해야한다고 결정했다. 예를 들어 1011의 mantisa는 실제로 1.1011입니다.

이로 인해 비교 문제가 발생할 수 있습니다. 특히 0은 부동 소수점으로 정확하게 표현할 수 없으므로 특히 0입니다. 이것이 다른 답변으로 설명 된 부동 소수점 수학 문제 외에도 ==가 권장되지 않는 주된 이유입니다.

Java는 다양한 플랫폼에서 언어가 보편적이라는 점에서 고유 한 문제가 있습니다. 각 플랫폼은 고유 한 부동 형식을 가질 수 있습니다. 따라서 ==를 피하는 것이 더욱 중요합니다.

평등에 대해 두 개의 부동 소수점 (언어별로 생각하지 않는)을 비교하는 올바른 방법은 다음과 같습니다.

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

여기서 ACCEPTABLE_ERROR는 #defined 또는 0.000000001과 같은 다른 상수 또는 Victor가 이미 언급했듯이 필요한 정밀도가 있습니다.

일부 언어에는이 기능 또는이 상수가 내장되어 있지만 일반적으로 좋은 습관입니다.


답변

현재로서는 빠르고 쉬운 방법은 다음과 같습니다.

if (Float.compare(sectionID, currentSectionID) == 0) {...}

그러나 문서 는 마진 차이 값 ( 엡실론)을 명확하게 지정하지 않습니다. 는 항상 부동 소수점 계산에 @Victor의 답변 하지는 않지만 표준 언어 라이브러리의 일부이므로 합리적인 것이어야합니다.

그러나 더 높거나 맞춤화 된 정밀도가 필요한 경우

float epsilon = Float.MIN_NORMAL;
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

또 다른 솔루션 옵션입니다.