Floating-point variables must not be used as loop counters. Limited-precision IEEE 754 floating-point types cannot represent

  • All simple fractions exactly.
  • All decimals precisely, even when the decimals can be represented in a small number of digits.
  • All digits of large values, meaning that incrementing a large floating-point value might not change that value within the available precision.

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

This rule is a subset of NUM04-J. Do not use floating-point numbers if precise computation is required.

Noncompliant Code Example

This noncompliant code example uses a floating-point variable as a loop counter. The decimal number 0.1 cannot be precisely represented as a float or even as a double.

for (float x = 0.1f; x <= 1.0f; x += 0.1f) {
  System.out.println(x);
}

Because 0.1f is rounded to the nearest value that can be represented in the value set of the float type, the actual quantity added to x on each iteration is somewhat larger than 0.1. Consequently, the loop executes only nine times and typically fails to produce the expected output.

Compliant Solution

This compliant solution uses an integer loop counter from which the desired floating-point value is derived:

for (int count = 1; count <= 10; count += 1) {
  float x = count/10.0f;
  System.out.println(x);
}

Noncompliant Code Example

This noncompliant code example uses a floating-point loop counter that is incremented by an amount that is typically too small to change its value given the precision:

for (float x = 100000001.0f; x <= 100000010.0f; x += 1.0f) {
  /* ... */
}

The code loops forever on execution.

Compliant Solution

This compliant solution uses an integer loop counter from which the floating-point value is derived. Additionally, it uses a double to ensure that the available precision suffices to represent the desired values. The solution also runs in strict floating-point (FP-strict) mode to guarantee portability of its results (see NUM53-J. Use the strictfp modifier for floating-point calculation consistency across platforms for more information).

for (int count = 1; count <= 10; count += 1) {
  double x = 100000000.0 + count;
  /* ... */
}

Risk Assessment

Using floating-point loop counters can lead to unexpected behavior.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

NUM09-J

Low

Probable

Low

P6

L2

Automated Detection

Automated detection of floating-point loop counters is straightforward.

ToolVersionCheckerDescription
Parasoft Jtest
2023.1
CERT.NUM09.FPLIDo not use floating point variables as loop indices
PVS-Studio

7.30

V6108

Related Guidelines

Bibliography



15 Comments

  1. It says automated detection is straight-forward. I'd argue that's rather optimistic, except for the examples in the rule.

    Imagine a loop like:

    for (; indirectFunction(); ) {
    }

    Now it's close to impossible to say whether this function, which doesn't even take parameters and potentially operates on object state, at some point uses floating-point variables as counter/condition.

    I also don't see a rule the disallows such indirection. Ofc this example is an exaggaration, but on such indirection the presented problem can still exist, so I think it's relevant.

    1. Sigh. Like most rules, this one is straightforward in most cases. Except for pathalogical examples that we never see in actual production code. Such as your example.

      In fact, I would argue that your example cannot violate this rule, even if you argue that indirectFunction() 's return value (which must be a boolean) depends on the increment of a float. At best, you could claim that indirectFunction() itself violates NUM04-J. Do not use floating-point numbers if precise computation is required.

      1. I can come up with less pathological examples, such as:

        1. the floating point being incremented/decremented in the body (possibly with indirection as well), not the for-loop
        2. the loop condition not comparing against the floating point, but against an int depending on the floating point, which may be updated in the body or directly in the for-loop
        3. the for-loop updater incrementing a floating point, which is not actually used in the condition... or in a safe way

        IMO, there are enough constructs that require more knowledge about what the for loop as a whole does, which is possible, but not straight-forward. To me, the term "loop counter" isn't even well enough defined.

        1. The problem is that this rule is not rigorously defined. Mainly because, as you mentioned, "loop counter" is not well-defined. I haven't found a good general definition of it online.

          Also this rule is a subset of NUM04-J...all violations of this rule also violate that one.

          Fortunately, this means we can craft our own rigorous definition of 'loop counter', strictly for the purpose of codifying this rule, and any pathological cases we miss are still covered by NUM04-J. So let me suggest the following:

          Do not use floats or doubles as loop counters, where a loop counter is defined as:

          • a variable that is used in the comparison operation of a do, while, or for loop
          • incremented or decremented by a compile-time constant exactly once per iteration of the loop

          This covers all the common cases, while excluding pathological cases such as:

          • non-comparisons in loops (such as your example)
          • loops with non-conventional exits (such as a break within an if statement)
          • unconventional loops made with goto or recursive functions.

          What do you think?

          1. FWIW, I've always heard it called the loop induction variable (and indeed, that's the phrasing we use in FLP30-C. Do not use floating-point variables as loop counters as well). I'm not alone in my use of this phrase, Wikipedia agrees with me: https://en.wikipedia.org/wiki/Induction_variable.

            1. It sounds like every loop counter is an induction variable, but the converse is not true (as the example at the beginning of that article indicates).

              1. Correct, but I believe the rule's guidance is still correct for induction variables that are not loop counters (for the same reasons).

          2. Yeah, I think we can simplify the phrasing a bit by using the proposed induction variable term. I'd also like to make it more explicit that the use of said variable must be "direct" (is there a better term?), so we are not required to look for variables that depend on other floating-point variables. Those cases are, as you said, covered by the less straight-forward NUM04-J. Maybe something like...

            A loop counter is defined as:

            • an induction variable that is directly used in the comparison of a do, while or for loop

            Of course this will still require us to check the body of the loop for the incrementation/decrementation, which unfortunately may have further indirection. But it's definitely an improvement.

            1. I've clarified the definition as you suggested.

  2. When I read this rule, I thought that there may be instances that you would need to do this, such as root finding or optimization functions. Then, it occurred to me that this rule applies and to use the counter to limit the number cycles in algorithm, and then to use a break when the function tolerance becomes within limits. I would recommend a code example like this for those coders that would use floating-point in their loop to see a compliant alternative.

    int maxLoopCount = 1000;

    double tolerance = 0.0001;

    double delta = 1.0;

    double input = initvalue;

    for(int ii = 0; ii < maxLoopCount; ii++){

        delta = someIterativeNumericFunction(input);

        // modify input

         if(delta < tolerance){

            break;

        }

    }

     

    1. Afais that's just a more verbose example of the above ones:

      1. have int as loop counter
      2. do something with a float in the loop body without the float influencing the loop counter (otherwise NUM04-J comes into play)

      I'd rather think about making the "maximum loop iteration thing" its own rule, unless there already is one and I missed it. MSC21-C looks a bit similar to that and I think the overall topic here is "reliable loop termination", of which a maximum loop iteration condition is part of.

      Other than that, I prefer simple examples.

  3. The point I was trying to make, however you want to capture it is this. If I am programming an indeterminate algorithm, where I am driving a variable below a tolerance, this may occur in 2 cycles or 2000 cycles depending the parameters. This is common to numerical methods. The average coder will do something like this:

    double toler = 0.00001;   

    double delta = 10.0; // some large value above tolerance to init delta, which is the change in answer value

    do

    {

        // calculate delta

    } while (delta > toler);

     

    This non-compliant code example is a mostly likely case that would found, and I would think providing appropriate guidance to fix problem would be nice. I like your comment about making its own rule, and to keep it simple.

    1. If your delta is incrementing (or decrementing) by the same value on each iteration of the loop, this rule explains that you should be using integers to do that math. (I suspect this is one of the arguments in support of fixed-point arithmetic.)

      FTM, if the delta is the same value, you should also compute it outside the loop for performance.

  4. No delta is never incremented or decremented by a fix value. For example go look at this numerical method: https://en.wikipedia.org/wiki/Newton%27s_method , and this will make more sense. Delta would be f(x)/f'(x) in this example.  This type of code is where you will find this kind of non-compliant looping, which is numerical analysis, a type iterative math solution problem.

    1. An induction variable is a variable that gets increased or decreased by a fixed amount on every iteration of a loop [Aho 1986

      If delta changes at every iteration of the loop, as it does with Newton's method, then, by the above quote, this rule does not apply. In that case, using fp values to determine loop bounds is perfectly fine (as integers are insufficient).