Modulus Result Compared With A Non-Zero Value
ID |
swift.no_modulus_direct_equality |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Swift |
Tags |
arithmetic, reliability, suspicious-comparison |
Description
Reports an equality comparison between a % expression and a non-zero integer literal, such as
value % 2 == 1. Both operand orders are covered, and so is a negated literal (== -1).
Comparisons against 0 are not reported.
Rationale
func isOdd(_ value: Int) -> Bool {
return value % 2 == 1
}
isOdd(3) // true
isOdd(-3) // false — and -3 is odd
In Swift the remainder operator keeps the sign of the dividend, so -3 % 2 is -1, not 1.
The odd-number test above therefore answers "even" for every negative odd value. The defect
survives testing easily: positive inputs behave exactly as intended, and only a negative one — a
delta, an offset, a coordinate, a value read from user input — exposes it.
A negated literal has the mirror-image blind spot: value % 2 == -1 is false for every positive
odd value.
A dividend that cannot be negative is exempt, because for it the sign rule never applies: a call
to abs, a conversion to an unsigned integer type (UInt(value) % 2 == 1), a size-like member
read (count, underestimatedCount, length, magnitude), a variable or parameter whose
declared type is unsigned, the offset a closure is handed by enumerated()
(items.enumerated().filter { idx, _ in idx % 2 == 1 }, where the offset counts from zero
upwards), and a dividend that already takes a remainder of its own — the shape of a normalised
remainder. Any other dividend is still reported, so no finding is lost when a type cannot be
resolved.
Remediation
Compare the remainder with zero, which no sign can change, or normalise the remainder into the non-negative range before comparing it.
// Compare against zero.
func isOdd(_ value: Int) -> Bool {
return value % 2 != 0
}
// Or normalise the remainder first, when the actual value matters.
func bucket(_ value: Int, _ buckets: Int) -> Int {
return ((value % buckets) + buckets) % buckets
}
For the parity case specifically, the standard library already answers it without arithmetic:
if value.isMultiple(of: 2) {
// even, for negative values too
}