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
后缀.原因包括:
性能.在编写时float foo(float x) { return x*3.14; }
,强制编译器发出将x转换为double的代码,然后执行乘法,然后将结果转换回单个.如果添加f
后缀,则会消除两次转换.在许多平台上,每次转换都与乘法本身一样昂贵.
表现(续).有平台(例如大多数手机),双精度算术比单精度慢得多.即使忽略转换开销(在1.中涵盖),每次强制计算以double计算时,都会降低程序的速度.这不仅仅是一个"理论"问题.
减少你对bug的暴露.考虑例如float x = 1.2; if (x == 1.2) // something;
是否something
执行?不,它不是,因为x保持1.2
舍入到a float
,但是与双精度值进行比较1.2
.两者并不相等.
我怀疑这样的事情:如果你正在使用一个浮点变量和一个双字面值,整个操作将以double形式完成,然后转换回float.
如果你使用浮点字面值,从概念上讲,计算将以浮点精度完成,即使某些硬件将其转换为双倍以进行计算.
我做了一个测试.
我编译了这段代码:
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)
但是加载单精度或双精度常数之间仍然存在差异.
这些是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,结果是一样的.