Irregular Number Pattern

ID

csharp.irregular_number_pattern

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

CSharp

Tags

code_smell, literals, readability

Description

Reports a numeric literal whose digit separators 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 have 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 myriad grouping (1_0000_0000) are accepted alongside the conventional groups of three. A literal with no separator is never reported, and for a real literal only the integral part is grouped — the digits after the point group from the left, so the same test does not apply to them.

Rationale

The digit separator exists so a reader can take in the magnitude of a long literal at a glance, the way a thousands separator does in print. That works because the eye reads the group width once and then counts groups. Irregular groups break the mechanism and actively mislead: 1_000_00_000 looks thousand-grouped and is off by a factor of ten from a quick reading, while 100_0 reads as a hundred with a stray digit after it.

A literal grouped this way is usually the residue of an edit — a digit added or removed without moving the separators. That is precisely the change formatting should make visible to the next reviewer instead of concealing.

public class Limits
{
    public const int Wrong = 100_0;              // FLAW - reads as 100, is 1000
    public const int Shifted = 1_000_00_000;      // FLAW - groups of 3, 2 and 3
    public const long Mixed = 1_0000_000L;        // FLAW - a group of 4 then a group of 3

    public const int Thousands = 10_000;          // OK - leading group holds the remainder
    public const int Pairs = 1_00_00;             // OK - consistent pair grouping
    public const int Millions = 1_000_000;        // OK
    public const int Plain = 1000000;             // OK - no separators to judge
    public const int Mask = 0xFF_FF_FF;           // OK - consistent byte grouping
}

Remediation

Re-group the digits so that every group after the first has the same width, then check the value itself: an irregular literal often means a digit was gained or lost during an edit, and moving the separators would silently bless the wrong number. Pick the grouping that matches the domain — threes for a decimal quantity, pairs or fours for a bit mask — and use it consistently across the literals it will be compared against.