1. ホーム
  2. c

[解決済み] Win32 で unsigned int へのダブルキャストが 2,147,483,648 に切り捨てられる

2023-07-09 13:42:57

質問

以下のコードをコンパイルしてください。

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 x64)。

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

マイクロソフトのドキュメント からの変換で、符号付き整数の最大値について言及されていません。 double から unsigned int .

上記のすべての値 INT_MAX に切り詰められます。 2147483648 に切り捨てられます。

を使っています。 ビジュアルスタジオ2019 を使用してプログラムをビルドしています。では発生しません。 gcc .

私は何か間違ったことをしているのでしょうか?を安全に変換する方法はありますか? doubleunsigned int ?

どのように解決するのですか?

コンパイラのバグか...

anastaciu氏から提供されたアセンブリから、ダイレクトキャストコードの呼び出しは __ftol2_sse を呼び出していますが、これは数値を 符号付き長さ . ルーチン名は ftol2_sse になっていますが、float は 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

で、double 値をポップしてローカル変数に格納し、それを SSE レジスタにロードして __dtoui3 を呼び出す...これは double から unsigned int への変換ルーチンです。

直接キャストの動作はC89に準拠しておらず、またそれ以降の改訂にも準拠していません。 にも適合しません。 C89は明確にそう言っています。

整数型の値が符号なし型に変換されるときに行われる再変換操作は、浮動型の値が符号なし型に変換されるときには行う必要がない。したがって、移植可能な値の範囲は [0, Utype_MAX + 1) となります。 .


私は、この問題はもしかしたら 2005 年からのこの続き - という変換関数がありました。 __ftol2 という変換関数がありましたが、これはおそらくこのコードで動作したことでしょう。 符号付き数値 -2147483647に変換し、符号なし数値として解釈したときに正しい結果を生成していたでしょう。

残念ながら __ftol2_sse の置き換えにはなりません。 __ftol2 というのは、最下位ビットをそのまま受け取るのではなく、範囲外のエラーを知らせるために LONG_MIN / 0x80000000 となっており、ここで unsigned long と解釈されるのは期待されたものでは全くありません。の動作は __ftol2_sse の動作は signed long のように、double 値の変換は > LONG_MAXsigned long に変更した場合、動作は未定義になります。