이 부동 소수점 최적화가 허용됩니까?

나는 float큰 정수를 정확하게 표현하는 능력을 잃는 곳을 확인하려고 노력했습니다 . 그래서이 작은 조각을 썼습니다.

int main() {
    for (int i=0; ; i++) {
        if ((float)i!=i) {
            return i;
        }
    }
}

이 코드는 clang을 제외한 모든 컴파일러에서 작동하는 것 같습니다. Clang은 간단한 무한 루프를 생성합니다. Godbolt .

허용됩니까? 그렇다면 QoI 문제입니까?



답변

@Angew는 지적!=연산자는 양쪽에 동일한 유형을 필요로한다.
(float)i != i결과적으로 RHS가 떠 다니는 것을 촉진하므로 (float)i != (float)i.


g ++는 또한 무한 루프를 생성하지만 내부에서 작업을 최적화하지는 않습니다. 당신은 함께 INT-> 부동 소수점 변환 볼 수 있습니다 cvtsi2ss및 수행 ucomiss xmm0,xmm0비교 (float)i자체. (이것은 C ++ 소스가 @Angew의 대답이 설명하는 것처럼 생각한 것을 의미하지 않는다는 첫 번째 단서였습니다.)

x != xxNaN 이기 때문에 “정렬되지 않은”경우에만 true 입니다. ( INFINITYIEEE 수학에서 자신과 동일하지만 NaN은 그렇지 않습니다. NAN == NAN거짓, NAN != NAN참).

gcc7.4 및 이전 버전 jnp은 루프 분기 ( https://godbolt.org/z/fyOhW1 ) 로 코드를 올바르게 최적화합니다 . 피연산자 x != x 가 NaN이 아닌 한 계속 루프를 수행합니다 . (gcc8 이상은 또한 je루프의 브레이크 아웃을 확인 하여 NaN이 아닌 입력에 대해 항상 참이라는 사실을 기반으로 최적화에 실패합니다.) x86 FP는 순서가 지정되지 않은 상태에서 세트 PF를 비교합니다.


그리고 BTW는 clang의 최적화도 안전함 을 의미 합니다 . 단지 (float)i != (implicit conversion to float)i동일한 것으로 CSE 해야 i -> float하며 가능한 범위에서 NaN이 아님을 증명해야합니다 int.

(이 루프가 signed-overflow UB에 도달한다는 점을 감안할 때, ud2불법 명령을 포함하여 문자 그대로 원하는 asm을 방출 하거나 루프 본문이 실제로 무엇인지에 관계없이 빈 무한 루프 를 방출 할 수 있습니다.) 그러나 signed-overflow UB를 무시합니다. ,이 최적화는 여전히 100 % 합법적입니다.


GCC 는 부호있는 정수 오버플로를 잘 정의 하더라도-fwrapv 루프 본문을 최적화하지 못합니다 (2의 보수 순환으로 ). https://godbolt.org/z/t9A8t_

활성화 -fno-trapping-math해도 도움이되지 않습니다. (GCC의 기본은 불행하게도 활성화
-ftrapping-math에도 불구하고 그것의 GCC의 구현 / 버그를 파괴한다 .) 예외 가능성에 합리적인 아니에요 폭로와 INT-> 부동 소수점 변환이 때문에, (정확히 표현하기에 너무 큰 숫자) 예외 부정확 한 FP를 일으킬 수 있습니다 루프 본체를 최적화하십시오. ( 16777217부정확 한 예외가 마스크 해제되면 float 로 변환 하면 관찰 가능한 부작용이 발생할 수 있기 때문 입니다.)

그러나 -O3 -fwrapv -fno-trapping-math를 사용하면이를 빈 무한 루프로 컴파일하지 않는 것이 100 % 최적화를 놓쳤습니다. 가 없으면 #pragma STDC FENV_ACCESS ON마스킹 된 FP 예외를 기록하는 고정 플래그의 상태는 코드의 관찰 가능한 부작용이 아닙니다. 아니오 int-> float변환은 NaN을 초래할 x != x수 있으므로 사실 일 수 없습니다.


이러한 컴파일러는 모두 IEEE 754 단 정밀도 (binary32) float및 32 비트 를 사용하는 C ++ 구현에 최적화되어 int있습니다.

버그가 잡힌(int)(float)i != i 루프는 좁은 16 비트와 C ++ 구현에 UB있을 것입니다 int및 / 또는 넓은 float당신이 때렸어 때문에, 서명 정수 오버 플로우 UB A와 정확하게 표현할 수없는했던 첫 번째 정수에 도달하기 전에 float.

그러나 다른 구현 정의 선택 세트 아래의 UB는 x86-64 System V ABI로 gcc 또는 clang과 같은 구현을 위해 컴파일 할 때 부정적인 결과를 초래하지 않습니다.


BTW, 에서 정의 된 FLT_RADIXand 에서이 루프의 결과를 정적으로 계산할 수 있습니다 . 또는 적어도 이론적으로 는 Posit / unum과 같은 다른 종류의 실수 표현보다는 IEEE 플로트 모델에 실제로 적합 하다면 할 수 있습니다 .FLT_MANT_DIG<climits>float

ISO C ++ 표준이 float동작 에 대해 얼마나 잘하고 있는지, 고정 너비 지수 및 유효 필드를 기반으로하지 않은 형식이 표준을 준수하는지 여부는 확실하지 않습니다.


댓글에서 :

@geza 결과 번호를 듣고 싶습니다!

@nada : 16777216입니다

이 루프를 인쇄 / 반환 할 수 있다고 주장 16777216하십니까?

업데이트 : 해당 댓글이 삭제되었으므로 삭제되지 않은 것 같습니다. 아마도 OP는 float32 비트로 정확하게 표현할 수없는 첫 번째 정수 앞의 인용 부호 일 것 float입니다. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values 즉,이 버그가있는 코드로 확인하려는 내용.

버그 수정 버전은 물론 16777217그 이전의 값보다는 정확하게 표현할 수 없는 첫 번째 정수인 print 합니다.

(높은 부동 소수점 값은 모두 정확한 정수이지만 유효 폭보다 높은 지수 값에 대해 2, 4, 8 등의 배수입니다. 더 높은 정수 값이 많이 표시 될 수 있지만 마지막 자리에는 1 단위가 있습니다. (유효 값의)는 1보다 크므로 연속 된 정수가 아닙니다. 가장 큰 유한 float은 2 ^ 128 바로 아래이며 짝수에 비해 너무 큽니다 int64_t.)

컴파일러가 원래 루프를 종료하고 인쇄하면 컴파일러 버그입니다.


답변

내장 연산자 !=는 피연산자가 동일한 유형이어야하며 필요한 경우 승격 및 변환을 사용하여이를 달성합니다. 즉, 조건은 다음과 같습니다.

(float)i != (float)i

절대 실패해서는 안되며 결국 코드가 오버플 i로되어 프로그램에 Undefined Behaviour를 제공합니다. 따라서 모든 동작이 가능합니다.

확인하려는 항목을 올바르게 확인하려면 결과를 int다음으로 다시 캐스팅해야합니다 .

if ((int)(float)i != i)