web-dev-qa-db-ja.com

floatがすべてのint値を表すことができないのに、なぜC ++はintをfloatにプロモートしますか?

次のようなものがあるとしましょう:

int i = 23;
float f = 3.14;
if (i == f) // do something

ifloatに昇格され、2つのfloat番号が比較されますが、floatはすべてのint値を表すことができますか? intfloatの両方をdoubleに昇格しないのはなぜですか?

67
user4344762

intが整数プロモーションでunsignedに昇格されると、負の値も失われます(これは、0u < -1がtrueになるなどの楽しみにつながります)。

Cのほとんどのメカニズム(C++に継承される)と同様に、通常の算術変換はハードウェア操作の観点から理解する必要があります。 Cのメーカーは、使用するマシンのアセンブリ言語に非常に精通しており、それまでアセンブリで記述されていたもの(UNIXなど)カーネル)。

現在、一般的にプロセッサには混合型の命令がありません(floatをdoubleに追加したり、intをfloatと比較したりするなど)。これはウェーハ上の不動産の大きな浪費になるため、実装する必要があります。さまざまなタイプをサポートするために必要な回数だけオペコードを追加します。 「intをintに追加する」、「floatをfloatに比較する」、「unsignedとunsignedを乗算する」などの命令しか持たないことは、最初の段階で通常の算術変換を必要とします。彼らと一緒に使用するのが最も理にかなっている家族。

低レベルマシンコードの記述に慣れている人の観点から、型が混在している場合、一般的なケースで考慮する可能性が最も高いアセンブラー命令は、変換の必要性が最も低い命令です。これは特に、変換にランタイムがかかる浮動小数点の場合に当てはまります。特に1970年代初期、Cが開発されたとき、コンピューターは遅く、浮動小数点計算はソフトウェアで行われていました。これは通常の算術変換で示されます-1つのオペランドのみが変換されます(long/unsigned intの単一の例外を除き、longunsigned longに変換できます。おそらく、例外が適用される場所ではないでしょう。

したがって、通常の算術変換は、アセンブリコーダーがほとんどの場合に行うことを行うために記述されています。適合しない2つの型があり、一方が他方に変換されるように変換します。これは、特に別の理由がない限り、アセンブラーコードで行うことです。アセンブラーコードの記述に慣れている人やdoには、異なる変換を明示的に強制する特定の理由があります。変換が自然であることを要求する。結局のところ、あなたは単に書くことができます

if((double) i < (double) f)

ちなみに、このコンテキストでは、unsignedintよりも階層内で高いため、intunsignedを比較すると興味深いことに注意してください。符号なし比較で終了します(したがって、最初から0u < -1ビット)。これは、昔は人々がunsignedをその値の範囲の拡張としてではなくintの制限として考えていなかったことを示す指標であると思われます。今はサインを必要としません。値の範囲を広げるために余分なビットを使用しましょう。 intがオーバーフローすることを予期する理由がある場合に使用します。16ビットintsの世界でははるかに大きな心配です。

68
Wintermute

doubleに含まれるビットの量によっては、intでさえすべてのint値を表すことができない場合があります。

なぜintとfloatをdoubleに昇格しないのですか?

おそらくdoubleとして既にfloatであるオペランドの1つを使用するよりも、両方のタイプをfloatに変換する方がコストが高いためです。また、算術演算子のルールと互換性のない比較演算子の特別なルールを導入します。

また、浮動小数点型がどのように表現されるかについての保証もありません。したがって、intdouble(またはlong double)比較のために何でも解決します。

13
doc

floatはすべてのint値を表すことができますか?

intfloatの両方が32ビットで保存されている典型的な最新のシステムでは、ありません。何かを与えなければなりません。 32ビットの整数は、小数を含む同じサイズのセットに1対1をマップしません。

ifloatに昇格され、2つのfloat番号が比較されます…

必ずしも。どの精度が適用されるのか本当にわかりません。 C++ 14§5/ 12:

浮動オペランドの値と浮動式の結果は、型が必要とするものよりも高い精度と範囲で表現できます。それによってタイプは変更されません。

プロモーション後のiのノミナルタイプはfloatですが、値はdoubleハードウェアを使用して表すことができます。 C++は、浮動小数点精度の損失またはオーバーフローを保証しません。 (これはC++ 14の新しいものではありません。昔からCから継承されています。)

intfloatの両方をdoubleに昇格しないのはなぜですか?

どこでも最適な精度が必要な場合は、代わりにdoubleを使用してください。floatは表示されません。またはlong double、しかしそれは遅くなるかもしれません。ルールは、1台のマシンがいくつかの代替精度を提供する可能性があることを考慮して、精度が制限されたタイプの大部分のユースケースに対して比較的賢明であるように設計されています。

ほとんどの場合、高速でゆるいだけで十分なので、マシンは最も簡単なことを自由に実行できます。これは、丸められた単精度比較、または倍精度で丸めなしを意味する場合があります。

しかし、そのようなルールは最終的に妥協であり、時には失敗することもあります。 C++(またはC)で算術を正確に指定するには、変換とプロモーションを明示的にすることが役立ちます。信頼性の高いソフトウェアの多くのスタイルガイドでは、暗黙的な変換の使用が完全に禁止されており、ほとんどのコンパイラは、それらを消去するのに役立つ警告を提供しています。

これらの妥協がどのように生じたかについて学ぶために、 C根拠文書 を熟読することができます。 (最新版はC99まで対応しています。)PDP-11またはK&Rの時代の無意味な荷物ではありません。

8
Potatoswatter

ここでの多くの回答がC言語の起源から議論され、intとfloatを組み合わせた場合にintがfloatに変換される理由としてKRと歴史的な荷物を明示的に挙げているのは興味深いことです。

これは、責任を誤った当事者に向けています。 KR Cでは、フロート計算などはありませんでした。 All浮動小数点演算は倍精度で行われました。そのため、整数(またはその他のもの)は暗黙的にfloatに変換されることはなく、doubleにのみ変換されました。また、floatを関数の引数の型にすることはできません。実際に、実際に、doubleへの変換を避けたい場合は、floatにポインターを渡す必要がありました。そのため、関数

int x(float a)
{ ... }

そして

int y(a)
float a;
{ ... }

異なる呼び出し規約があります。最初の引数はfloat引数を取得し、2番目の引数(現在は構文として許可されていません)はdouble引数を取得します。

単精度浮動小数点演算および関数引数は、ANSI Cでのみ導入されました。Kernighan/ Ritchieは無害です。

新しく利用可能になった単一のfloat expressions(以前は単一のfloatのみがストレージ形式でした)では、新しい型変換も必要でした。 ANSI Cチームがここで選んだものは何でも(そして私がより良い選択のために途方に暮れるでしょう)KRのせいではありません。

6
user4467309

プログラミング言語が作成されると、いくつかの決定が直感的に行われます。

たとえば、なぜint + floatをfloat + floatまたはdouble + doubleの代わりにint + intに変換しないのですか? int-> floatが同じビット数を保持している場合、なぜプロモーションを呼び出すのですか? float-> intをプロモーションと呼ばないのはなぜですか?

暗黙の型変換に依存している場合は、それらがどのように機能するかを知っている必要があります。そうでなければ、単に手動で変換します。

一部の言語は、自動タイプ変換をまったく行わずに設計できた可能性があります。また、設計段階でのすべての決定が論理的な理由で行われたわけではありません。

JavaScriptをダックタイピングで使用すると、内部でさらに曖昧な決定が行われます。絶対に論理的な言語を設計することは不可能であり、ゲーデル不完全性定理に行くと思います。論理、直観、実践、理想のバランスを取る必要があります。

3
exebook

ルールは16ビット整数(最小必要サイズ)用に記述されています。 32ビット整数を使用するコンパイラーは、必ず両側をダブルに変換します。とにかく現代のハードウェアにはフロートレジスタがないので、has doubleに変換します。 64ビットのintがある場合、それが何をするのかよくわかりません。 long doubleが適切です(通常は80ビットですが、標準ではありません)。

2
Joshua

問題は、それが高速で、説明が簡単で、コンパイルが簡単であるためです。これらはすべて、C言語が開発された当時の非常に重要な理由でした。

算術値を比較するたびに、実際の数値を比較するという結果になるという、別のルールがあります。比較される式の1つが定数である場合は些細なこと、符号付き整数と符号なしintを比較する場合は1つの追加命令、そしてlong longとdoubleを比較し、long longをdoubleとして表現できない場合に正しい結果が必要な場合は非常に困難です。 (0u <-1は、タイプ0と-1をタイプを考慮せずに比較するため、falseになります)。

Swiftでは、異なるタイプ間での操作を禁止することで問題を簡単に解決できます。

2
gnasher729