float를 다른 변수에 복사하면 동일합니까?


167

내가 사용하는 것을 알고 ==부동 소수점 변수의 평등을 확인하는 것은 좋은 방법이 아닙니다. 그러나 나는 다음 진술로 그것을 알고 싶습니다.

float x = ...

float y = x;

assert(y == x)

이후 y에서 복사 x, 주장은 사실 일 것이다?


78
실제 코드를 사용한 데모로 실제로 불평등을 증명하는 사람에게 50의 현상금을 제공하겠습니다. 80 비트와 64 비트가 실제로 작동하는 것을보고 싶습니다. 또한 하나의 변수가 레지스터에 있고 다른 변수가 아닌 것을 보여주는 생성 된 어셈블러 코드에 대한 설명을위한 또 다른 50 (또는 불평등의 이유가 무엇이든 저수준에서 설명하고 싶습니다).
토마스 웰러

1
@ThomasWeller GCC 버그 : gcc.gnu.org/bugzilla/show_bug.cgi?id=323 ; 그러나 방금 x86-64 시스템에서 재현하려고 시도했지만 -ffast-math에서도 마찬가지입니다. 32 비트 시스템에서 오래된 GCC가 필요하다고 생각합니다.
pjc50

5
@ pjc50 : 실제로 버그 323을 재현하려면 80 비트 시스템이 필요합니다. 문제를 일으킨 80x87 FPU입니다. x86-64는 SSE FPU를 사용합니다. 여분의 비트는 32 비트 플로트에 값을 흘릴 때 반올림되므로 문제가 발생합니다.
MSalters

4
MSalters의 이론이 맞다면 (그리고 그것이 옳다고 생각되는 경우) 32 비트 ( -m32) 를 컴파일 하거나 GCC에 x87 FPU ( -mfpmath=387) 를 사용하도록 지시 하여 재현 할 수 있습니다 .
코디 그레이

4
"48 비트"를 "80 비트"로 변경 한 다음 @Hot에서 "신화적인"형용사를 제거 할 수 있습니다. 그것이 바로 귀하의 의견 바로 전에 논의 된 내용입니다. x87 (x86 아키텍처 용 FPU)은 "확장 정밀도"형식 인 80 비트 레지스터를 사용합니다.
코디 그레이

답변:


125

assert(NaN==NaN);kmdreko가 지적한 경우 외에도 80 비트 부동 소수점이 메모리에 임시로 저장된 후 나중에 레지스터 내부에 저장된 값과 비교되는 x87-math가있는 상황이 발생할 수 있습니다.

가능한 최소 예제 -O2 -m32. 다음 과 같이 컴파일하면 gcc9.2에서 실패합니다 .

#include <cassert>

int main(int argc, char**){
    float x = 1.f/(argc+2);
    volatile float y = x;
    assert(x==y);
}

Godbolt 데모 : https://godbolt.org/z/X-Xt4R

volatile충분한 레지스터 압력을 만들기 위해 관리하는 경우 아마 한 것으로, 생략 할 수 y저장하고 메모리에서 다시로드 (그러나 컴파일러만큼, 모두 함께 비교를 생략하지 혼동).

GCC FAQ 참조를 참조하십시오.


2
float표준 정밀도와 추가 정밀도를 비교할 때 추가 비트를 고려하는 것이 이상하게 보입니다 .
Nat

13
그것은 @Nat 입니다 이상; 이것은 버그 입니다.
궤도에서 가벼운 경주

13
@ThomasWeller 아니요, 합리적인 상입니다. 답변이 적합하지 않은 행동임을 지적하고 싶지만
궤도에서

4
나는이 답변을 확장하여 어셈블리 코드에서 정확히 무슨 일이 일어나는지 지적하고 이것이 실제로 표준을 위반한다는 것을 알 수 있습니다-언어 변호사라고 부르지는 않을 것이기 때문에 모호하지 않은 것을 보장 할 수는 없습니다 그 행동을 명시 적으로 허용하는 조항. OP가 버그가없고 완벽하게 호환되는 컴파일러 (실제로는 존재하지 않음)가 아닌 실제 컴파일러의 실제 합병증에 더 관심이 있다고 생각합니다.
chtz

4
-ffloat-store이것을 언급 할 가치가있는 것으로 보입니다.
OrangeDog

116

에 대한 비교 가 항상 거짓 이므로 (예, 짝수 ) xis NaN인 경우 사실 NaN아닙니다NaN == NaN . 다른 모든 경우 (정상 값, 비정규 값, 무한대, 0)의 경우이 어설 션이 적용됩니다.

부동 소수점 을 피하기 ==위한 조언은 부동 소수점 숫자가 산술 표현식에서 사용될 때 많은 결과를 정확하게 표현할 수 없기 때문에 계산에 적용됩니다 . 할당은 계산이 아니며 할당이 원래와 다른 값을 산출 할 이유가 없습니다.


표준을 준수하는 경우 확장 정밀 평가는 문제가되지 않아야합니다. <cfloat>C 에서 상속 된 것 [5.2.4.2.2.8] ( 강조 광산 ) :

할당 및 캐스트 (모든 추가 범위 및 정밀도를 제거함)를 제외하고 , 일반 피연산자 및 부동 상수에 종속 된 부동 피연산자 및 값이있는 연산의 값은 범위 및 정밀도가 필요한 것보다 큰 형식으로 평가됩니다. 유형.

그러나 주석에서 지적했듯이 특정 컴파일러, 빌드 옵션 및 대상 있는 일부 경우에는 역설적으로 거짓이 수 있습니다.


10
x첫 번째 행의 레지스터에서 계산되는 경우 a의 최소값보다 더 높은 정밀도를 유지합니다 float. 은 y = x단지 유지, 메모리에있을 수 있습니다 float정밀도를. 그런 다음 레지스터에 대한 메모리로 다른 정밀도로 평등 테스트를 수행하므로 보장 할 수 없습니다.
David Schwartz

5
x+pow(b,2)==x+pow(a,3)다를 수 auto one=x+pow(b,2); auto two=y+pow(a,3); one==two(intermediste 값 FPU 80ish 비트에있는 동안, 하나 / 두 RAM의 64 개 비트 값을 인 경우) 다른 것보다 더 정밀하여 비교할 수 있기 때문에 하나. 따라서 과제는 때때로 무언가를 할 수 있습니다.
Yakk-Adam Nevraumont

22
@evg 물론! 내 대답은 단순히 표준을 따릅니다. 특히 빠른 계산을 가능하게 할 때 컴파일러가 비공식적이라고 말하면 모든 베팅이 해제됩니다.
kmdreko

11
@Voo 내 답변에서 인용문을 참조하십시오. RHS의 값은 LHS의 변수에 지정됩니다. LHS의 결과 가치가 RHS의 가치와 다르다는 법적 정당성은 없습니다. 여러 컴파일러가 이와 관련하여 버그가 있음을 이해합니다. 그러나 무언가가 레지스터에 저장되어 있는지 여부는 관련이 없습니다.
궤도에서 가벼움

6
@Voo : ISO C ++에서 타입 너비로 반올림하는 것은 모든 할당에서 발생합니다. x87을 대상으로하는 대부분의 컴파일러에서 실제로 컴파일러가 스 필링 / 리로드를 결정할 때만 발생합니다. gcc -ffloat-store엄격한 준수 를 위해 이를 강제 할 수 있습니다 . 그러나이 질문은 x=y; x==y; 중간에 var에 아무것도하지 않고 관한 것입니다. float에 맞게 이미 반올림 된 경우 ydouble 또는 long double 및 back으로 변환해도 값이 변경되지 않습니다. ...
Peter Cordes

34

예, y확실히의 값에 걸릴 것입니다 x:

[expr.ass]/2: 단순 할당 (=)에서 왼쪽 피연산자가 참조하는 객체는 해당 값을 오른쪽 피연산자의 결과로 대체하여 수정됩니다 ([defns.access]).

다른 값을 지정할 여지가 없습니다.

(다른 사람들은 동등성 비교 ==가 그럼에도 불구하고 falseNaN 값 에 대해 평가 될 것이라고 이미 지적했다 .)

부동 소수점의 일반적인 문제 ==는 자신 이 생각하는 가치를 쉽게 갖지 못한다 는 것입니다. 여기서 우리는 두 값이 모두 동일하다는 것을 알고 있습니다.


7
@ThomasWeller 결과적으로 비준수 구현에서 알려진 버그입니다. 그래도 언급하기 좋습니다!
궤도에서 가벼운 경주

처음에 나는 "가치"와 "결과"의 차이를 변호하는 언어 변호사가 왜곡 될 것이라고 생각했지만,이 차이가 C2.2, 7.1.6의 언어와 차이가있을 필요는 없다. C3.3, 7.1.6; 인용 표준 초안의 C4.2, 7.1.6 또는 C5.3, 7.1.6.
에릭 타워

@EricTowers 죄송합니다. 해당 참조를 명확히 할 수 있습니까? 나는 당신이 가리키는 것을 찾지 못했습니다
궤도에서 가벼운 경주

@ LightnessRacesBY-SA3.0 : C . C2.2 , C3.3 , C4.2C5.3 .
에릭 타워

@EricTowers 그래, 아직 안 따라와. 첫 번째 링크는 부록 C 색인으로 이동합니다 (아무 말도하지 않음). 다음 네 개의 링크는 모두로 이동합니다 [expr]. 링크를 무시하고 인용에 초점을 맞추려면 C.5.3 과 같이 "값"또는 "결과"라는 용어를 사용하지 않는 것처럼 혼동 될 수 있습니다. 일반적인 영어 상황에서 "결과"를 한 번 사용하십시오. 아마도 표준이 구별되는 부분을 더 명확하게 설명하고 이러한 일에 대한 명확한 인용을 제공 할 수있을 것입니다. 감사!
궤도에서 가벼운 경주

3

예, 모든 경우 (NaN 및 x87 문제는 무시) 이것이 사실입니다.

당신이 memcmp그들에 대해하면 NaN과 sNaN을 비교하면서 평등을 테스트 할 수 있습니다. 또한 컴파일러는 변수의 주소를 사용하여 값을 float80 비트 대신 32 비트로 강제 변환해야합니다 . 이것은 x87 문제를 제거합니다. 여기서 두 번째 주장은 ==NaN을 참으로 비교하지 않을 것이라는 것을 보여 주려는 의도 는 아닙니다.

#include <cmath>
#include <cassert>
#include <cstring>

int main(void)
{
    float x = std::nan("");
    float y = x;
    assert(!std::memcmp(&y, &x, sizeof(float)));
    assert(y == x);
    return 0;
}

NaN의 내부 표현이 다르면 (즉, 가수가 다름) memcmp사실과 비교되지 않습니다.


1

일반적인 경우에는 true로 평가됩니다. (또는 주장 진술은 아무것도하지 않을 것입니다)

편집 :

'일반적인 경우'라는 말은 다른 사용자가 지적한 것처럼 NaN 값 및 80x87 부동 소수점 단위와 같은 앞에서 언급 한 시나리오를 제외한다는 의미입니다.

오늘날의 맥락에서 8087 칩이 더 이상 사용되지 않기 때문에 문제는 다소 분리되어 있으며 사용 된 부동 소수점 아키텍처의 현재 상태에 적용 할 수 있습니다 .NaN을 제외한 모든 경우에 해당됩니다.

(8087에 대한 참조 - https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm )

좋은 예를 재현 해 준 @chtz와 NaN을 언급 한 @kmdreko의 쿠도스 (Kodos) – 이전에는 그것들에 대해 몰랐습니다!


1
메모리에서로드되는 x동안 부동 소수점 레지스터에있을 수 있다고 생각했습니다 y. 메모리가 레지스터보다 정밀도가 떨어지면 비교에 실패 할 수 있습니다.
David Schwartz

1
그것은 거짓의 경우 일 수 있습니다. 나는 그렇게 생각하지 않았습니다. (OP가 특별한 경우를 제공하지 않았기 때문에 추가 제한이 없다고 가정합니다)
Anirban166

1
나는 당신이 무슨 말을하는지 이해하지 못합니다. 질문을 이해하면 OP는 플로트를 복사 한 다음 동등성 테스트가 성공하는지 묻습니다. 당신의 대답은 "예"라고 말하는 것 같습니다. 나는 왜 대답이 아니오가 아닌지 묻고 있습니다.
David Schwartz

6
수정하면이 답변이 잘못됩니다. C ++ 표준에서는 대입에서 값을 대상 유형으로 변환해야합니다. 식의 평가에는 초과 정밀도가 사용될 수 있지만 대입을 통해 유지되지 않을 수 있습니다. 값이 레지스터 또는 메모리에 유지되는지 여부는 중요하지 않습니다. C ++ 표준은 코드 작성시 float추가 정밀도가없는 값 이어야합니다 .
Eric Postpischil

2
@AProgrammer 이론적으로 (n 매우) 버그가있는 컴파일러가 int a=1; int b=a; assert( a==b );주장을 던질 수 있다고 생각하면 올바르게 작동하는 컴파일러와 관련 하여이 질문에 대답하는 것이 합리적이라고 생각합니다 (일부 컴파일러의 일부 버전은 / -알려져서 잘못되었습니다). 실제로 어떤 이유로 컴파일러가 레지스터 저장 할당의 결과에서 추가 정밀도를 제거하지 않으면 해당 값을 사용 하기 전에 그렇게해야합니다 .
TripeHound

-1

예, NaN 인 경우를 제외하고 항상 True를 반환 합니다. 변수 값이 NaN 이면 항상 False를 반환합니다 .

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