22

次の C プログラムの出力について疑問があります。Visual C++ 6.0 と MinGW32 (gcc 3.4.2) の両方を使用してコンパイルしようとしました。

#include <stdio.h>

int main() {
    int x = 2147483647;
    printf("%f\n", (float)2147483647);
    printf("%f\n", (float)x);
    return 0;
}

出力は次のとおりです。

2147483648.000000
2147483647.000000

私の質問は、なぜ両方の行が異なるのですか? 整数値 2147483647 を IEEE 754 浮動小数点形式に変換すると、2147483648.0 に近似されます。したがって、両方の行が 2147483648.000000 に等しいと予想しました。

EDIT : 値 "2147483647.000000" を単精度浮動小数点値にすることはできません。これは、数値 2147483647 を IEEE 754 単精度浮動小数点形式で精度を失うことなく正確に表すことができないためです。

4

5 に答える 5

11

どちらの場合も、コードは整数型からfloat.doubleへの変換を試みます。変換は、可変引数関数に渡される値doubleであるため発生します。float

の設定を確認しFLT_EVAL_METHOD、値が 1 または 2 であると思われます (2少なくとも 1 つのコンパイラを使用した OP レポート)。これにより、コンパイラはfloatより大きい「範囲と精度に対する演算と定数」を評価できますfloat

コンパイラ(float)xは、直接演算intに最適化されました。doubleこれは、実行時のパフォーマンスの向上です。

(float)2147483647はコンパイル時のキャストであり、パフォーマンスはここでは問題にならないため、コンパイラは正確に最適化されintfloatいます。double


[Edit2] C11 仕様が C99 仕様よりも具体的で、「代入とキャストを除く...」が追加されているのは興味深いことです。これは、C99 コンパイラが最初に通過せずintに直接変換を許可することがあり、C11 が修正されてキャストのスキップが明確に許可されなかったことを意味します。doublefloat

C11 ではこの動作が正式に除外されているため、最新のコンパイラはこれを行うべきではありませんが、OP のような古いコンパイラはこれを行う必要があります。つまり、C11 標準によるバグです。他の C99 または C89 仕様で別段の記述がない限り、これは許容されるコンパイラの動作のようです。


[編集] @Keith Thompson、@tmyklebu、@Matt McNabb によるコメントをまとめると、コンパイラは、ゼロ以外の であっても、FLT_EVAL_METHODを生成することが期待され2147483648.0...ます。したがって、コンパイラの最適化フラグが正しい動作を明示的に上書きしているか、コンパイラにコーナー バグがあります。


C99dr §5.2.4.2.2 8 浮動オペランドを伴う演算の値、および通常の算術変換の対象となる値、および浮動定数の値は、その範囲と精度が型で必要とされるよりも大きい形式に評価されます。評価形式の使用は、FLT_EVAL_METHODの実装定義の値によって特徴付けられます。

-1 不定;

0 は、すべての演算と定数を型の範囲と精度だけで評価します。

1 型の演算と定数を型floatdouble範囲と精度でdouble評価し、型の範囲と精度でlong double演算と定数を評価しlong doubleます`;

2 すべての演算と定数を型の範囲と精度で評価しますlong double


C11dr §5.2.4.2.2 9 代入とキャスト (すべての余分な範囲と精度を削除する) を除き、浮動オペランドを持つ演算子によって生成された値と、通常の算術変換および浮動定数の対象となる値は、その範囲の形式に評価されます。精度は、型で必要とされるよりも大きい場合があります。評価フォーマットの使用は、FLT_EVAL_METHODの実装定義の値によって特徴付けられます。

-1 (C99 と同じ)

0 (C99 と同じ)

1 (C99 と同じ)

2 (C99と同じ)

于 2014-11-24T22:16:56.767 に答える
7

これは確かにコンパイラのバグです。C11 標準から、次の保証があります (C99 も同様でした)。

  • 型には一連の表現可能な値があります (暗黙)
  • で表現できるすべての値は、 (6.2.5/10)floatでも表現できます。double
  • に変換floatdoubleても値は変わりません (6.3.1.5/1)
  • int 値が の表現可能な値のセットにある場合、 にキャストintすると、その値が得られます。floatfloat
  • int 値の大きさが よりも小さく、が の表現可能な値ではない場合、 にキャストintすると、次に高い値または次に低い値のいずれかが選択され、どちらが選択されるかは実装定義です。(6.3.1.4/2)floatFLT_MAXintfloatfloat

これらのポイントの 3 番目は、 にfloat提供される値printfがデフォルト引数のプロモーションによって変更されないことを保証します。

2147483647が で表現できる場合float(float)xおよび(float)2147483647は を与えなければなりません2147483647.000000

2147483647が で表現できない場合float(float)xand(float)2147483647は次に高いまたは次に低い のいずれかを与える必要がありますfloat。両方が同じ選択をする必要はありません。ただし、これは12147483647.000000の出力が許可されていないことを意味し、それぞれが高い値または低い値でなければなりません。


12147483646.9999999...そうですね - 理論的には、値が 6 桁の精度で表示される場合、次に低い float がそうであった可能性がありprintfます。しかし、これは IEEE754 には当てはまらず、簡単に実験してこの可能性を割り引くことができます。

于 2014-11-24T22:58:37.357 に答える
2

最初のprintfでは、integer から float への変換がコンパイラによって行われます。2 つ目は、C ランタイム ライブラリによって実行されます。精度の限界で同一の回答を生成する特別な理由はありません。

于 2014-11-24T20:27:40.480 に答える
0

Visual C++ 6.0 は前世紀にリリースされましたが、標準の C++ よりも前のものだと思います。VC++ 6.0 が壊れた動作を示すことはまったく驚くべきことではありません。

また、gcc-3.4.2 が 2004 年のものであることにも注意してください。実際、32 ビット コンパイラを使用しています。x86 の gcc は、浮動小数点数演算でかなり高速で緩い動作をします。FLT_EVAL_METHODこれは、gccがゼロ以外の値に設定されている場合、C 標準によって技術的に正当化される可能性があります。

于 2014-11-24T22:24:50.353 に答える
-1

最適化のバグだと言う人もいますが、私はそうは思いません。これは妥当な浮動小数点精度エラーであり、浮動小数点がどのように機能するかを示す良い例だと思います。

http://ideone.com/Ssw8GR

多分OPは私のプログラムをあなたのコンピューターに貼り付けようとし、あなたのコンパイラーでコンパイルして何が起こるかを見ようとするかもしれません. または試してください:

http://ideone.com/OGypBC

(明示的な float 変換を使用)。

とにかく、エラーを計算すると、それはそれだけ4.656612875245797e-10であり、かなり正確であると見なされるべきです.

の好みにprintfも関係している可能性があります。

于 2014-11-24T22:28:58.757 に答える