是'float a = 3.0;' 正确的陈述?


86

如果我有以下声明:

float a = 3.0 ;

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


2
编译器会3.0为您将双精度文字转换为浮点数。最终结果与没有区别float a = 3.0f
David Heffernan 2014年

6
@EdHeal:是的,但是与这个关于C ++规则的问题并不特别相关。
基思·汤普森

20
好吧,至少您需要一个;之后。
2014年

3
10张赞成票,在评论中没有太多解释它们,非常令人沮丧。这是OP的第一个问题,如果人们认为值得进行10次否决,则应进行一些解释。这是一个有效的问题,具有非显而易见的含义,并且可以从答案和评论中学习很多有趣的东西。
Shafik Yaghmour

3
@HotLicks并不是要感觉不好或好,要确保它看起来不公平,但这就是生活,毕竟它们是独角兽。肯定投票不会取消您不喜欢的投票,就像投票不会取消您不喜欢的投票一样。如果人们认为这个问题可以解决,那么问问者肯定会第一次得到反馈。我没有理由拒绝投票,但我想知道为什么其他人这样做,尽管他们可以随意不说。
沙菲克·雅格慕

Answers:


159

声明不是错误float a = 3.0:如果这样做,编译器将为您将双字面量3.0转换为浮点数。


但是,您在特定情况下使用浮点文字表示法。

  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!
    

    (注意:当然,通常这不是您应该比较浮点数或双数以确保相等的方法

  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 2014年

@MatteoItalia,是的,我的意思42.0 OFC(编辑,感谢)
quantdev

2
@ChristianHackl转换4.2为@4.2f可能会产生设置FE_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=
Pascal Cuoq,2014年

1
如果要计算的值是的十分之一someFloat,则该表达式的someFloat * 0.1结果将比精确someFloat * 0.1f,而在许多情况下,比浮点除法便宜。例如,(float)(167772208.0f * 0.1)会正确舍入为16777220而不是16777222。某些编译器可能会用double乘法代替浮点除法,但对于那些没有用的乘法器(虽然不是所有值,但对很多都是安全的) )乘法可能是有用的优化,但前提是必须进行double倒数运算。
2014年

22

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

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 Floatingliteral部分中有介绍:

[...]除非后缀明确指定,否则浮动文字的类型为double。[...]

因此,它是分配错误3.0一个双重文字浮动

float a = 3.0

不,不是,它将进行转换,这将在4.8 浮点转换部分中介绍:

浮点类型的prvalue可以转换为另一种浮点类型的prvalue。如果可以在目标类型中准确表示源值,则转换的结果就是该准确表示。如果源值在两个相邻的目标值之间,则转换的结果是这些值之一的实现定义选择。否则,行为是不确定的。

我们可以在GotW#67中阅读更多有关此含义的详细信息:double或none表示:

这意味着可以将双精度常量隐式(即,无声地)转换为浮点常量,即使这样做会失去精度(即,数据)。出于C兼容性和可用性的原因,允许保留此设置,但是在进行浮点工作时,请记住这一点。

如果您尝试执行未定义行为的行为,即将小于浮点数可表示的最小值或大于最大浮点数的双倍数量放入浮点数中,则质量编译器会警告您。如果您尝试执行可能已定义但可能会丢失信息的操作,那么一个非常好的编译器会提供可选警告,即,将一个双精度数放入一个浮点数,该浮点数介于浮点数可表示的最小值和最大值之间,但不能精确地表示为浮点数

因此,您应该注意一般情况的注意事项。

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

正如Pascal在此评论中指出的那样,您将永远无法指望这一点。分别使用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

不管您是否可以确定转换是否会对性能产生影响,使用正确的类型都可以更好地证明您的意图。例如,使用显式转换static_cast还有助于阐明转换的意图,而不是偶然的,这可能表示错误或潜在的错误。

注意

正如超级猫指出的那样,乘以例如0.10.1f是不等效的。我只想引用此评论,因为它非常出色,而总结可能无法做到这一点:

例如,如果f等于100000224(可以精确地表示为浮点数),则将其乘以十分之一应会得出四舍五入为10000022的结果,而乘以0.1f则会得出错误地舍入为10000023的结果。如果打算除以10,那么乘以双常数0.1可能会比除以10f更快,并且比乘以0.1f更精确。

我的原始观点是要演示另一个问题中给出的错误示例,但这很好地演示了玩具示例中可能存在的细微问题。


1
可能值得注意的是,这些表达方式f = f * 0.1;f = f * 0.1f; 做不同的事情。例如,如果f等于100000224(精确地表示为a float),则将其乘以十分之一应会得出四舍五入为10000022的结果,而乘以0.1f则会得出错误地舍入为10000023的结果。目的是除以10,乘以double常数0.1可能会比除以更快10f,并且比乘以更精确0.1f
2014年

@supercat谢谢您提供的好例子,我直接引用了您,请根据需要随意编辑。
Shafik Yaghmour 2014年

4

从编译器将拒绝它的意义上说这不是错误,但是从某种意义上来说它不是您想要的东西是一个错误。

正如您的书正确指出的那样,3.0是type的值double。从double到都有隐式转换float,因此float a = 3.0;变量的有效定义也是如此。

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

3.0f 避免了这个问题:尽管从技术上讲,仍然允许编译器在运行时计算常数(始终为常数),但是在这里,绝对没有理由让任何编译器都可以这样做。


确实,在交叉编译器的情况下,在编译时执行转换是非常不正确的,因为它将在错误的平台上发生。
2014年

2

本身虽然不是错误,但是有点草率。您知道要使用浮点数,因此请使用浮点数对其进行初始化。
可能会出现另一个程序员,并且不确定声明的哪一部分正确,类型或初始化程序。为什么它们都不正确?
float答案= 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

如图所示,在32位计算机上,3.2f的大小取为4个字节,而在32位计算机上,3.2解释为取8个字节的double值。这应该提供您正在寻找的答案。


这表明doublefloat不同,它无法回答您是否可以float从双重文字中初始化a
Jonathan Wakely 2014年

当然,你可以初始化从双重价值受到数据截断的浮动,如果适用
Debasish贾纳博士

4
是的,我知道,但这是OP的问题,因此尽管声称提供了答案,您的回答却未能真正回答!
乔纳森·韦克利

0

编译器从文字中推导出最适合的类型,或者至少认为最适合的类型。那样会损失效率,而不是精度,即使用double而不是float。如有疑问,请使用花括号将其明确化:

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

如果从另一个使用类型转换规则的变量进行初始化,则故事会变得更加有趣:尽管构造一个双精度形式的文字是合法的,但不能从int构造它而不能缩小范围:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.