Table of Contents

Discrete integer intervals

DiscreteInterval<T> is the discrete counterpart to Interval<T>. Where Interval<T> models a continuum of coordinates, DiscreteInterval<T> (constrained to IBinaryInteger<T> - int, long, BigInteger, …) models the set of representable integers between its bounds. That difference changes two behaviours that matter for integer ranges such as indices, pages, or IDs.

Emptiness reflects representable membership

A continuous open interval over two consecutive integers is non-empty - it holds all the reals between them - but it contains no integer. The discrete type reports that correctly:

Interval<int>.Open(1, 2).IsEmpty;           // False - continuous range (1, 2)
DiscreteInterval<int>.Open(1, 2).IsEmpty;   // True  - no integer strictly between 1 and 2

Every shape is canonicalized to inclusive [First, Last] integer bounds at construction (an open bound shifts inward by one), so equal integer sets share one representation and the default value is the empty set:

DiscreteInterval<int>.Open(1, 5) == DiscreteInterval<int>.Closed(2, 4);   // True
DiscreteInterval<int>.Closed(1, 10).Count;                                // 10

Adjacency is by successor

Two integer intervals with no integer between them are adjacent and union to a single run, even though their endpoints are not equal:

var a = DiscreteInterval<int>.Closed(1, 2);
var b = DiscreteInterval<int>.Closed(3, 4);

a.TryUnion(b, out var run);   // run = [1, 4], result = true - 2 and 3 are successors

DiscreteInterval<int>.Closed(1, 2)
    .TryUnion(DiscreteInterval<int>.Closed(4, 5), out _);   // false - 3 is missing

The surface

DiscreteInterval<T> offers First / Last / IsBounded / IsEmpty / Count (which throws InvalidOperationException for an unbounded interval, and OverflowException when the member count does not fit in T - a full-domain interval has one more member than the type can represent), Contains, Overlaps, Intersect, TryUnion, Difference, and SymmetricDifference (the last two returning a DiscreteIntervalPair<T> of up to two runs); the Closed / Open / ClosedOpen / OpenClosed / Singleton / Empty factories and the unbounded All / AtLeast / GreaterThan / AtMost / LessThan family (with type-inferring DiscreteInterval.* helpers); equality; and ToInterval() / FromInterval(...) conversions to and from the continuous type. Formatting mirrors Interval<T>: the type implements IFormattable, ISpanFormattable, and IUtf8SpanFormattable, delegating to the equivalent continuous interval's canonical closed form.

DiscreteInterval<int>.Closed(0, 10).Difference(DiscreteInterval<int>.Closed(3, 5));   // [0, 2] ∪ [6, 10]

For an arbitrary union of ranges over the continuous domain, see IntervalSet<T>.

See also