Irregular Number Pattern
ID |
java.irregular_number_pattern |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
Java |
Tags |
code_smell, literals, readability |
Description
Reports a numeric literal whose digit-separator underscores split the digits into groups of
inconsistent size — 100_0, 100_00, 1_000_00_000.
Uniformity is judged on the digit groups only: every group after the first must be the same
width, and the leading group may be shorter because it holds the remainder. Any consistent
grouping passes, so pair grouping (1_00_00) and hex nibble grouping (0x7fff_ffff) are accepted
alongside the conventional groups of three. A literal with no separator is never reported, and for
a floating-point literal only the integral part is graded — the fractional digits group
differently and are left alone.
Binary literals (0b/0B) are exempt outright: their bits are commonly grouped to match a field
layout rather than a magnitude, so an irregular-looking grouping there is often deliberate.
Test code is out of scope. A test that writes a duration as 3600_000L, or pins a schema version
as 2026_01_000, is not a literal anyone reads for its magnitude. Decided by the user-configured
applicativeCode.excludedPathPatterns plus the src/it/java convention.
Rationale
The underscore separator exists so a reader can take in a long literal’s magnitude at a glance,
the way a thousands separator does in print. That only works while the grouping is regular,
because the eye reads the group width once and then counts groups. 1_000_00_000 reads as
thousand-grouped and is a factor of ten off from what a quick glance suggests, while 100_0 reads
as a hundred with a stray trailing digit.
A literal grouped this way is usually the residue of an edit — a digit added or removed without moving the separators. That is exactly the change the formatting should make visible to the next reviewer, not hide from them.
long shipmentTotal = 10_000_00_000L; // Bad -- groups of 2, 3, 2, 3
long retryBudget = 20000_000_000L; // Bad -- leading group wider than the rest
long badOffset = 0x1234_567_89ab_cdefL; // Bad -- groups of 4, 3, 4, 4
long shipmentTotal = 10_000_000_000L; // Good -- uniform groups of three
long badOffset = 0x1234_5678_9abcL; // Good -- uniform hex groups
int statusBits = 0b101_11001_0001; // Good -- binary literals are exempt
Remediation
Re-group the digits so every group after the first is the same width, then check the value itself: an irregular literal often means a digit was gained or lost during an edit, and simply moving the separators would bless the wrong number without noticing it. Pick the grouping that matches the domain — threes for a decimal quantity, nibbles or bytes for a hex mask — and apply it consistently to every literal it will be compared against.