如果我有以下声明:
float a = 3.0 ;
Run Code Online (Sandbox Code Playgroud)
那是一个错误吗?我在一本书中读到了3.0一个double值,我必须将其指定为float a = 3.0f.是这样吗?
qua*_*dev 159
声明这不是错误float a = 3.0:如果这样做,编译器会将double literal 3.0转换为浮点数.
但是,您应该在特定方案中使用float literals表示法.
出于性能原因:
具体来说,考虑:
float foo(float x) { return x * 0.42; }
Run Code Online (Sandbox Code Playgroud)
在这里,编译器将为每个返回的值发出一个转换(您将在运行时支付).为了避免它,你应该声明:
float foo(float x) { return x * 0.42f; } // OK, no conversion required
Run Code Online (Sandbox Code Playgroud)为了避免在比较结果时出现错误:
例如,以下比较失败:
float x = 4.2;
if (x == 4.2)
std::cout << "oops"; // Not executed!
Run Code Online (Sandbox Code Playgroud)
我们可以使用float文字表示法修复它:
if (x == 4.2f)
std::cout << "ok !"; // Executed!
Run Code Online (Sandbox Code Playgroud)
(注意:当然,这不是你应该如何比较浮点数或双数字的相等性)
要调用正确的重载函数(出于同样的原因):
例:
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;
}
Run Code Online (Sandbox Code Playgroud)正如Cyber所指出的,在类型推导上下文中,有必要帮助编译器推导出float:
在以下情况下auto:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
Run Code Online (Sandbox Code Playgroud)
同样,在模板类型扣除的情况下:
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;
}
Run Code Online (Sandbox Code Playgroud)Cor*_*mer 22
编译器会将以下任何文字转换为浮点数,因为您将变量声明为浮点数.
float a = 3; // converted to float
float b = 3.0; // converted to float
float c = 3.0f; // float
Run Code Online (Sandbox Code Playgroud)
如果您使用auto(或其他类型的扣除方法),例如:
auto d = 3; // int
auto e = 3.0; // double
auto f = 3.0f; // float
Run Code Online (Sandbox Code Playgroud)
Sha*_*our 14
没有后缀的浮点文字是double类型,这在草案C++标准部分2.14.4 浮动文字中有所介绍:
[...]浮点文字的类型是双精度,除非后缀明确指定.[...]
因此,它是分配错误3.0一个双重文字的浮动?
float a = 3.0
Run Code Online (Sandbox Code Playgroud)
不,它不会,它将被转换,这在4.8 浮点转换部分中有所介绍:
浮点类型的prvalue可以转换为另一个浮点类型的prvalue.如果源值可以在目标类型中准确表示,则转换的结果就是精确表示.如果源值在两个相邻目标值之间,则转换结果是这些值中任一个的实现定义选择.否则,行为未定义.
我们可以在GotW#67中阅读更多有关此含义的详细信息:双重或没有任何内容:
这意味着双常量可以隐式(即,静默)转换为浮点常量,即使这样做会失去精度(即数据).由于C兼容性和可用性原因,这被允许保留,但是当您进行浮点工作时,请记住这一点.
如果您尝试执行未定义行为的事情,质量编译器会发出警告,即将双倍数量放入小于最小值的浮点数,或者大于浮点数能够表示的最大值.一个非常好的编译器会提供一个可选的警告,如果你试图做一些可能被定义但可能丢失信息的东西,即将一个double数量放入一个浮点数,浮点数可以浮点数表示的最小值和最大值之间,但不能完全表示为浮点数.
因此,您应该注意的一般情况有一些警告.
从实际角度来看,在这种情况下,结果很可能是相同的,即使在技术上存在转换,我们可以通过在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;
}
Run Code Online (Sandbox Code Playgroud)
我们看到结果func1和func2相同,使用clang和gcc:
func1():
movss xmm0, DWORD PTR .LC0[rip]
ret
func2():
movss xmm0, DWORD PTR .LC0[rip]
ret
Run Code Online (Sandbox Code Playgroud)
正如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
}
Run Code Online (Sandbox Code Playgroud)
导致以下程序集:
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
Run Code Online (Sandbox Code Playgroud)
无论您是否可以确定转换是否会对性能产生影响,使用正确的类型更好地记录您的意图.例如,使用显式转换static_cast也有助于澄清转换意图与意外相反,这可能表示错误或潜在错误.
注意
作为supercat通过如指出,乘法0.1和0.1f是不等价的.我只是引用评论,因为它非常好,总结可能不会公正:
例如,如果f等于100000224(它可以精确地表示为浮点数),则将其乘以十分之一应该产生向下舍入到10000022的结果,但乘以0.1f将产生错误地向上舍入到10000023的结果如果意图除以10,则乘以双常数0.1可能比除以10f更快,并且比乘以0.1f更精确.
我原来的观点是在另一个问题中展示一个错误的例子,但这很好地说明了玩具例子中可能存在的微妙问题.