Should we generally use float literals for floats instead of the simpler double literals?

后端 未结 7 821
轻奢々
轻奢々 2020-11-29 04:47

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

相关标签:
7条回答
  • 2020-11-29 05:01

    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

    0 讨论(0)
  • 2020-11-29 05:03

    Yes, you should use the f suffix. Reasons include:

    1. 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.

    2. 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.

    3. 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.

    0 讨论(0)
  • 2020-11-29 05:04

    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.

    Update for 64-bit

    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.

    0 讨论(0)
  • 2020-11-29 05:09

    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 ).

    0 讨论(0)
  • 2020-11-29 05:22

    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.

    0 讨论(0)
  • 2020-11-29 05:22

    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.

    0 讨论(0)
提交回复
热议问题