Detailed Explanation of C++ Floating Point Types

Like ANSI C, C++ provides three floating-point types to handle decimals and scientific calculations. Their main differences lie in precision (significant digits), range of representation, and memory usage.

1. Core Concepts

  • Precision (Significant Digits): Refers to the number of digits that can be accurately represented in a floating-point number, starting from the first non-zero digit. It determines the accuracy of the value.

    • Written as <span>14179</span>: 5 significant digits (1, 4, 1, 7, 9).
    • Written approximately as <span>14000</span>: 2 significant digits (1, 4), with the three zeros being placeholders.
    • Written as <span>14.162</span> feet: still 5 significant digits (1, 4, 1, 6, 2). This illustrates that the number of significant digits is independent of the decimal point position.
    • Your example is great: Mount Shasta is 14179 feet high.
  • Memory Usage and Standard Requirements:

    • <span>float</span>: Single precision. Typically occupies 32 bits (4 bytes).
    • <span>double</span>: Double precision. Typically occupies 64 bits (8 bytes). The C++ standard guarantees that the precision of <span>double</span> is not less than that of <span>float</span>.
    • <span>long double</span>: Extended double precision. Typically 80 bits, 96 bits, or 128 bits. The C++ standard guarantees that the precision of <span>long double</span> is not less than that of <span>double</span>.
  • Exponent Range: The standard requires that the exponent range for these three types is at least 10⁻³⁷ to 10³⁷, meaning they can represent very small and very large numbers.

2. How to View System-Specific Limits

You can obtain detailed limits of floating-point types on your current compilation platform by including <span><cfloat></span> (C++ style) or <span><float.h></span> (C style) header files. These header files define many macros.

Code Example and Explanation

The following code demonstrates the basic usage of the three floating-point types and shows how to obtain system-specific limits from <span><cfloat></span>.

#include <iostream>
#include <iomanip> // For controlling output format, e.g., std::setprecision
#include <cfloat>  // Includes constants for floating-point type limits

int main() {
    using std::cout;
    using std::endl;
    using std::setprecision;

    // 1. Variable declaration and initialization
    float price = 19.99f;        // Note the 'f' suffix, explicitly indicating float type; otherwise, it defaults to double
    double distance = 3.141592653589793;
    long double atomWidth = 1.0e-15L; // Note the 'L' suffix, indicating long double

    // 2. Output variable values, demonstrating default precision
    cout << "=== Variable Values and Default Precision ===" << endl;
    cout << "Price (float): " << price << endl;
    cout << "Distance (double): " << distance << endl;
    cout << "Atom Width (long double): " << atomWidth << endl;
    cout << endl;

    // 3. Use setprecision to control the number of decimal places in output
    // It affects the number of digits displayed in the output stream but does not change the precision of the variable itself.
    cout << "=== Using setprecision(10) to Control Output ===" << endl;
    cout << setprecision(10); // Set subsequent output to display 10 significant digits
    cout << "Price (float): " << price << endl;
    cout << "Distance (double): " << distance << endl;
    cout << "Atom Width (long double): " << atomWidth << endl;
    cout << endl;

    // 4. View precision defined in <cfloat> (decimal significant digits)
    cout << "=== Getting System Precision from <cfloat> ===" << endl;
    cout << "Decimal significant digits for float (FLT_DIG): " << FLT_DIG << endl;
    cout << "Decimal significant digits for double (DBL_DIG): " << DBL_DIG << endl;
    cout << "Decimal significant digits for long double (LDBL_DIG): " << LDBL_DIG << endl;
    cout << endl;

    // 5. Demonstration of precision issues
    // float has about 7 significant decimal digits, exceeding this may lead to inaccuracies
    cout << "=== Demonstration of Precision Issues ===" << endl;
    float f1 = 1234567.89f;
    float f2 = 1234560.0f;
    
    cout << setprecision(10);
    cout << "f1: " << f1 << endl; // May not accurately display all digits
    cout << "f2: " << f2 << endl;
    
    // A dangerous comparison: due to precision limits, they may not be equal
    float f_sum = 0.1f + 0.1f + 0.1f;
    float f_expected = 0.3f;
    
    cout << "f_sum (0.1+0.1+0.1): " << f_sum << endl;
    cout << "f_expected (0.3): " << f_expected << endl;
    cout << "Are they equal? " << (f_sum == f_expected ? "Yes" : "No") << endl; // Likely outputs No!
    cout << "Difference: " << f_sum - f_expected << endl; // Displays a small error
    cout << endl;

    // 6. View other system limits
    cout << "=== Other System Limits (from <cfloat>) ===" << endl;
    cout << "Minimum positive float (FLT_MIN): " << FLT_MIN << endl;
    cout << "Maximum float (FLT_MAX): " << FLT_MAX << endl;
    cout << "Minimum positive double (DBL_MIN): " << DBL_MIN << endl;
    cout << "Maximum double (DBL_MAX): " << DBL_MAX << endl;

    return 0;
}

Code Output and Key Point Explanation

Running the above code, you may see output similar to the following (specific values depend on your compiler and system):

=== Variable Values and Default Precision ===
Price (float): 19.99
Distance (double): 3.14159
Atom Width (long double): 1e-15

=== Using setprecision(10) to Control Output ===
Price (float): 19.99000066
Distance (double): 3.141592654
Atom Width (long double): 1.0000000000000001e-15

=== Getting System Precision from <cfloat> ===
Decimal significant digits for float (FLT_DIG): 6
Decimal significant digits for double (DBL_DIG): 15
Decimal significant digits for long double (LDBL_DIG): 18

=== Demonstration of Precision Issues ===
f1: 1234567.875
f2: 1234560
f_sum (0.1+0.1+0.1): 0.3000000119
f_expected (0.3): 0.3000000000
Are they equal? No
Difference: 1.192092896e-08

=== Other System Limits (from <cfloat>) ===
Minimum positive float (FLT_MIN): 1.17549e-38
Maximum float (FLT_MAX): 3.40282e+38
Minimum positive double (DBL_MIN): 2.22507e-308
Maximum double (DBL_MAX): 1.79769e+308

Key Point Explanation:

  1. The Importance of Suffixes: The <span>f</span> in <span>19.99f</span> tells the compiler that this is a <span>float</span> literal. Floating-point literals without a suffix (like <span>3.14159</span>) default to <span>double</span> type. Use <span>L</span> to indicate <span>long double</span>.
  2. Output Precision Control: <span>std::setprecision(n)</span> controls the number of digits displayed in the output stream, but it does not change the actual precision of the variable in memory. When you output a high-precision <span>float</span>, it exposes the fact that it cannot accurately represent all numbers (e.g., <span>Price</span> becomes <span>19.99000066</span>).
  3. Verification of Significant Digits: The output shows that <span>FLT_DIG</span> is 6, meaning the <span>float</span> type guarantees at least 6 significant decimal digits. This is why <span>1234567.89</span> becomes inaccurate when outputting the 8th digit and beyond.
  4. The Danger of Floating-Point Comparisons: The code clearly demonstrates that <span>(0.1 + 0.1 + 0.1) == 0.3</span> results in <span>false</span>! This is because numbers like 0.1 cannot be precisely represented in binary floating-point (similar to how 1/3 cannot be precisely represented in decimal), leading to small rounding errors. Never directly use <span>==</span> or <span>!=</span> to compare floating-point numbers; instead, check if the absolute difference is within a small tolerance (epsilon).
  5. Huge Range of Representation: From <span>FLT_MAX</span> and <span>DBL_MAX</span>, it can be seen that floating-point types can represent extremely large numbers.

Summary and Recommendations

  • Default Choice: For most general floating-point operations, **<span>double</span>** is the best balance of precision and performance on modern computers and is the recommended default choice.
  • **When to Use <span>float</span>**: When handling large amounts of data (such as graphics, scientific simulations) where memory bandwidth is a bottleneck, or in some embedded systems, using <span>float</span> can save space.
  • **When to Use <span>long double</span>**: When extremely high precision calculations are needed, and performance is not a high priority (such as in finance, cryptography, or certain scientific fields).
  • Be Aware of Precision Limits: Always be aware that floating-point numbers are approximate representations, and be particularly careful when making equality comparisons.

Detailed Explanation of C++ Floating Point Types

Leave a Comment