using System; using System.Collections.Generic; using System.Linq; using BizHawk.Common.NumberExtensions; namespace BizHawk.Common { /// represents a closed range of (class invariant: ) public interface Range where T : unmanaged, IComparable { T Start { get; } T EndInclusive { get; } } /// represents a closed range of which can be grown or shrunk (class invariant: ) public class MutableRange : Range where T : unmanaged, IComparable { private (T Start, T EndInclusive) r; /// public MutableRange(T start, T endInclusive) => Overwrite(start, endInclusive); /// (from setter) > public T Start { get => r.Start; set => r.Start = r.EndInclusive.CompareTo(value) < 0 ? throw new ArgumentOutOfRangeException(nameof(value), value, "attempted to set start > end") : value; } /// (from setter) < public T EndInclusive { get => r.EndInclusive; set => r.EndInclusive = value.CompareTo(r.Start) < 0 ? throw new ArgumentOutOfRangeException(nameof(value), value, "attempted to set end < start") : value; } /// is / and either bound is /// < public void Overwrite(T start, T endInclusive) { if (endInclusive.CompareTo(start) < 0) throw new ArgumentOutOfRangeException(nameof(endInclusive), endInclusive, "range end < start"); if (start is float fs) { if (float.IsNaN(fs)) throw new ArgumentException("range start is NaN", nameof(start)); if (endInclusive is float fe && float.IsNaN(fe)) throw new ArgumentException("range end is NaN", nameof(endInclusive)); } else if (start is double ds) { if (double.IsNaN(ds)) throw new ArgumentException("range start is NaN", nameof(start)); if (endInclusive is double de && double.IsNaN(de)) throw new ArgumentException("range end is NaN", nameof(endInclusive)); } r = (start, endInclusive); } } /// contains most of the logic for ranges /// /// non-generic overloads are used where the method requires an increment or decrement
/// TODO which Enumerate algorithm is faster - yield return in loop or ? ///
public static class RangeExtensions { private const ulong MIN_LONG_NEGATION_AS_ULONG = 9223372036854775808UL; private static readonly ArithmeticException ExclusiveRangeMinValExc = new ArithmeticException("exclusive range end is min value of integral type"); /// if it's contained in , or else whichever bound of is closest to public static T ConstrainWithin(this T value, Range range) where T : unmanaged, IComparable => value.CompareTo(range.Start) < 0 ? range.Start : range.EndInclusive.CompareTo(value) < 0 ? range.EndInclusive : value; /// true iff is contained in ( is considered to be in the range if it's exactly equal to either bound) /// public static bool Contains(this Range range, T value) where T : unmanaged, IComparable => !(value.CompareTo(range.Start) < 0 || range.EndInclusive.CompareTo(value) < 0); public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); /// beware integer overflow when contains every value public static uint Count(this Range range) => (uint) ((long) range.EndInclusive - range.Start) + 1U; /// public static ulong Count(this Range range) => (range.Contains(0L) ? (range.Start == long.MinValue ? MIN_LONG_NEGATION_AS_ULONG : (ulong) -range.Start) + (ulong) range.EndInclusive : (ulong) (range.EndInclusive - range.Start) ) + 1UL; public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); /// public static uint Count(this Range range) => range.EndInclusive - range.Start + 1U; /// public static ulong Count(this Range range) => range.EndInclusive - range.Start + 1UL; public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); public static void Deconstruct(this Range range, out T start, out T endInclusive) where T : unmanaged, IComparable { start = range.Start; endInclusive = range.EndInclusive; } public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (byte) i); /// public static IEnumerable Enumerate(this Range range, double step) { var d = range.Start; while (d < range.EndInclusive) { yield return d; d += step; } if (d.HawkFloatEquality(range.EndInclusive)) yield return d; } /// beware precision errors public static IEnumerable Enumerate(this Range range, float step) { var f = range.Start; while (f < range.EndInclusive) { yield return f; f += step; } if (f.HawkFloatEquality(range.EndInclusive)) yield return f; } public static IEnumerable Enumerate(this Range range) { var i = range.Start; while (i < range.EndInclusive) yield return i++; yield return i; } public static IEnumerable Enumerate(this Range range) { var l = range.Start; while (l < range.EndInclusive) yield return l++; yield return l; } public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (sbyte) i); public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (short) i); public static IEnumerable Enumerate(this Range range) { var i = range.Start; while (i < range.EndInclusive) yield return i++; yield return i; } public static IEnumerable Enumerate(this Range range) { var l = range.Start; while (l < range.EndInclusive) yield return l++; yield return l; } public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (ushort) i); public static Range GetImmutableCopy(this Range range) where T : unmanaged, IComparable => GetMutableCopy(range); public static MutableRange GetMutableCopy(this Range range) where T : unmanaged, IComparable => new MutableRange(range.Start, range.EndInclusive); /// public static MutableRange MutableRangeTo(this T start, T endInclusive) where T : unmanaged, IComparable => new MutableRange(start, endInclusive); /// public static MutableRange MutableRangeToExclusive(this byte start, byte endExclusive) => endExclusive == byte.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, (byte) (endExclusive - 1U)); /// public static MutableRange MutableRangeToExclusive(this int start, int endExclusive) => endExclusive == int.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, endExclusive - 1); /// public static MutableRange MutableRangeToExclusive(this long start, long endExclusive) => endExclusive == long.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, endExclusive - 1L); /// public static MutableRange MutableRangeToExclusive(this sbyte start, sbyte endExclusive) => endExclusive == sbyte.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, (sbyte) (endExclusive - 1)); /// public static MutableRange MutableRangeToExclusive(this short start, short endExclusive) => endExclusive == short.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, (short) (endExclusive - 1)); /// public static MutableRange MutableRangeToExclusive(this uint start, uint endExclusive) => endExclusive == uint.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, endExclusive - 1U); /// public static MutableRange MutableRangeToExclusive(this ulong start, ulong endExclusive) => endExclusive == ulong.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, endExclusive - 1UL); /// public static MutableRange MutableRangeToExclusive(this ushort start, ushort endExclusive) => endExclusive == ushort.MinValue ? throw ExclusiveRangeMinValExc : new MutableRange(start, (ushort) (endExclusive - 1U)); /// public static Range RangeTo(this T start, T endInclusive) where T : unmanaged, IComparable => start.MutableRangeTo(endInclusive); /// public static Range RangeToExclusive(this byte start, byte endExclusive) => MutableRangeToExclusive(start, endExclusive); /// (empty ranges where = are not permitted) /// is min value of integral type (therefore ) public static Range RangeToExclusive(this int start, int endExclusive) => MutableRangeToExclusive(start, endExclusive); /// public static Range RangeToExclusive(this long start, long endExclusive) => MutableRangeToExclusive(start, endExclusive); /// public static Range RangeToExclusive(this sbyte start, sbyte endExclusive) => MutableRangeToExclusive(start, endExclusive); /// public static Range RangeToExclusive(this short start, short endExclusive) => MutableRangeToExclusive(start, endExclusive); /// public static Range RangeToExclusive(this uint start, uint endExclusive) => MutableRangeToExclusive(start, endExclusive); /// public static Range RangeToExclusive(this ulong start, ulong endExclusive) => MutableRangeToExclusive(start, endExclusive); /// public static Range RangeToExclusive(this ushort start, ushort endExclusive) => MutableRangeToExclusive(start, endExclusive); /// true iff is strictly contained in ( is considered to be OUTSIDE the range if it's exactly equal to either bound) /// public static bool StrictlyBoundedBy(this T value, Range range) where T : unmanaged, IComparable => range.Start.CompareTo(value) < 0 && value.CompareTo(range.EndInclusive) < 0; } }