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.
Tool | Version | Checker | Description |
---|---|---|---|
Parasoft Jtest | 2024.1 | CERT.NUM09.FPLI | Do not use floating point variables as loop indices |
PVS-Studio | 7.33 | V6108 |
Related Guidelines
FLP30-C. Do not use floating-point variables as loop counters | |
Floating-point Arithmetic [PLF] |
Bibliography
[Aho 1986] | |
Puzzle 34, "Down for the Count" | |
[JLS 2015] | |
[Seacord 2015] |
15 Comments
Julian Ospald
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:
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.
David Svoboda
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.
Julian Ospald
I can come up with less pathological examples, such as:
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.
David Svoboda
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:
This covers all the common cases, while excluding pathological cases such as:
What do you think?
Aaron Ballman
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.
David Svoboda
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).
Aaron Ballman
Correct, but I believe the rule's guidance is still correct for induction variables that are not loop counters (for the same reasons).
Julian Ospald
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:
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.
David Svoboda
I've clarified the definition as you suggested.
David Hickerson
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;
}
}
Julian Ospald
Afais that's just a more verbose example of the above ones:
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.
David Hickerson
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.
David Svoboda
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.
David Hickerson
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.
David Svoboda
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).