program story

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

inputbox 2020. 9. 10. 07:48
반응형

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


나는 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.

(이 루프가 서명 된 오버플로 UB에 도달한다는 점을 감안할 때, ud2불법 명령을 포함하여 문자 그대로 원하는 모든 asm을 방출 하거나 루프 본문이 실제로 무엇인지에 관계없이 빈 무한 루프 를 방출 할 수 있습니다.) 그러나 서명 된 오버플로 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)

참고 URL : https://stackoverflow.com/questions/57003891/is-this-floating-point-optimization-allowed

반응형