Because floating-point numbers represent real numbers, it is often mistakenly assumed that they can represent any simple fraction exactly. Floating-point numbers are subject to representational limitations just as integers are, and binary floating-point numbers cannot represent all real numbers exactly, even if they can be represented in a small number of decimal digits.

In addition, because floating-point numbers can represent large values, it is often mistakenly assumed that they can represent all significant digits of those values. To gain a large dynamic range, floating-point numbers maintain a fixed number of precision bits (also called the significand) and an exponent, which limit the number of significant digits they can represent.

Different implementations have different precision limitations, and to keep code portable, floating-point variables must not be used as the loop induction variable. See Goldberg's work for an introduction to this topic [Goldberg 1991].

For the purpose of this rule, a loop counter is an induction variable that is used as an operand of a comparison expression that is used as the controlling expression of a do, while, or for loop. An induction variable is a variable that gets increased or decreased by a fixed amount on every iteration of a loop [Aho 1986]. Furthermore, the change to the variable must occur directly in the loop body (rather than inside a function executed within the loop).

Noncompliant Code Example

In this noncompliant code example, a floating-point variable is used as a loop counter. The decimal number 0.1 is a repeating fraction in binary and cannot be exactly represented as a binary floating-point number. Depending on the implementation, the loop may iterate 9 or 10 times.

void func(void) {
  for (float x = 0.1f; x <= 1.0f; x += 0.1f) {
    /* Loop may iterate 9 or 10 times */
  }
}

For example, when compiled with GCC or Microsoft Visual Studio 2013 and executed on an x86 processor, the loop is evaluated only nine times.

Compliant Solution

In this compliant solution, the loop counter is an integer from which the floating-point value is derived:

#include <stddef.h>
 
void func(void) {
  for (size_t count = 1; count <= 10; ++count) {
    float x = count / 10.0f;
    /* Loop iterates exactly 10 times */
  }
}

Noncompliant Code Example

In this noncompliant code example, a floating-point loop counter is incremented by an amount that is too small to change its value given its precision:

void func(void) {
  for (float x = 100000001.0f; x <= 100000010.0f; x += 1.0f) {
    /* Loop may not terminate */
  }
}

On many implementations, this produces an infinite loop.

Compliant Solution

In this compliant solution, the loop counter is an integer from which the floating-point value is derived. The variable x is assigned a computed value to reduce compounded rounding errors that are present in the noncompliant code example.

void func(void) {
  for (size_t count = 1; count <= 10; ++count) {
    float x = 100000000.0f + (count * 1.0f);
    /* Loop iterates exactly 10 times */
  }
}

Risk Assessment

The use of floating-point variables as loop counters can result in  unexpected behavior .

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

FLP30-C

Low

Probable

Low

P6

L2

Automated Detection

Tool

Version

Checker

Description

Astrée
24.04
for-loop-floatFully checked
Axivion Bauhaus Suite

7.2.0

CertC-FLP30Fully implemented
Clang
3.9
cert-flp30-cChecked by clang-tidy
CodeSonar
8.1p0
LANG.STRUCT.LOOP.FPCFloat-typed loop counter
Compass/ROSE




Coverity
2017.07

MISRA C 2004 Rule 13.4

MISRA C 2012 Rule 14.1

Implemented
ECLAIR
1.2
CC2.FLP30Fully implemented
Helix QAC

2024.1

C3339, C3340, C3342

C++4234


Klocwork
2024.1
MISRA.FOR.COUNTER.FLT
LDRA tool suite
9.7.1

39 S

Fully implemented

Parasoft C/C++test
2023.1
CERT_C-FLP30-a
Do not use floating point variables as loop counters
PC-lint Plus

1.4

9009

Fully supported

Polyspace Bug Finder

R2023b

CERT C: Rule FLP30-C

Checks for use of float variable as loop counter (rule fully covered)

PVS-Studio

7.30

V1034
RuleChecker
24.04
for-loop-floatFully checked
SonarQube C/C++ Plugin

3.11

S2193Fully implemented
TrustInSoft Analyzer

1.38

non-terminatingExhaustively detects non-terminating statements (see one compliant and one non-compliant example).

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Key here (explains table format and definitions)

Taxonomy

Taxonomy item

Relationship

CERT CFLP30-CPP. Do not use floating-point variables as loop countersPrior to 2018-01-12: CERT: Unspecified Relationship
CERT Oracle Secure Coding Standard for JavaNUM09-J. Do not use floating-point variables as loop countersPrior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TR 24772:2013Floating-Point Arithmetic [PLF]Prior to 2018-01-12: CERT: Unspecified Relationship
MISRA C:2012Directive 1.1 (required)Prior to 2018-01-12: CERT: Unspecified Relationship
MISRA C:2012Rule 14.1 (required)Prior to 2018-01-12: CERT: Unspecified Relationship

Bibliography



9 Comments

  1. is this line in the second CCE a violation of FLP33-C. Convert integers to floating point for floating point operations?

    double x = 100000000.0 + count;
    
    1. I don't think so. As long as one of the arguments is a floating point number, the calculation should be performed as a floating point operation.

  2. As I commented elsewhere, I see no real problem with either noncompliant example since they both use the less-than or equal operator (i.e., the loops are guaranteed to terminate). An example that, IMO, demonstrates the problem better is one that uses [in]equality since, unlike integer arithmetic, floating point arithmetic is inexact:

    for (float x = 0.1f; x != 1.0f; x += 0.1f) {   /* loop may not terminate */
      /* ... */
    }
    
    1. I believe the rationale here is that a programmer might expect the loop to execute 10 times but it executes only 9 times. At least in Java and VC++ 2005. (smile)

      1. I see – very tricky! That hadn't even occurred to me despite the fact that it's mentioned below the noncompliant example. I should read these guidelines before the CSCG ones!

        Maybe the example could be enhanced to make this more obvious. How's this:

        int main ()
        {
            int i = 6;
            float x = 0.6;
        
            for (; x <= 1.0f; x += 0.1f, i += 1);
        
            assert(x > 1.0f);
            assert(i > 10);   /* may fail */
        }
        
  3. This page doesn't really make it clear that cumulative rounding error is an issue that cannot be addressed by the usual advice for avoiding an exact comparison of floating point numbers.

    Readers might think that the first noncompliant example could be 'fixed' by changing the termination condition to 'x < 10.05' (as I did at first.) Here is a proposed replacement which makes it clear that this 'fix' would not be generally applicable:

    void func(void) {
      for (float x = 0.1f; x < 1000.05f; x += 0.1f) {
        /* Loop may not iterate 10,000 times */
      }
    }

    When compiled on gcc for x86 this will iterate 10,001 times.

     

    1. Added a reference to [Goldberg 1991] to provide background material for why fps should not be loop counters. (We also have recommendations addressing this like FLP00-C. Understand the limitations of floating-point numbers).

  4. Also, the solution for the second noncompliant example seems problematic. While it addresses the loop termination issue, we are still left with a value of x that may or may not be incrementing depending on the implementation, and if it is incrementing, will certainly be subject to cumulative rounding errors.

    Here's an improved solution for the second noncompliant example:

    void func(void) {
        for (size_t count = 1; count <= 10; ++count) {
            float x = 100000000.0f + 1.0f * count;
            /* Loop iterates exactly 10 times */
        }
    }
    1. Fixed the 2nd compliant solution as you suggest, thanks!