programing

Win32에서 서명되지 않은 int에 대한 더블캐스트가 2,147,483,648로 잘립니다.

javaba 2022. 8. 13. 22:01
반응형

Win32에서 서명되지 않은 int에 대한 더블캐스트가 2,147,483,648로 잘립니다.

다음 코드 컴파일:

double getDouble()
{
    double value = 2147483649.0;
    return value;
}

int main()
{
     printf("INT_MAX: %u\n", INT_MAX);
     printf("UINT_MAX: %u\n", UINT_MAX);

     printf("Double value: %f\n", getDouble());
     printf("Direct cast value: %u\n", (unsigned int) getDouble());
     double d = getDouble();
     printf("Indirect cast value: %u\n", (unsigned int) d);

     return 0;
}

출력(MSVC x86) :

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483648
Indirect cast value: 2147483649

출력(MSVC x 64):

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483649
Indirect cast value: 2147483649

Microsoft 문서에서는 변환 시 부호 있는 정수 최대값에 대한 언급은 없습니다.double로로 합니다.unsigned int.

의 값INT_MAX to are are 、 are 、 려 、 are 、 are 、 are 。2147483648함수의 반환일 때.

Visual Studio 2019를 사용해서 프로그램을 만들고 있습니다.이것은 gcc에서는 발생하지 않습니다.

가가뭘 ?못 ???요?double로로 합니다.unsigned int

컴파일러 버그...

는 @anastaciu를 합니다.__ftol2_sse이것은 숫자를 부호 있는 길이로 변환하는 것 같습니다.루틴명은ftol2_sse이는 SSE 지원 기계이지만 플로트가 x87 부동 소수점 레지스터에 있기 때문입니다.

; Line 17
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp, 8

반면 간접 캐스팅은 그렇다.

; Line 18
    call    _getDouble
    fstp    QWORD PTR _d$[ebp]
; Line 19
    movsd   xmm0, QWORD PTR _d$[ebp]
    call    __dtoui3
    push    eax
    push    OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp, 8

변수에 후하고 SSE를 호출합니다.__dtoui3두 번에서 서명 없이 변환하는 루틴입니다

직접 주조물의 거동은 C89에 준거하지 않으며 이후 개정판에도 준거하지 않는다. 심지어 C89도 다음과 같이 명시하고 있다.

정수형 값을 부호 없는 유형으로 변환한 나머지 작업은 부동형 값을 부호 없는 유형으로 변환한 경우 수행할 필요가 없습니다.따라서 휴대용 값의 범위는 [0, Utype_MAX + 1)입니다.


2005년 이후 계속된 문제일 수 있다고 생각합니다.이전에는 변환 기능이 있었습니다.__ftol2이 코드에서는 아마 동작했을 것입니다.즉, 값을 부호 있는 번호 -2147483647로 변환하여 부호 없는 번호를 해석했을 때 올바른 결과를 얻을 수 있습니다.

도 ★★★★★★★★★★★★★★★.__ftol2_sse is 、 。__ftol2의 비트를 취하는 , 「」를 반환하는 의 에러를 있습니다.LONG_MIN0x80000000여기서 부호 없이 길게 해석되는 것은 기대했던 것과 전혀 다릅니다.★★★★★★★★★★★★★★★의 동작__ftol2_ssesigned longvalue > double a value >의 으로서 「double a value」를 선택합니다LONG_MAX로로 합니다.signed long정의되지 않은 동작을 했을 것입니다.

@AntiHaapala의 답변에 따라 최적화를 사용하여 코드를 테스트했습니다./Ox이것으로 버그가 제거되는 것을 알 수 있었습니다.__ftol2_sse는 사용되지 않게 되었습니다.

//; 17   :     printf("Direct cast value: %u\n", (unsigned int)getDouble());

    push    -2147483647             //; 80000001H
    push    OFFSET $SG10116
    call    _printf

//; 18   :     double d = getDouble();
//; 19   :     printf("Indirect cast value: %u\n", (unsigned int)d);

    push    -2147483647             //; 80000001H
    push    OFFSET $SG10117
    call    _printf
    add esp, 28                 //; 0000001cH

최적화 기능(인라인)getdouble()고정 표현식 평가를 추가함으로써 런타임에 변환할 필요가 없어지고 버그가 사라집니다.

궁금해서 몇 가지 테스트를 더 했습니다. 즉, 런타임에 float-to-int 변환을 강제하도록 코드를 변경했습니다.이 경우 결과는 여전히 정확합니다.컴파일러는 최적화 기능을 사용하여__dtoui3두 변환 모두:

//; 19   :     printf("Direct cast value: %u\n", (unsigned int)getDouble(d));

    movsd   xmm0, QWORD PTR _d$[esp+24]
    add esp, 12                 //; 0000000cH
    call    __dtoui3
    push    eax
    push    OFFSET $SG9261
    call    _printf

//; 20   :     double db = getDouble(d);
//; 21   :     printf("Indirect cast value: %u\n", (unsigned int)db);

    movsd   xmm0, QWORD PTR _d$[esp+20]
    add esp, 8
    call    __dtoui3
    push    eax
    push    OFFSET $SG9262
    call    _printf

단, 인라이닝을 방지하기 위해__declspec(noinline) double getDouble(){...}이 버그를 되돌립니다.

//; 17   :     printf("Direct cast value: %u\n", (unsigned int)getDouble(d));

    movsd   xmm0, QWORD PTR _d$[esp+76]
    add esp, 4
    movsd   QWORD PTR [esp], xmm0
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET $SG9261
    call    _printf

//; 18   :     double db = getDouble(d);

    movsd   xmm0, QWORD PTR _d$[esp+80]
    add esp, 8
    movsd   QWORD PTR [esp], xmm0
    call    _getDouble

//; 19   :     printf("Indirect cast value: %u\n", (unsigned int)db);

    call    __ftol2_sse
    push    eax
    push    OFFSET $SG9262
    call    _printf

__ftol2_sse양쪽 변환에서 호출되어 출력이 생성됩니다.2147483648두 경우 모두 @zwol 의혹은 옳았다.


컴파일 상세:

  • 명령줄 사용:
cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c        
  • Visual Studio에서:

    • 무효화RTC -> Properties -> Code Generation 기본 런타임 체크를 기본값으로 설정합니다.

    • 에서의 최적화 활성화 -> Properties -> Optimization Optimization을 /Ox설정합니다.

    • ★★★★★★★의 디버거 사용x86

.__ftol2_sse.

에서 부호 있는 「x87」로할 수 .intlong), (Windows의 경우 32비트타입)에 하게 접속하지 ,uint32_t.

정수 결과를 오버플로 하는 x86 FP -> 정수 명령어는 단순히/삭제하는 것이 아닙니다.이 명령어는 수신처에 정확한 값이 표시되지 않을 때 인텔에서 "integer infinite"라고 부르는 것을 생성합니다.즉, 고비트 세트, 기타 비트 클리어입니다.

(또는 FP 무효 예외가 마스크되지 않은 경우 FP가 기동하여 값이 저장되지 않습니다.단, 기본 FP 환경에서는 모든 FP 예외가 마스크됩니다.따라서 FP 계산에서는 장애 대신 NaN을 얻을 수 있습니다.)

여기에는 (현재 반올림 모드 사용)와 같은 x87 명령과 (0을 향해 잘라내기 사용)와 같은 SSE2 명령이 모두 포함됩니다.t수단)을 참조해 주세요.

편집하는 입니다.double->unsigned__ftol2_sse.


사이드 노트/탄젠트:

FP -> 를 x86-64 의 FP -> uint32_t 로 할 수 .cvttsd2si rax, xmm064비트 서명 수신처로 변환하여 정수 수신처의 Low Half(EAX; 로우 하프)에서 원하는 uint32_t를 생성합니다.

결과가 0을 벗어나면 C와 C++ UB가 됩니다.2^32-1 범위이므로 큰 양의 값 또는 음의 값이 RAX(EAX)의 하위 절반을 정수 비트 패턴에서 남길 수 있습니다(정수 변환과 달리 값의 모듈로 축소는 보장되지 않습니다).부호 없는 음의 이중 int를 주조하는 동작이 C 표준에 정의되어 있는가? ARM과 x86의 동작은 다릅니다.명확하게 말하면, 질문에 정의되어 있지 않거나, 실장 정의의 동작도 포함되어 있지 않습니다.즉, FP->int64_t가 있으면 FP->uint32_t를 효율적으로 구현할 수 있다는 것입니다.여기에는 x87이 포함됩니다.fistp는, 64비트 모드에서 64비트 정수만을 직접 처리할 수 있는SSE2 명령과는 달리, 32비트 및 16비트 모드에서도 64비트 정수 수신처를 쓸 수 있습니다.

언급URL : https://stackoverflow.com/questions/63983079/double-cast-to-unsigned-int-on-win32-is-truncating-to-2-147-483-648

반응형