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

时间:2011-10-05 13:34:53

标签: 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?

或者,我添加,甚至:

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后缀?)

7 个答案:

答案 0 :(得分:48)

是的,您应该使用f后缀。原因包括:

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

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

  3. 减少您对虫子的暴露。考虑示例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

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

更新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

F2:

mulss     .LC2(%rip), %xmm0  # Single-precision multiply
ret

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

答案 3 :(得分:3)

通常情况下,我认为它不会产生任何影响,但值得 指出3.1415f3.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   另一种浮点类型。如果源值可以准确   在目标类型中表示,转换的结果是   确切的表示。如果源值在两个相邻之间   目的地值,转换的结果是   实现 - 定义这些值中的任何一个的选择。否则,   行为未定

这样做的结果是,您可以通过以目标类型指定的精度指定常量来避免不必要的转换,前提是您不会因为这样做而失去计算精度(即,您的操作数可以在目的地类型的精度)。