次の宣言がある場合:
float a = 3.0 ;
それはエラーですか?値であり、として指定する必要3.0
がある本を読みました。そうですか?double
float a = 3.0f
次の宣言がある場合:
float a = 3.0 ;
それはエラーですか?値であり、として指定する必要3.0
がある本を読みました。そうですか?double
float a = 3.0f
;
後が必要です。
回答:
宣言するのはエラーではありません。宣言するfloat a = 3.0
と、コンパイラはdoubleリテラル3.0をfloatに変換します。
ただし、特定のシナリオでは、floatリテラル表記を使用する必要があります。
パフォーマンス上の理由:
具体的には、次のことを考慮してください。
float foo(float x) { return x * 0.42; }
ここで、コンパイラーは、戻り値ごとに変換(実行時に支払う)を発行します。それを回避するには、次のように宣言する必要があります。
float foo(float x) { return x * 0.42f; } // OK, no conversion required
結果を比較するときにバグを回避するには:
たとえば、次の比較は失敗します。
float x = 4.2;
if (x == 4.2)
std::cout << "oops"; // Not executed!
floatリテラル表記で修正できます:
if (x == 4.2f)
std::cout << "ok !"; // Executed!
正しいオーバーロードされた関数を呼び出すには(同じ理由で):
例:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
int main()
{
foo(42.0); // calls double overload
foo(42.0f); // calls float overload
return 0;
}
Cyberが指摘しているように、型推定のコンテキストでは、コンパイラがfloat
:を推定するのを支援する必要があります。
の場合auto
:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
同様に、テンプレートタイプの控除の場合:
void foo(float f) { std::cout << "\nfloat"; }
void foo(double d) { std::cout << "\ndouble"; }
template<typename T>
void bar(T t)
{
foo(t);
}
int main()
{
bar(42.0); // Deduce double
bar(42.0f); // Deduce float
return 0;
}
42
は整数であり、自動的にプロモートされますfloat
(そして、適切なコンパイラーではコンパイル時に発生します)。したがって、パフォーマンスの低下はありません。おそらくあなたはのようなものを意味しました42.0
。
4.2
すると、コンパイラとシステムによって4.2f
はFE_INEXACT
フラグを設定するという副作用が発生する可能性があります。一部の(確かに少数の)プログラムは、どの浮動小数点演算が正確でどれが正確でないかを考慮し、そのフラグをテストします。 。これは、単純な明らかなコンパイル時の変換がプログラムの動作を変更することを意味します。
float foo(float x) { return x*42.0; }
単精度乗算にコンパイルでき、前回試したときにClangによってコンパイルされました。ただしfloat foo(float x) { return x*0.1; }
、単一の単精度乗算にコンパイルすることはできません。このパッチの前は少し楽観的すぎたかもしれませんが、パッチの後は、結果が常に同じである場合にのみ、conversion-double_precision_op-conversionをsingle_precision_opに組み合わせる必要があります。article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
someFloat
、式someFloat * 0.1
は、よりも正確な結果になりますがsomeFloat * 0.1f
、多くの場合、浮動小数点除算よりも安価です。たとえば、(float)(167772208.0f * 0.1)は、16777222ではなく16777220に正しく丸められます。一部のコンパイラはdouble
、浮動小数点除算の代わりに乗算を使用する場合がありますが、そうでない場合は(すべてではありませんが、多くの値で安全です)。 )乗算は有用な最適化である可能性がありますが、double
逆数で実行される場合に限ります。
変数をfloatとして宣言したため、コンパイラは次のリテラルのいずれかをfloatに変換します。
float a = 3; // converted to float
float b = 3.0; // converted to float
float c = 3.0f; // float
auto
たとえば、次のように使用したかどうか(または他のタイプの控除方法)が重要になります。
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
auto
だけではありません。
接尾辞のない浮動小数点リテラルはdouble型です。これは、ドラフトC ++標準セクションの2.14.4
浮動小数点リテラルで説明されています。
[...]接尾辞で明示的に指定されていない限り、浮動リテラルのタイプはdoubleです。[...]
したがって、floatにdoubleリテラルを割り当てるの3.0
はエラーですか?:
float a = 3.0
いいえ、そうではありません。変換されます。これについては、4.8
浮動小数点変換のセクションで説明しています。
浮動小数点型のprvalueは、別の浮動小数点型のprvalueに変換できます。ソース値を宛先タイプで正確に表すことができる場合、変換の結果はその正確な表現になります。ソース値が2つの隣接する宛先値の間にある場合、変換の結果は、これらの値のいずれかの実装定義の選択になります。それ以外の場合、動作は定義されていません。
これの意味についての詳細は、GotW#67で読むことができます。
これは、たとえ精度(つまりデータ)が失われたとしても、double定数を暗黙的に(つまり、サイレントに)float定数に変換できることを意味します。これは、Cの互換性と使いやすさの理由からそのままにしておくことができましたが、浮動小数点演算を行うときは覚えておく価値があります。
品質コンパイラは、未定義の動作を実行しようとすると警告を表示します。つまり、floatが表すことができる最小値よりも小さい、または最大値よりも大きい値をfloatに2倍の量を入れます。非常に優れたコンパイラーは、定義されている可能性があるが情報を失う可能性があることを実行しようとすると、オプションの警告を提供します。フロートとして正確に表されます。
したがって、注意が必要な一般的なケースには注意が必要です。
実用的な観点から、この場合、技術的には変換があったとしても、結果はおそらく同じになるでしょう。これは、godboltで次のコードを試すことで確認できます。
#include <iostream>
float func1()
{
return 3.0; // a double literal
}
float func2()
{
return 3.0f ; // a float literal
}
int main()
{
std::cout << func1() << ":" << func2() << std::endl ;
return 0;
}
との両方を使用すると、func1
との結果func2
が同じであることがわかります。clang
gcc
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
以下のようパスカルはこのコメントで指摘あなたは、常にこの上で数えることができません。0.1
とを0.1f
それぞれ使用すると、変換を明示的に実行する必要があるため、生成されるアセンブリが異なります。次のコード:
float func1(float x )
{
return x*0.1; // a double literal
}
float func2(float x)
{
return x*0.1f ; // a float literal
}
結果は次のアセンブリになります。
func1(float):
cvtss2sd %xmm0, %xmm0 # x, D.31147
mulsd .LC0(%rip), %xmm0 #, D.31147
cvtsd2ss %xmm0, %xmm0 # D.31147, D.31148
ret
func2(float):
mulss .LC2(%rip), %xmm0 #, D.31155
ret
変換がパフォーマンスに影響を与えるかどうかを判断できるかどうかに関係なく、正しいタイプを使用すると、意図がより適切に文書化されます。たとえば、明示的な変換を使用static_cast
すると、バグまたは潜在的なバグを示す可能性のある偶発的な変換ではなく、変換が意図されたものであることを明確にするのにも役立ちます。
注意
スーパーキャットが指摘しているように、例えば0.1
とによる乗算0.1f
は同等ではありません。コメントは素晴らしく、要約ではおそらく正義とは言えないので、コメントを引用します。
たとえば、fが100000224(floatとして正確に表現可能)に等しい場合、10分の1を掛けると、10000022に切り捨てられる結果になりますが、0.1fを掛けると、誤って10000023に切り上げられる結果になります。 。10で除算する場合、二重定数0.1による乗算は、10fによる除算よりも高速であり、0.1fによる乗算よりも正確である可能性があります。
私の最初のポイントは、別の質問で与えられた誤った例を示すことでしたが、これはおもちゃの例に存在する可能性のある微妙な問題を細かく示しています。
f = f * 0.1;
とf = f * 0.1f;
は異なることをすることは注目に値するかもしれません。たとえば、f
が100000224(これは正確にとして表現可能)に等しい場合float
、10分の1を掛けると、10000022に切り捨てられる結果が得られますが、0.1fを掛けると、誤って10000023に切り上げられる結果が得られます。意図は10で除算することであり、double
定数0.1による乗算は、による除算よりも高速10f
であり、0.1f
。による乗算よりも正確である可能性があります。
コンパイラがそれを拒否するという意味でのエラーではありませんが、それがあなたが望むものではないかもしれないという意味でのエラーです。
あなたの本が正しく述べているように、3.0
はタイプの値ですdouble
。からdouble
への暗黙の変換があるfloat
ためfloat a = 3.0;
、変数の有効な定義もあります。
ただし、少なくとも概念的には、これは不必要な変換を実行します。コンパイラーによっては、変換はコンパイル時に実行される場合と、実行時に保存される場合があります。実行時に保存する正当な理由は、浮動小数点変換が難しく、値を正確に表現できない場合に予期しない副作用が発生する可能性があり、値を正確に表現できるかどうかを確認するのは必ずしも簡単ではないためです。
3.0f
この問題を回避します。技術的には、コンパイラーは実行時に定数を計算できますが(常にそうです)、ここでは、コンパイラーがそれを実行する理由はまったくありません。
以下を試してみると:
std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;
次のように出力されます。
4:8
つまり、3.2fのサイズは32ビットマシンでは4バイトと見なされますが、3.2は32ビットマシンでは8バイトとなる倍精度値として解釈されます。これはあなたが探している答えを提供するはずです。
double
を示してfloat
いますがfloat
、ダブルリテラルからを初期化できるかどうかは
コンパイラは、リテラルから最適な型を推測するか、少なくとも、最適であると考えるものを推測します。つまり、精度よりも効率が低下します。つまり、floatの代わりにdoubleを使用します。疑わしい場合は、中括弧を使用して明示的にしてください。
auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int
型変換規則が適用される別の変数から初期化すると、話はさらに興味深いものになります。double形式をリテラルとして構成することは合法ですが、intから構成するには、絞り込みを行う必要があります。
auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'
3.0
をfloatに変換します。最終結果はと区別できませんfloat a = 3.0f
。