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


177

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

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

if(sectionID == currentSectionID)

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

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


29
float를 ==와 비교하는 것은 문제가되기 때문에 ID로 사용하는 것은 현명하지 않습니다. 예제 코드의 이름은 현재하고있는 작업임을 나타냅니다. long 정수 (longs)가 선호되며 ID의 사실상 표준입니다.
Carl Manaster


4
예, 그게 임의의 예입니까, 아니면 실제로 float를 ID로 사용합니까? 이유가 있습니까?
퍼 Wiklander 당

5
"float field의 경우 Float.compare 메소드를 사용하고 double field의 경우 Double.compare를 사용하십시오. float 및 double 필드의 특수 처리는 Float.NaN, -0.0f 및 유사한 이중 상수의 존재로 인해 필요합니다. 자세한 내용은 Float.equals 설명서를 참조하십시오. " (Joshua Bloch : Effective Java)
lbalazscs

답변:


211

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

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

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


26
고정 엡실론이 항상 좋은 아이디어가 아닌 이유 는 허용되는 답변 ( cygnus-software.com/papers/comparingfloats/comparingfloats.htm ) 의 링크를 참조하십시오 . 구체적으로, 비교되는 플로트의 값이 커지거나 작아 질수록 엡실론은 더 이상 적합하지 않습니다. (그러나 float 값이 모두 비교적 합리적인 경우 epsilon을 사용하는 것이 좋습니다.)
PT

1
@PT 그는 엡실론에 하나의 숫자를 곱하고 if(Math.abs(sectionID - currentSectionID) < epsilon*sectionID그 문제를 해결하기 위해 기능을 변경할 수 있습니까?
enthusiasticgeek

3
이것은 지금까지 가장 좋은 대답 일지 모르지만 여전히 결함이 있습니다. 엡실론은 어디서 구할 수 있습니까?
Michael Piefel

1
@MichaelPiefel은 이미 "원하는 정밀도에 따라"라고 말합니다. 플로트는 본질적으로 물리적 가치와 비슷합니다. 전체 부정확성에 따라 제한된 수의 위치에만 관심이 있습니다.
ivan_pozdeev

그러나 OP는 실제로 평등을 테스트하기를 원했으며 신뢰할 수없는 것으로 알려져 있기 때문에 다른 방법을 사용해야합니다. 아직도, 나는 그의 "원하는 정밀도"가 무엇인지 알지 못한다. 따라서 당신이 원하는 모든 것이 더 신뢰할 수있는 평등 테스트라면, 질문은 여전히 ​​남아 있습니다 : 엡실론은 어디서 구할 수 있습니까? 나는 Math.ulp()이 질문에 대한 답을 제안 했다.
Michael Piefel

53

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

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


@ kevindtimm : 그래서 당신은 당신이 숫자를 알고 싶을 때마다 6.1과 같다면 (숫자 == 6.099999904632568359375) 등식 테스트를 할 것입니다 ... 예 맞습니다 ... 컴퓨터의 모든 것이 엄격히 결정적입니다. 수레에 사용 된 근사값은 수학 문제를 수행 할 때 반 직관적이라는 것입니다.
Newtopian

부동 소수점 값은 매우 특정한 하드웨어 에서만 결정적으로 부정확 합니다 .
스튜어트 P. 벤틀리

1
@Stuart 나는 잘못 생각할 수 있지만 FDIV 버그가 비 결정적이라고 생각하지 않습니다. 하드웨어가 제공 한 답변은 사양을 준수하지 않았지만 동일한 계산이 항상 동일한 잘못된 결과를 생성한다는 점에서 결정 론적이었습니다.
Gravity

@Gravity 특정 행동에 대해 어떤 행동이 결정적이라고 주장 할 수 있습니다.
스튜어트 P. 벤틀리

부동 소수점 이 정확하지 않습니다. 모든 부동 소수점 값이 그대로입니다. 부정확 한 점은 부동 소수점 계산 의 결과입니다 . 그러나 조심하십시오! 프로그램에서 0.1과 같은 것을 볼 때 부동 소수점 값이 아닙니다. 부동 소수점 리터럴 --- 컴파일러가 계산 을 수행하여 부동 소수점 값으로 변환하는 문자열입니다 .
Solomon Slow

22

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

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

이진에서 대부분의 프로그래머는 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을 저장하고 마지막에 몇 비트를 저장합니다. 소수점을 네 자리 오른쪽으로 이동합니다.

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


1
@Matt K-음, 고정 소수점이 아닙니다. "소수점 [N] 비트를 오른쪽으로 이동하기 위해 끝에 약간의 비트를 저장하는 경우"는 부동 소수점입니다. 고정 점은 기수 점의 위치를 ​​고정시킵니다. 또한 일반적으로 이항 (?) 지점을 이동하면 항상 가장 왼쪽 위치에 '1'을 남겨 둘 수 있기 때문에 앞의 '1'을 생략하고 이렇게 해방 된 공간을 할당하는 일부 시스템을 찾을 수 있습니다 (1). 비트!) 지수의 범위를 확장합니다.
JustJeff

이 문제는 이진 대 십진수 표현과 관련이 없습니다. 십진 부동 소수점을 사용하면 여전히 (1/3) * 3 == 0.9999999999999999999999999999와 같은 것들이 있습니다.
dan04

2
@ dan04 예, 1/3에는 십진 OR 이진 표현이 없으므로 삼진 표현이 있으며 올바르게 변환됩니다 :). 내가 열거 한 숫자 (.1, .25 등)는 모두 완벽한 10 진수 표현을 갖지만 이진 표현은 없으며 사람들은 "정확한"표현을 가진 사람들에게 익숙합니다. BCD는 그것들을 완벽하게 처리 할 것입니다. 그 차이입니다.
Bill K

1
문제의 실제 원인을 설명하기 때문에 더 많은 투표를해야합니다.
레위

19

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

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


7
무엇에 대해 Float.compare(0.1f+0.2f, 0.3f) == 0?
물병 자리 힘

0.1f + 0.2f == 0.3f이지만 0.1d + 0.2d! = 0.3d. 기본적으로 0.1 + 0.2는 두 배입니다. 0.3도 두 배입니다.
burnabyRails

12

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

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

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

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


float를 ID로 사용하는 경우을 사용 ==하지 않고 비교 equals하거나 다른 것과 비교하는 float이 테이블에 저장되지 않도록주의해야 합니다. 그렇지 않으면, 다양한 입력이 공급 될 때 식에서 생성 될 수있는 고유 한 결과 수를 세는 프로그램은 모든 NaN 값을 고유 한 것으로 간주 할 수 있습니다.
supercat

위의 내용은 Float을 의미 float합니다.
quant_dev 22시 27 분

무슨 소리 야 Float? 고유 한 float값 의 테이블을 작성하려고하고 이를 값과 비교 ==하면 끔찍한 IEEE-754 비교 규칙으로 인해 테이블에 NaN값 이 넘칠 수 있습니다.
supercat

float유형에는 equals메소드 가 없습니다 .
quant_dev

Ah-- equals인스턴스 메소드를 의미하는 것이 아니라 Float타입의 두 값을 비교 하는 정적 메소드 ( 클래스 내에서 생각 )입니다 float.
supercat

9

이것은 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가 이미 언급했듯이 필요한 정밀도가 있습니다.

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


3
Java에는 float에 대해 정의 된 동작이 있습니다. 플랫폼에 따라 다릅니다.
Yishai

9

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

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

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

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

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

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


1
연결 한 문서에 "f1이 f2와 숫자가 같으면 값 0"이 표시 (sectionId == currentSectionId)되어 부동 소수점에 대해 정확하지 않은 것과 같습니다 . 엡실론 방법은이 대답에있는 더 나은 방법입니다 : stackoverflow.com/a/1088271/4212710
typoerrpr

8

반올림 오류로 인해 시작점 값이 신뢰할 수 없습니다.

따라서 sectionID와 같은 키 값으로 사용해서는 안됩니다. 대신 정수를 사용하거나 가능한 값이 충분하지 않은 long경우 정수를 사용하십시오 int.


2
동의했다. 이것이 ID이기 때문에 부동 소수점 산술로 문제를 복잡하게 할 이유가 없습니다.
요니

2
아니면 길다. 앞으로 생성되는 고유 ID 수에 따라 int가 충분히 크지 않을 수 있습니다.
Wayne Hartman

float와 비교하여 double이 얼마나 정확합니까?
아빈 드 마니

1
@ArvindhMani doubles는 훨씬 정확하지만 부동 소수점 값이기도하므로 내 대답은 float및을 모두 포함해야합니다 double.
Eric Wilson

7

이전 답변 외에도 다음과 관련된 이상한 행동이 있음을 알고 있어야합니다. -0.0f 하고 +0.0f(그들이 ==아니라 equals)과 Float.NaN(그것은이다 equals하지만== ) (희망 나는 그 권리있어! - 아아, 그것을하지 않습니다).

편집 : 확인하자!

import static java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

IEEE / 754에 오신 것을 환영합니다.


무언가 ==이면 비트와 동일합니다. 그들은 어떻게 equals ()가 될 수 없습니까? 어쩌면 당신은 그것을 뒤로 가지고 있습니까?
mkb

@Matt NaN은 특별합니다. Java에서 Double.isNaN (double x)은 실제로 {return x! = x; } ...
quant_dev

1
float를 사용 ==한다고해서 숫자가 "비트와 동일"하다는 의미는 아닙니다 (동일한 숫자는 다른 비트 패턴으로 표시 될 수 있지만 그 중 하나만 정규화 된 형식 임). 물론, -0.0f그리고 0.0f상이한 비트 패턴 (부호 비트가 상이하다)으로 표시되지만와 동일하게 비교된다 ==(그러나 함께 equals). ==비트 비교에 대한 귀하의 가정 은 일반적으로 말하면 잘못입니다.
Pavel Minaev


5

Float.floatToIntBits ()를 사용할 수 있습니다.

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

1
당신은 올바른 길을 가고 있습니다. floatToIntBits ()는 올바른 방법이지만 Float의 내장 equals () 함수를 사용하는 것이 더 쉽습니다. 여기를 참조하십시오 : stackoverflow.com/a/3668105/2066079 . 기본 equals ()가 floatToIntBits를 내부적으로 사용한다는 것을 알 수 있습니다.
dberm22

1
Float 오브젝트 인 경우 예입니다. 프리미티브에 위의 방정식을 사용할 수 있습니다.
aamadmi

4

우선, 그들은 부유물입니까 아니면 부유물입니까? 그중 하나가 Float 인 경우 equals () 메소드를 사용해야합니다. 또한 정적 Float.compare 메서드를 사용하는 것이 가장 좋습니다.


4

다음은 최상의 정밀도를 자동으로 사용합니다.

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

물론, 5 개 이상의 ULP ( '마지막 장소')를 선택할 수 있습니다.

아파치 커먼즈 라이브러리에있어 경우, Precision클래스가 compareTo()equals()엡실론과 ULP 모두.


배가 플로트를 변경할 때, 이러한 방법은 (0.0, 0.1 + 0.2-0.3) == false를 isDoubleEqual로 작동하지 않는
hychou

이것을 다루기 위해서는 10_000_000_000_000_000L과 같은 것이 더 필요 double합니다.
Michael Piefel

3

==가 되길 원하지만 123.4444444444443! = 123.4444444444442



2

동일한 실수를 생성하는 두 개의 다른 계산이 반드시 동일한 부동 소수점 숫자를 생성하지는 않습니다. 계산 결과를 비교하기 위해 ==를 사용하는 사람들은 일반적으로 이것에 놀라게되므로 경고는 미묘하고 버그를 재현하기 어려운 것을 표시하는 데 도움이됩니다.


2

sectionID 및 currentSectionID라는 이름의 플로트를 사용하는 아웃소싱 코드를 처리하고 있습니까? 그냥 궁금해서

@Bill K : "float의 이진 표현은 일종의 성가신 일입니다." 어떻게 요? 어떻게하면 좋을까요? 어떤 숫자도 절대 끝나지 않기 때문에 제대로 표현할 수없는 숫자가 있습니다. Pi는 좋은 예입니다. 근사치 만 가능합니다. 더 나은 솔루션이 있으면 인텔에 문의하십시오.


1

다른 답변에서 언급했듯이 복식에는 작은 편차가있을 수 있습니다. 또한 "허용 가능한"편차를 사용하여 비교할 고유 한 방법을 작성할 수 있습니다. 그러나 ...

복식 비교를위한 아파치 클래스가 있습니다 : org.apache.commons.math3.util.Precision

그것은 몇 가지 흥미로운 상수를 포함 SAFE_MIN하고EPSILON 간단한 산술 연산의 가능한 최대 편차입니다.

또한 복식, 동등 또는 반올림을 비교하는 데 필요한 방법을 제공합니다. (ulps 또는 절대 편차 사용)


1

한 줄로 대답하면 다음을 사용해야합니다.

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

관련 연산자를 올바르게 사용하는 방법에 대해 더 배우기 위해 여기에서 몇 가지 사례를 자세히 설명합니다. 일반적으로 Java에서 문자열을 테스트하는 세 가지 방법이 있습니다. ==, .equals () 또는 Objects.equals ()를 사용할 수 있습니다.

그것들은 어떻게 다릅니 까? == 문자열에서 참조 품질을 테스트하여 두 개체가 동일한 지 여부를 확인합니다. 반면에 .equals ()는 두 문자열이 논리적으로 동일한 값인지 여부를 테스트합니다. 마지막으로 Objects.equals ()는 두 문자열의 null을 테스트 한 다음 .equals ()를 호출할지 여부를 결정합니다.

사용하기에 이상적인 연산자

세 사업자는 각각 고유 한 강점과 약점을 가지고 있기 때문에 이것은 많은 논쟁의 대상이되었습니다. 예를 들어, ==는 종종 객체 참조를 비교할 때 선호되는 옵션이지만 문자열 값도 비교하는 것처럼 보일 수 있습니다.

그러나 Java는 가치를 비교하고 있지만 실제로는 그렇지 않다는 환상을 만들어 내기 때문에 하락 가치입니다. 아래 두 가지 경우를 고려하십시오.

사례 1 :

String a="Test";
String b="Test";
if(a==b) ===> true

사례 2 :

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

따라서 설계된 특정 속성을 테스트 할 때 각 연산자를 사용하는 것이 좋습니다. 그러나 거의 모든 경우에 Objects.equals ()는보다 보편적 인 연산자이므로 웹 개발자가이를 선택하는 경험이 있습니다.

여기에서 자세한 정보를 얻을 수 있습니다 : http://fluentthemes.com/use-compare-strings-java/


-2

올바른 방법은

java.lang.Float.compare(float1, float2)

7
Float.compare (float1, float2)는 int를 반환하므로 if 조건에서 float1 == float2 대신 사용할 수 없습니다. 또한이 경고가 참조하는 근본적인 문제를 실제로 해결하지는 않습니다. float가 수치 계산의 결과 인 경우 float1! = float2는 반올림 오류로 인해 발생할 수 있습니다.
quant_dev

1
오른쪽, 붙여 넣기를 복사 할 수 없으므로 먼저 문서를 확인해야합니다.
Eric

2
float1 == float2 대신에 할 수있는 것은 Float.compare (float1, float2) == 0입니다.
deterb

29
이것은 당신에게 아무것도 사지 않습니다-당신은 여전히 ​​얻습니다Float.compare(1.1 + 2.2, 3.3) != 0
Pavel Minaev

-2

반올림 오류를 줄이는 한 가지 방법은 float 대신 double을 사용하는 것입니다. 이렇게해도 문제가 해결되지는 않지만 프로그램의 오류 양이 줄어들고 float은 거의 최선의 선택이 아닙니다. 이모.

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