是'浮动a = 3.0;' 一个正确的说法?

如果我有以下声明:

float a = 3.0 ; 

是一个错误? 我在一本书中读到3.0是一个double值,我必须将其指定为float a = 3.0f 。 是这样吗?

声明float a = 3.0并不是一个错误:如果你这样做,编译器会将双字面值3.0转换为你的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! 

    我们可以用浮点文字符号来解决它:

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

    (注:当然, 这不是你应该如何比较一般的平等浮点数或双数 )

  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​​所指出的那样 ,在types演绎上下文中,有必要帮助编译器推导一个float

    auto情况下:

     auto d = 3; // int auto e = 3.0; // double auto f = 3.0f; // float 

    同样,在模板types扣除的情况下:

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

现场演示

编译器会将以下任何文字转换为浮点数,因为您将该variables声明为浮点数。

 float a = 3; // converted to float float b = 3.0; // converted to float float c = 3.0f; // float 

重要的是如果你使用auto (或其他types的扣除方法),例如:

 auto d = 3; // int auto e = 3.0; // double auto f = 3.0f; // float 

没有后缀的浮点文字是doubletypes的,这在草案C ++标准部分中有介绍2.14.4 浮动文字

[…]浮动文字的types是双重的,除非由后缀明确指定。

所以将一个双重字符赋值给一个float是错误的:

 float a = 3.0 

不,它将被转换,这在第4.8节中有介绍。 浮点转换

浮点types的前值可以转换为另一个浮点types的前值。 如果源值可以在目标types中精确表示,则转换的结果就是该确切的表示。 如果源值在两个相邻的目标值之间,则转换的结果是这些值中任一个的实现定义的select。 否则,行为是不确定的。

我们可以在GotW#67中看到更多关于这个含义的细节:double或者nothing :

这意味着即使这样做会丢失精度(即数据),双精度常量也可以隐含地(即静默地)转换为浮点常量。 考虑到C兼容性和可用性的原因,这是允许的,但是当你做浮点运算的时候值得记住。

一个质量编译器会警告你,如果你试图做一些未定义的行为,也就是将一个double数量放入一个小于最小值的浮点数,或者大于浮点数能够表示的最大值。 一个非常好的编译器会提供一个可选的警告,如果你试图做一些可能被定义但可能会丢失信息的东西,也就是把一个double数量放到一个浮点数的最小值和最大值之间,但是不能被完全表示为一个浮点数。

所以,你应该知道的一般情况有一些警告。

从实际的angular度来看,在这种情况下,即使技术上存在转换,结果也很可能是相同的,我们可以通过在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; } 

我们看到func1func2的结果是一样的,同时使用clanggcc

 func1(): movss xmm0, DWORD PTR .LC0[rip] ret func2(): movss xmm0, DWORD PTR .LC0[rip] ret 

正如帕斯卡尔在这个评论中指出的那样,你不会总是能够依靠这一点。 分别使用0.10.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 

无论您是否可以确定转换是否会影响性能,使用正确的types可以更好地logging您的意图。 例如,使用明确的转换static_cast也有助于澄清转换意图,而不是意外,这可能意味着一个错误或潜在的错误。

注意

正如超级卡特指出的那样,乘以例如0.10.1f是不相等的。 我只是想引用这个评论,因为它非常好,而且一个总结可能不会公正:

例如,如果f等于100000224(它可以精确地表示为一个浮点数),将其乘以十分之一应该产生一个下舍入到10000022的结果,但乘以0.1f将会产生错误地舍入到10000023的结果如果意图除以十,则乘以双倍常数0.1可能会快于除以10f,并且比乘以0.1f更精确。

我最初的观点是要certificate在另一个问题上给出了一个假的例子,但是这很好地certificate了微妙的问题可以存在于玩具的例子中。

编译器会拒绝它,这不是一个错误,但从某种意义上说,这可能不是你想要的。

正如您的书正确指出的那样, 3.0doubletypes的值。 有一个从doublefloat的隐式转换,所以float a = 3.0; 是一个variables的有效定义。

然而,至less在概念上,这是一个不必要的转换。 根据编译器的不同,转换可以在编译时执行,也可以保存为运行时。 将其保存为运行时的一个合理原因是浮点转换很困难,如果无法准确表示值,可能会产生意想不到的副作用,并且validation该值是否可以精确表示也并非易事。

3.0f可以避免这个问题:尽pipe在技术上,编译器仍然允许在运行时计算常量(总是),在这里,任何编译器都不可能做到这一点。

虽然这不是一个错误,但本质上,这是一个马虎。 你知道你想要一个浮点数,所以用浮点数来初始化它。
另一个程序员可能会出现,不能确定声明的哪一部分是正确的,types或初始化程序。 为什么不让他们都是正确的?
float答案= 42.0f;

当你定义一个variables时,它会使用提供的初始化程序进行初始化。 这可能需要将初始化器的值转换为正在初始化的variables的types。 这就是当你说float a = 3.0;时发生的事情float a = 3.0; :初始化器的值被转换为float ,并且转换的结果成为a的初始值。

这通常是好的,但是写3.0f来表明你意识到你正在做什么,特别是如果你想写auto a = 3.0f ,这并没有什么坏处。

如果您尝试以下操作:

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

你会得到如下输出:

 4:8 

表示,在32位机上3.2f的大小被取为4字节,在32位机上3.2被解释为取8字节的双值。 这应该提供你正在寻找的答案。

编译器从文字推导出最适合的types,或者至less认为它是最合适的。 这是相当精确的失去效率,即使用双重而不是浮动。 如果有疑问,请使用大括号使其明确:

 auto d = double{3}; // make a double auto f = float{3}; // make a float auto i = int{3}; // make a int 

如果从另一个应用types转换规则的variables初始化,故事会变得更加有趣:虽然build立一个双重forms的文字是合法的,但它不能从int中构造而不会缩小:

 auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'