'float a = 3.0;' 正しいステートメント?


86

次の宣言がある場合:

float a = 3.0 ;

それはエラーですか?値であり、として指定する必要3.0がある本を読みました。そうですか?doublefloat a = 3.0f


2
コンパイラは、doubleリテラル3.0をfloatに変換します。最終結果はと区別できませんfloat a = 3.0f
David Heffernan

6
@EdHeal:そうですが、C ++ルールに関するこの質問には特に関係ありません。
キーストンプソン

20
まあ、少なくともあなたは;後が必要です。
ホットリック2014

3
10の反対票があり、それらを説明するコメントはあまりなく、非常に落胆しています。これはOPの最初の質問であり、これが10の反対票の価値があると人々が感じる場合は、いくつかの説明があるはずです。これは、明白ではない意味と、回答とコメントから学ぶべき多くの興味深いことを伴う有効な質問です。
Shafik Yaghmour 2014

3
@HotLicksそれは気分が悪いか良いかではなく、不公平に見えるかもしれませんが、それは人生です、結局のところ、それらはユニコーンポイントです。ダウ投票は確かに、あなたが嫌い​​な賛成票をキャンセルしないのと同じように、あなたが嫌い​​な賛成票をキャンセルすることはありません。質問が改善できると人々が感じた場合、確かに初めての質問者はいくつかのフィードバックを得る必要があります。反対する理由は見当たらないが、他の人がそう言うのは自由であるのに、なぜそうするのか知りたい。
Shafik Yaghmour 2014

回答:


159

宣言するのはエラーではありません。宣言するfloat a = 3.0と、コンパイラはdoubleリテラル3.0をfloatに変換します。


ただし、特定のシナリオでは、floatリテラル表記を使用する必要があります。

  1. パフォーマンス上の理由:

    具体的には、次のことを考慮してください。

    float foo(float x) { return x * 0.42; }
    

    ここで、コンパイラーは、戻り値ごとに変換(実行時に支払う)を発行します。それを回避するには、次のように宣言する必要があります。

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  2. 結果を比較するときにバグを回避するには:

    たとえば、次の比較は失敗します。

    float x = 4.2;
    if (x == 4.2)
       std::cout << "oops"; // Not executed!
    

    floatリテラル表記で修正できます:

    if (x == 4.2f)
       std::cout << "ok !"; // Executed!
    

    (注:もちろん、これは一般的に等しいかどうかをfloatまたはdoubleの数値と比較する方法ではありません

  3. 正しいオーバーロードされた関数を呼び出すには(同じ理由で):

    例:

    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;
    }
    
  4. 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;
    }
    

ライブデモ


2
ポイント142は整数であり、自動的にプロモートされますfloat(そして、適切なコンパイラーではコンパイル時に発生します)。したがって、パフォーマンスの低下はありません。おそらくあなたはのようなものを意味しました42.0
Matteo Italia

@MatteoItalia、はい私は42.0 ofcを意味しました(編集、ありがとう)
quantdev 2014

2
@ChristianHacklに変換4.2すると、コンパイラとシステムによって4.2fFE_INEXACTフラグを設定するという副作用が発生する可能性があります。一部の(確かに少数の)プログラムは、どの浮動小数点演算が正確でどれが正確でないかを考慮し、そのフラグをテストします。 。これは、単純な明らかなコンパイル時の変換がプログラムの動作を変更することを意味します。

6
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=
パスカルCuoq

1
の10分の1の値を計算したい場合someFloat、式someFloat * 0.1は、よりも正確な結果になりますがsomeFloat * 0.1f、多くの場合、浮動小数点除算よりも安価です。たとえば、(float)(167772208.0f * 0.1)は、16777222ではなく16777220に正しく丸められます。一部のコンパイラはdouble、浮動小数点除算の代わりに乗算を使用する場合がありますが、そうでない場合は(すべてではありませんが、多くの値で安全です)。 )乗算は有用な最適化である可能性がありますが、double逆数で実行される場合に限ります。
スーパーキャット2014

22

変数を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

5
テンプレートを使用するとタイプも推測されるため、それautoだけではありません。
Shafik Yaghmour 2014

14

接尾辞のない浮動小数点リテラルは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が同じであることがわかります。clanggcc

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による乗算よりも正確である可能性があります。

私の最初のポイントは、別の質問で与えられた誤った例を示すことでしたが、これはおもちゃの例に存在する可能性のある微妙な問題を細かく示しています。


1
表現f = f * 0.1;f = f * 0.1f; は異なることをすることは注目に値するかもしれません。たとえば、fが100000224(これは正確にとして表現可能)に等しい場合float、10分の1を掛けると、10000022に切り捨てられる結果が得られますが、0.1fを掛けると、誤って10000023に切り上げられる結果が得られます。意図は10で除算することであり、double定数0.1による乗算は、による除算よりも高速10fであり、0.1f。による乗算よりも正確である可能性があります。
スーパーキャット2014

@supercat良い例をありがとう、私はあなたを直接引用しました、あなたが適当と思うように自由に編集してください。
Shafik Yaghmour 2014

4

コンパイラがそれを拒否するという意味でのエラーではありませんが、それがあなたが望むものではないかもしれないという意味でのエラーです。

あなたの本が正しく述べているように、3.0はタイプの値ですdouble。からdoubleへの暗黙の変換があるfloatためfloat a = 3.0;、変数の有効な定義もあります。

ただし、少なくとも概念的には、これは不必要な変換を実行します。コンパイラーによっては、変換はコンパイル時に実行される場合と、実行時に保存される場合があります。実行時に保存する正当な理由は、浮動小数点変換が難しく、値を正確に表現できない場合に予期しない副作用が発生する可能性があり、値を正確に表現できるかどうかを確認するのは必ずしも簡単ではないためです。

3.0f この問題を回避します。技術的には、コンパイラーは実行時に定数を計算できますが(常にそうです)、ここでは、コンパイラーがそれを実行する理由はまったくありません。


実際、クロスコンパイラーの場合、変換がコンパイル時に実行されるのはまったく正しくありません。これは、変換が間違ったプラットフォームで行われるためです。
ローン侯爵2014

2

エラーではありませんが、それ自体は少しずさんです。フロートが必要なことがわかっているので、フロートで初期化します。
別のプログラマーがやって来て、宣言のどの部分が正しいか、タイプかイニシャライザーかわからない場合があります。両方とも正しくないのはなぜですか?
float Answer = 42.0f;


0

変数を定義すると、提供されているイニシャライザーで初期化されます。これには、初期化子の値を初期化される変数の型に変換する必要がある場合があります。それはあなたが言うときに起こっていることですfloat a = 3.0;:初期化子の値はに変換されfloat、変換の結果はの初期値になりaます。

それは一般的には問題ありませんが3.0f、特にあなたが書きたいのであれば、あなたが何をしているのかを知っていることを示すために書くことは害はありませんauto a = 3.0f


0

以下を試してみると:

std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;

次のように出力されます。

4:8

つまり、3.2fのサイズは32ビットマシンでは4バイトと見なされますが、3.2は32ビットマシンでは8バイトとなる倍精度値として解釈されます。これはあなたが探している答えを提供するはずです。


これは、異なることdoubleを示してfloatいますがfloat、ダブルリテラルからを初期化できるかどうかは
わかり

もちろん、必要に応じて、データの切り捨てを条件として、double値からfloatを初期化できます
Dr. Debasish Jana 2014

4
はい、わかっていますが、それはOPの質問だったので、答えを提供すると主張しているにもかかわらず、あなたの答えは実際には答えられませんでした。
Jonathan Wakely 2014

0

コンパイラは、リテラルから最適な型を推測するか、少なくとも、最適であると考えるものを推測します。つまり、精度よりも効率が低下します。つまり、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' 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.