Exact equality comparison against a floating-point literal
ID |
objectivec.correctness.float_equality |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
low |
Resource |
Numeric |
Language |
Objective-C |
Description
Reports == or != where one operand is a floating-point literal written with a decimal point, such as 1.5, .5, 2., 0.0, 1.5f or 1.5e3, in either operand order. The literal is what identifies the comparison as floating point: without types, an operand named only by an identifier cannot be told apart from an integer, so a == b is never reported even when both are doubles. A literal written without a decimal point, such as 1e5, is not recognised either. Ordered comparisons (<, >=) are correct for floating point and are not reported.
Rationale
Floating-point values hold approximations, and most decimal literals — 0.1 among them — have no exact binary representation, so a value that should equal 1.5 after a few operations differs from it in the last bits. == then answers false on inputs that are equal for every purpose the program cares about. The failure is intermittent and input-dependent, which is what makes it expensive: the comparison works in the unit test that uses whole numbers and fails on the production value that came out of a division, and the branch it guards is silently skipped rather than raising anything.
The following code illustrates the pattern detected by this rule:
@implementation Measure
- (BOOL)matchesExactly:(double)d {
// FLAGGED: Exact equality comparison against a floating-point literal
if (d == 1.5) {
return YES;
}
return NO;
}
@end
Remediation
Compare within a tolerance instead of exactly — fabs(d - 1.5) < DBL_EPSILON for values near one, or a tolerance scaled to the magnitudes involved for larger ones. Where the value is money or another quantity that must be exact, keep it in integer units (cents) or in NSDecimalNumber and compare those, rather than tightening the tolerance.