In C++ (or maybe only our compilers VC8 and VC10) 3.14
is a double literal and 3.14f
is a float literal.
Now I have a colleague
I personally tend to use the f postfix notation as a matter of principles and to make it obvious as much as I can that this is a float type rather than a double.
My two cents
Yes, you should use the f
suffix. Reasons include:
Performance. When you write float foo(float x) { return x*3.14; }
, you force the compiler to emit code that converts x to double, then does the multiplication, then converts the result back to single. If you add the f
suffix, then both conversions are eliminated. On many platforms, each those conversions are about as expensive as the multiplication itself.
Performance (continued). There are platforms (most cellphones, for example), on which double-precision arithmetic is dramatically slower than single-precision. Even ignoring the conversion overhead (covered in 1.), every time you force a computation to be evaluated in double, you slow your program down. This is not just a "theoretical" issue.
Reduce your exposure to bugs. Consider the example float x = 1.2; if (x == 1.2) // something;
Is something
executed? No, it is not, because x holds 1.2
rounded to a float
, but is being compared to the double-precision value 1.2
. The two are not equal.
I did a test.
I compiled this code:
float f1(float x) { return x*3.14; }
float f2(float x) { return x*3.14F; }
Using gcc 4.5.1 for i686 with optimization -O2.
This was the assembly code generated for 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
And this is the assembly code generated for f2:
pushl %ebp
flds .LC2 # Load a single-precision floating point constant
movl %esp, %ebp
fmuls 8(%ebp) # Multiply by parameter
popl %ebp
ret
So the interesting thing is that for f1, the compiler stored the value and re-loaded it just to make sure that the result was truncated to single-precision.
If we use the -ffast-math option, then this difference is significantly reduced:
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
But there is still the difference between loading a single or double precision constant.
These are the results with gcc 5.2.1 for x86-64 with optimization -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
With -ffast-math, the results are the same.
From the C++ Standard ( Working Draft ), section 5 on binary operators
Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows: — If either operand is of scoped enumeration type (7.2), no conversions are performed; if the other operand does not have the same type, the expression is ill-formed. — If either operand is of type long double, the other shall be converted to long double. — Otherwise, if either operand is double, the other shall be converted to double. — Otherwise, if either operand is float, the other shall be converted to float.
And also section 4.8
A prvalue of floating point type can be converted to a prvalue of another floating point type. If the source value can be exactly represented in the destination type, the result of the conversion is that exact representation. If the source value is between two adjacent destination values, the result of the conversion is an implementation-defined choice of either of those values. Otherwise, the behavior is undefined
The upshot of this is that you can avoid unnecessary conversions by specifying your constants in the precision dictated by the destination type, provided that you will not lose precision in the calculation by doing so (ie, your operands are exactly representable in the precision of the destination type ).
I suspect something like this: If you're working with a float variable and a double literal the whole operation will be done as double and then converted back to float.
If you use a float literal, notionally speaking the computation will be done at float precision even though some hardware will convert it to double anyway to do the calculation.
Typically, I don't think it will make any difference, but it is worth
pointing out that 3.1415f
and 3.1415
are (typically) not equal. On
the other hand, you don't normally do any calculations in float
anyway, at least on the usual platforms. (double
is just as fast, if
not faster.) About the only time you should see float
is when there
are large arrays, and even then, all of the calculations will typically
be done in double
.