如果我有以下声明:
float a = 3.0 ;
这是一个错误吗?我读了一本书,这3.0
是一个double
值,必须将其指定为float a = 3.0f
。是这样吗?
如果我有以下声明:
float a = 3.0 ;
这是一个错误吗?我读了一本书,这3.0
是一个double
值,必须将其指定为float a = 3.0f
。是这样吗?
;
之后。
Answers:
声明不是错误float a = 3.0
:如果这样做,编译器将为您将双字面量3.0转换为浮点数。
但是,您应在特定情况下使用浮点文字表示法。
出于性能原因:
具体来说,请考虑:
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)会正确舍入为16777220而不是16777222。某些编译器可能会用double
乘法代替浮点除法,但对于那些没有用的乘法器(虽然不是所有值,但对很多都是安全的) )乘法可能是有用的优化,但前提是必须进行double
倒数运算。
编译器会将以下任何文字转换为浮点数,因为您将变量声明为浮点数。
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
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;
}
而我们看到的结果func1
和func2
是相同的,同时使用clang
和gcc
:
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
正如Pascal在此评论中指出的那样,您将永远无法指望这一点。分别使用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(可以精确地表示为浮点数),则将其乘以十分之一应会得出四舍五入为10000022的结果,而乘以0.1f则会得出错误地舍入为10000023的结果。如果打算除以10,那么乘以双常数0.1可能会比除以10f更快,并且比乘以0.1f更精确。
我的原始观点是要演示另一个问题中给出的错误示例,但这很好地演示了玩具示例中可能存在的细微问题。
f = f * 0.1;
和f = f * 0.1f;
做不同的事情。例如,如果f
等于100000224(精确地表示为a float
),则将其乘以十分之一应会得出四舍五入为10000022的结果,而乘以0.1f则会得出错误地舍入为10000023的结果。目的是除以10,乘以double
常数0.1可能会比除以更快10f
,并且比乘以更精确0.1f
。
从编译器将拒绝它的意义上说这不是错误,但是从某种意义上来说它不是您想要的东西是一个错误。
正如您的书正确指出的那样,3.0
是type的值double
。从double
到都有隐式转换float
,因此float a = 3.0;
变量的有效定义也是如此。
但是,至少在概念上,这执行了不必要的转换。根据编译器的不同,转换可以在编译时执行,也可以保存为运行时。将其保存为运行时的一个合理原因是,浮点转换很困难,并且如果无法精确表示该值,可能会产生意想不到的副作用,并且验证该值是否可以正确表示并不总是那么容易。
3.0f
避免了这个问题:尽管从技术上讲,仍然允许编译器在运行时计算常数(始终为常数),但是在这里,绝对没有理由让任何编译器都可以这样做。
如果您尝试以下方法:
std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;
您将获得以下输出:
4:8
如图所示,在32位计算机上,3.2f的大小取为4个字节,而在32位计算机上,3.2解释为取8个字节的double值。这应该提供您正在寻找的答案。
double
和float
不同,它无法回答您是否可以float
从双重文字中初始化a
编译器从文字中推导出最适合的类型,或者至少认为最适合的类型。那样会损失效率,而不是精度,即使用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'
3.0
为您将双精度文字转换为浮点数。最终结果与没有区别float a = 3.0f
。