我们通常应该使用浮点文字来表示浮点数而不是更简单的双重文字吗?

Mar*_* Ba 29 c++ floating-point double literals

在C++中(或者只有我们的编译器VC8和VC10) 3.14是双字面文并且3.14f是浮点字面值.

现在我有一位同事说:

我们应该使用float-literals进行浮点计算,使用double-literals进行双重计算,因为在计算中使用常量时,这可能会影响计算的精度.

具体来说,我认为他的意思是:

double d1, d2;
float f1, f2;
... init and stuff ...
f1 = 3.1415  * f2;
f1 = 3.1415f * f2; // any difference?
d1 = 3.1415  * d2;
d1 = 3.1415f * d2; // any difference?
Run Code Online (Sandbox Code Playgroud)

或者,由我添加,甚至:

d1 = 42    * d2;
d1 = 42.0f * d2; // any difference?
d1 = 42.0  * d2; // any difference?
Run Code Online (Sandbox Code Playgroud)

更一般地,只有我可以看到用点2.71828183f是确保我试图不断的指定实际上将放入一个浮动(编译错误/警告其他方式).

有人可以对此有所了解吗?你指定f后缀吗?为什么?

从答案中引用我隐含的理所当然:

如果你正在使用float变量和double literal,整个操作将以double形式完成,然后转换回float.

可能有任何伤害吗?(除了非常非常理论的性能影响?)

进一步编辑:如果包含技术细节的答案(赞赏!)也可以包括这些差异如何影响通用代码,那将是很好的.(是的,如果你是数字运算,你可能希望确保你的big-n浮点运算尽可能高效(和正确) - 但这对于被称为几次的通用代码是否重要?Isn'如果代码只使用0.0并跳过 - 难以维护,那么它会更清洁! - float后缀?)

Ste*_*non 49

是的,你应该使用f后缀.原因包括:

  1. 性能.在编写时float foo(float x) { return x*3.14; },强制编译器发出将x转换为double的代码,然后执行乘法,然后将结果转换回单个.如果添加f后缀,则会消除两次转换.在许多平台上,每次转换都与乘法本身一样昂贵.

  2. 表现(续).有平台(例如大多数手机),双精度算术比单精度慢得多.即使忽略转换开销(在1.中涵盖),每次强制计算以double计算时,都会降低程序的速度.这不仅仅是一个"理论"问题.

  3. 减少你对bug的暴露.考虑例如float x = 1.2; if (x == 1.2) // something; 是否something执行?不,它不是,因为x保持1.2舍入到a float,但是与双精度值进行比较1.2.两者并不相等.

  • @supercat:`clang -Wconversion` 产生`警告:隐式转换失去浮点精度:'double' to 'float'` 用于 double-to-float,并且没有对 float-to-double 的诊断。值“1.f/10.f”毫无疑问是一个“float”,无论程序员的意图是什么,它的值都可以精确地用“double”来表示,因此没有损失需要警告。 (2认同)

Mar*_*k B 9

我怀疑这样的事情:如果你正在使用一个浮点变量和一个双字面值,整个操作将以double形式完成,然后转换回float.

如果你使用浮点字面值,从概念上讲,计算将以浮点精度完成,即使某些硬件将其转换为双倍以进行计算.

  • @DeadMG:x86使用80位扩展*,如果你在一个将浮点计算编码为x87并且没有在控制字*中设置精度的系统上.越来越多的系统使用SSE进行浮点计算,这完全避免了这个细节.除非您特别要求使用80位格式,否则新软件应使用SSE. (2认同)

Vau*_*ato 8

我做了一个测试.

我编译了这段代码:

float f1(float x) { return x*3.14; }            
float f2(float x) { return x*3.14F; }   
Run Code Online (Sandbox Code Playgroud)

使用gcc 4.5.1 for i686进行优化-O2.

这是为f1生成的汇编代码:

pushl   %ebp
movl    %esp, %ebp
subl    $4, %esp # Allocate 4 bytes on the stack
fldl    .LC0     # Load a double-precision floating point constant
fmuls   8(%ebp)  # Multiply by parameter
fstps   -4(%ebp) # Store single-precision result on the stack
flds    -4(%ebp) # Load single-precision result from the stack
leave
ret
Run Code Online (Sandbox Code Playgroud)

这是为f2生成的汇编代码:

pushl   %ebp
flds    .LC2          # Load a single-precision floating point constant
movl    %esp, %ebp
fmuls   8(%ebp)       # Multiply by parameter
popl    %ebp
ret
Run Code Online (Sandbox Code Playgroud)

所以有趣的是,对于f1,编译器存储了值并重新加载它只是为了确保结果被截断为单精度.

如果我们使用-ffast-math选项,那么这个差异会大大减少:

pushl   %ebp
fldl    .LC0             # Load double-precision constant
movl    %esp, %ebp
fmuls   8(%ebp)          # multiply by parameter
popl    %ebp
ret


pushl   %ebp
flds    .LC2             # Load single-precision constant
movl    %esp, %ebp
fmuls   8(%ebp)          # multiply by parameter
popl    %ebp
ret
Run Code Online (Sandbox Code Playgroud)

但是加载单精度或双精度常数之间仍然存在差异.

更新为64位

这些是gcc 5.2.1 for x86-64的优化结果-O2:

F1:

cvtss2sd  %xmm0, %xmm0       # Convert arg to double precision
mulsd     .LC0(%rip), %xmm0  # Double-precision multiply
cvtsd2ss  %xmm0, %xmm0       # Convert to single-precision
ret
Run Code Online (Sandbox Code Playgroud)

F2:

mulss     .LC2(%rip), %xmm0  # Single-precision multiply
ret
Run Code Online (Sandbox Code Playgroud)

使用-ffast-math,结果是一样的.