在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?
或者,我添加,甚至:
d1 = 42 * d2;
d1 = 42.0f * d2; // any difference?
d1 = 42.0 * d2; // any difference?
更一般地说,我可以看到使用2.71828183f
的仅点是为了确保我想要指定的常量实际上适合浮点数(编译器错误/警告)否则)。
有人可以对此有所了解吗?你指定f
后缀吗?为什么呢?
引用答案,我暗示理所当然:
如果你正在使用float变量和double literal整体 操作将以double的形式完成,然后转换回float。
这可能有任何伤害吗? (除了非常非常理论的性能影响之外?)
进一步编辑:如果包含技术详细信息的答案(赞赏!)也可以包含这些差异如何影响通用代码,那就太好了。 (是的,如果你是数字运算,你可能希望确保你的big-n浮点运算尽可能高效(和正确) - 但对于被称为几次的通用代码是否重要?Isn'如果代码只使用0.0
并跳过 - 难以维护! - float后缀?)
答案 0 :(得分:48)
是的,您应该使用f
后缀。原因包括:
性能。当您编写float foo(float x) { return x*3.14; }
时,强制编译器发出将x转换为double的代码,然后执行乘法,然后将结果转换回单个。如果您添加f
后缀,则会消除两次转化。在许多平台上,每次转换都与乘法本身一样昂贵。
表演(续)。有平台(例如大多数手机),双精度算术比单精度慢得多。甚至忽略转换开销(在1.中涵盖),每次强制计算以double计算时,都会降低程序的速度。这不仅仅是一个“理论上的”问题。
减少您对虫子的暴露。考虑示例float x = 1.2; if (x == 1.2) // something;
是否something
已执行?不,它不是,因为x将1.2
四舍五入到float
,但是与双精度值1.2
进行比较。两者并不相等。
答案 1 :(得分:9)
我怀疑这样的事情:如果你正在使用float变量和double literal,整个操作将以double形式完成,然后转换回float。
如果使用浮点字面值,从概念上讲,计算将以浮点精度完成,即使某些硬件仍会将其转换为double以进行计算。
答案 2 :(得分:8)
我做了一个测试。
我编译了这段代码:
float f1(float x) { return x*3.14; }
float f2(float x) { return x*3.14F; }
使用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
这是为f2生成的汇编代码:
pushl %ebp
flds .LC2 # Load a single-precision floating point constant
movl %esp, %ebp
fmuls 8(%ebp) # Multiply by parameter
popl %ebp
ret
有趣的是,对于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
但是加载单精度或双精度常数之间仍然存在差异。
这些是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
F2:
mulss .LC2(%rip), %xmm0 # Single-precision multiply
ret
使用-ffast-math,结果是一样的。
答案 3 :(得分:3)
通常情况下,我认为它不会产生任何影响,但值得
指出3.1415f
和3.1415
(通常)不相等。上
另一方面,您通常不会在float
中进行任何计算
无论如何,至少在通常的平台上。 (double
同样快
不会更快。)关于你应该看到float
的唯一时间就在那里
是大型数组,即使这样,所有的计算通常都是
在double
完成。
答案 4 :(得分:1)
存在差异:如果使用double常量并将其与float变量相乘,则变量首先转换为double,计算以double形式完成,然后将结果转换为float。虽然精确度在这里确实不是问题,但这可能会导致混淆。
答案 5 :(得分:1)
我个人倾向于使用f后缀表示法作为原则问题,并尽可能明显地说明这是浮点型而不是双精度。
我的两分钱
答案 6 :(得分:1)
来自C++ Standard ( Working Draft ),关于二元运算符的第5节
许多期望算术或算术操作数的二元运算符 枚举类型导致转换并产生类似的结果类型 办法。目的是产生一种普通类型,它也是一种类型 结果。这种模式称为通常的算术转换, 其定义如下: - 如果任一操作数是作用域 枚举类型(7.2),不执行转换;如果是另一个 操作数不具有相同的类型,表达式是不正确的。 - 如果任一操作数的类型为long double,则另一个操作数将被转换 长一倍。 - 否则,如果任一操作数是双倍,则另一个 应转换为双倍。 - 否则,如果任一操作数是浮点数, 另一个应转换为浮动。
还有第4.8节
浮点数类型的prvalue可以转换为prvalue 另一种浮点类型。如果源值可以准确 在目标类型中表示,转换的结果是 确切的表示。如果源值在两个相邻之间 目的地值,转换的结果是 实现 - 定义这些值中的任何一个的选择。否则, 行为未定
这样做的结果是,您可以通过以目标类型指定的精度指定常量来避免不必要的转换,前提是您不会因为这样做而失去计算精度(即,您的操作数可以在目的地类型的精度)。