42

私の実際の問題のSSCCEで次のコードを、考えてみましょうするためにint型から行くとき:符号変化浮くとバック

#include <iostream> 

int roundtrip(int x) 
{ 
    return int(float(x)); 
} 

int main() 
{ 
    int a = 2147483583; 
    int b = 2147483584; 
    std::cout << a << " -> " << roundtrip(a) << '\n'; 
    std::cout << b << " -> " << roundtrip(b) << '\n'; 
} 

私のコンピュータ(Xubuntuの12.04.3 LTS)の出力は次のとおりです。

2147483583 -> 2147483520 
2147483584 -> -2147483648 

肯定番号bが往復の後に否定的になることに注意してください。この動作は明確に規定されていますか? Iラウンドトリップ少なくとも正しく記号を維持するためにINT-にフロート期待しているだろう...

フム、on ideone、出力は異なります。

2147483583 -> 2147483520 
2147483584 -> 2147483647 

グラム++チームがでバグを修正しましたその間、または両方の出力が完全に有効ですか?

+0

x86_64で '' g ++(GCC)4.8.2 20131017(Red Hat 4.8.2-1) ''で記述した動作(ideoneのものではない)を確認できます。 –

+0

@Mat:これを確認できますが、 '' -O {s、1,2,3} ''のどれであるかは関係ありません。 –

+1

数字は仮数部には大きすぎますか? –

答えて

69

プログラムは、浮動小数点から整数への変換でオーバーフローが発生するため、未定義の動作を呼び出しています。あなたが見ているのは、x86プロセッサの通常の症状だけです。

2147483584に最も近いfloat値が正確に(浮動小数点の整数からの変換は、通常、2 をアップすることができ、最寄りに丸め、この場合には最大である。具体的には、行動整数から浮動小数点への変換は実装定義であり、ほとんどの実装では「FPU丸めモード」として丸めを定義し、FPUのデフォルト丸めモードは最も近いものに丸めます。

次に、2 からintを表す浮動小数点数から、オーバーフローが発生します。このオーバーフローは未定義の動作です。プロセッサによっては例外が発生するものもあれば、飽和するものもあります。 IA-32命令cvttsd2siは、floatが正か負かにかかわらず、オーバーフローの場合に常にINT_MINを返します。

Intelプロセッサを対象としている場合でも、この動作に頼るべきではありません。x86-64を対象とする場合、コンパイラは浮動小数点から整数への変換のためにsequences of instructions that take advantage of the undefined behavior to return results other than what you might otherwise expect for the destination integer typeを出力できます。

+0

では、理想はx86上で動作しないと結論づけることができますか? :) – fredoverflow

+0

@FredOverflowコメントを書くと同時に、http://blog.frama-c.com/index.php?post/2013/10/09/Overflow-float-integerへのリンクを追加していました後半は私がこれに答えると思う。 –

+1

「x86プロセッサは常にINT_MINを返すようになりました。ありがとうございます。 –

10

パスカルの答えはOKですが、一部のユーザーには分かりません。下位レベルでどのように見えるか興味があれば(コプロセッサで、ソフトウェアで浮動小数点演算を処理していないと仮定して)、読んでください。フロートの32ビット(IEEE 754)は、内から整数のすべてを格納することができる[-2 ... 2 ]範囲で

。範囲外の整数も浮動小数点数として表現されていてもかまいませんが、すべてが浮動小数点数ではありません。問題は、浮動小数点数で演奏するのに24の重要なビットしか持てないことです。ここで

INT-からどのように変換することです>フロートは、典型的には、低レベルで次のようになります。

fild dword ptr[your int] 
fstp dword ptr[your float] 

それは2つのコプロセッサ命令の単なるシーケンスです。最初に32ビットintをコンパイラのスタックにロードし、それを80ビット幅のフロートに変換します。

インテル®64およびIA-32アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル

(X87 FPUを使用したプログラミング):

浮動小数点、整数、またはパックBCD整数 の値がメモリからいずれかのx87 FPUデータレジスタにロードされた場合、値は となり、倍精度浮動小数点形式に自動的に変換されます( はまだその形式ではありません)。

FPUレジスタは80ビット幅の浮動小数点数であるため、 - 32ビットintは完全に浮動小数点形式の64ビットの仮数に収まるようfildとはここでの問題はありません。

これまでのところとても良いです。

2番目の部分 - fstpは少しトリッキーで、驚くかもしれません。これは、32bit浮動小数点で80bit浮動小数点を格納することになっています。それは整数値に関するものですが(質問の)コプロセッサは実際には「丸め」を実行することがあります。ケ?浮動小数点形式で格納されている場合でも、整数値はどのように丸めますか? ;-)。

私はまもなく説明します。最初にx87が提供する丸めモード(IEE 754丸めモードのインカネーション)を見ていきましょう。 X87 fpuには、fpuの制御ワードのビット#10と#11によって制御される4つの丸めモードがあります。

  • 00 - 最も近い偶数に丸められた結果は無限に正確です。 2つの値が等しく近い場合、結果は偶数の値になります(つまり、最下位ビットがゼロの )。 デフォルト
  • 01 - + INF
  • 11に向けて - - -Inf
  • 10側に向けて0

あなたはこの単純なコードを使用して丸めモードで遊ぶことができます(それものの(すなわち切り捨てます。)ここでは低レベルを示しています):

enum ROUNDING_MODE 
{ 
    RM_TO_NEAREST = 0x00, 
    RM_TOWARD_MINF = 0x01, 
    RM_TOWARD_PINF = 0x02, 
    RM_TOWARD_ZERO = 0x03 // TRUNCATE 
}; 

void set_round_mode(enum ROUNDING_MODE rm) 
{ 
    short csw; 
    short tmp = rm; 

    _asm 
    { 
     push ax 
     fstcw [csw] 
     mov ax, [csw] 
     and ax, ~(3<<10) 
     shl [tmp], 10 
     or ax, tmp 
     mov [csw], ax 
     fldcw [csw] 
     pop ax 
    } 
} 

これは整数値にどのように関連していますか?忍耐...切り捨て(デフォルトではない) - - あなたが浮かぶようにint型に変換する変換チェック最も明白な方法をfloat型にint型に関与丸めモードが必要になる場合があります理由を理解するには、次のようになります。

  • レコード記号
  • あなたのint型を否定左/右の左端の1
  • シフトintの場合、ゼロ未満
  • 検索位置あなたが指数
  • を計算することができるように上記見つかっ1は、プロセス中のシフトのビット#23
  • レコード番号の上に位置するように、

そして、このbahaviorをシミュレートするコードは次のようになります。

float int2float(int value) 
{ 
    // handles all values from [-2^24...2^24] 
    // outside this range only some integers may be represented exactly 
    // this method will use truncation 'rounding mode' during conversion 

    // we can safely reinterpret it as 0.0 
    if (value == 0) return 0.0; 

    if (value == (1U<<31)) // ie -2^31 
    { 
     // -(-2^31) = -2^31 so we'll not be able to handle it below - use const 
     value = 0xCF000000; 
     return *((float*)&value); 
    } 

    int sign = 0; 

    // handle negative values 
    if (value < 0) 
    { 
     sign = 1U << 31; 
     value = -value; 
    } 

    // although right shift of signed is undefined - all compilers (that I know) do 
    // arithmetic shift (copies sign into MSB) is what I prefer here 
    // hence using unsigned abs_value_copy for shift 
    unsigned int abs_value_copy = value; 

    // find leading one 
    int bit_num = 31; 
    int shift_count = 0; 

    for(; bit_num > 0; bit_num--) 
    { 
     if (abs_value_copy & (1U<<bit_num)) 
     { 
      if (bit_num >= 23) 
      { 
       // need to shift right 
       shift_count = bit_num - 23; 
       abs_value_copy >>= shift_count; 
      } 
      else 
      { 
       // need to shift left 
       shift_count = 23 - bit_num; 
       abs_value_copy <<= shift_count; 
      } 
      break; 
     } 
    } 

    // exponent is biased by 127 
    int exp = bit_num + 127; 

    // clear leading 1 (bit #23) (it will implicitly be there but not stored) 
    int coeff = abs_value_copy & ~(1<<23); 

    // move exp to the right place 
    exp <<= 23; 

    int ret = sign | exp | coeff; 

    return *((float*)&ret); 
} 

今の例 - 切り捨てモードは21474835202147483583に変換します。

2147483583 = 01111111_11111111_11111111_10111111 

int-> float変換では、左端の1をビット23にシフトする必要があります。今や1をリードするのがビット#30です。ビット#23に配置するには、7つの位置で右シフトを実行する必要があります。その間にあなたは緩んでいます(32ビットの浮動小数点フォーマットに収まらないでしょう)。右から7 lsbビット(切り捨て/切り詰める)。彼らは次のとおりだった:

01111111 = 63 

そして63は元の数が失わです:

2147483583 -> 2147483520 + 63 

切捨ては簡単ですが、必ずしもあなたが望むものではないかもしれない、および/またはすべてのケースのために最善をです。例の下に考えてみましょう:値を上回る

67108871 = 00000100_00000000_00000000_00000111 

は正確にフロートによって表されるが、それには何を切り捨てをチェックすることはできません。これまでのように、左端の1をビット#23に移動する必要があります。

00000001.[0000000_00000000_00000000] 111 * 2^26 (3 bits shifted out) 

切り捨てチョップ:これは3 LSBビット(今のように私は違ったフロートの暗黙の24ビットであり、仮の明示的な23bitsを挟むれる場所を示す数字を書きます)を失う​​右正確に3つの位置をシフトさせる値を必要と67108864(67108864 + 7(3チョップビット))= 67108871(私たちはシフトしますが、指数操作で補正しますが、ここでは省略します)を残す3つの末尾のビット。

これで十分ですか?ちょっと67108872は32bit floatで完全に表現でき、67108864よりはるかに良いはずですか?これはintを32bit floatに変換するときの丸めについて話したいと思うかもしれません。

ここで、デフォルトの「最も近い偶数」モードへの丸めがどのように機能し、OPの場合にどのような意味があるかを見てみましょう。同じ例をもう一度考えてみましょう。

67108871 = 00000100_00000000_00000000_00000111 

我々は右ビット#23で1左端を置くに移行3を必要と知っているように:「でも、最寄りに丸める」の

00000000_1.[0000000_00000000_00000000] 111 * 2^26 (3 bits shifted out) 

手順は下からそのブラケット入力値67108871を2つの数字を見つける必要可能な限り近くに置いてください。私たちはまだ80ビットでFPU内で動作しているので、いくつかのビットがシフトアウトされていることを示していますが、まだFPUレジスタにありますが、出力値を格納するときは丸め処理中に削除されます。

00000000_1.[0000000_00000000_00000000] 111 * 2^26 (3 bits shifted out) 

2密接ブラケット00000000_1.[0000000_00000000_00000000] 111 * 2^26がある値:上から

00000000_1.[0000000_00000000_00000000] 111 * 2^26 
            +1 
= 00000000_1.[0000000_00000000_00000001] * 2^26 = 67108872 

以下から:明らか

00000000_1.[0000000_00000000_00000000] * 2^26 = 67108864 

67108872が故に67108864より67108871にはるかに近いです32bit int値からの変換67108871は、67108872(最も近い偶数モードに丸める)を示します。

今OPの番号(まださえ最寄りに丸める):

2147483583 = 01111111_11111111_11111111_10111111 
= 00000000_1.[1111111_11111111_11111111] 0111111 * 2^30 

ブラケット値:

トップ:

00000000_1.[1111111_111111111_11111111] 0111111 * 2^30 
             +1 
= 00000000_10.[0000000_00000000_00000000] * 2^30 
= 00000000_1.[0000000_00000000_00000000] * 2^31 = 2147483648 

下:

00000000_1.[1111111_111111111_11111111] * 2^30 = 2147483520 

は覚えておいてくださいそのでも入力値が括弧の値の中間にある場合にのみ、「最も近い偶数に丸める」問題の単語。その後、という単語だけがであり、どのブラケット値を選択すべきかを決定する。上記の場合でさえは問題ではないと、私たちは単純に近い値を選択する必要があり、ある2147483520

最終OPの場合は、でもワード事項の問題を示しています。 :

2147483584 = 01111111_11111111_11111111_11000000 
= 00000000_1.[1111111_11111111_11111111] 1000000 * 2^30 

ブラケット値が以前と同じである。

トップ:00000000_1.[0000000_00000000_00000000] * 2^31 = 2147483648

底:00000000_1.[1111111_111111111_11111111] * 2^30 = 2147483520

全く近い値が(2147483648から2147483584 = 64 = 2147483584から2147483520今はありません)でもに依存し、トップ(偶数)の値2147483648を選択する必要があります。

ここでOPの問題は、パスカルが簡単に説明したことです。 FPUは符号付きの値に対してのみ機能し、2147483648は最大値が2147483647なので符号付き整数として格納できません。

FPUは符号付きの値に対してのみ動作することを簡単に証明します。扱い署名されたように、すべての値は、このデバッグすることによるものである。

unsigned int test = (1u << 31); 

_asm 
{ 
    fild [test] 
} 

試験値がローディング付きおよび符号なしの値のための別の指示がないように、それはのよう-2 ロードされる符号なしとして扱われるべきであるように見えるがFPUに書き込む。同様に、FPUの符号なしの値をmemに格納するための命令を見つけることもできません。あなたのプログラムでどのように宣言したのかにかかわらず、すべてが署名付きで扱われるちょっとしたパターンです。

長いですが、誰かがそれから何かを学ぶことを願っています。

+0

OPのコンパイラが387をターゲットにしていると仮定する必要はありません。最新のインテル命令セットをターゲットとする現代コンパイラは、使用されるレジスタサイズの最小値(32ビットまたは64ビット)を返す 'cvttsd2si'を生成します。ビット)をオーバーフローさせます。 –

+1

@PascalCuoq:そうです。私のポストは、それ以上のものを追加しないのに十分な長さだった。 – Artur

関連する問題