diff --git a/BizHawk.Common/Ranges.cs b/BizHawk.Common/Ranges.cs index d65526d213..68064fdb53 100644 --- a/BizHawk.Common/Ranges.cs +++ b/BizHawk.Common/Ranges.cs @@ -6,6 +6,7 @@ using BizHawk.Common.NumberExtensions; namespace BizHawk.Common { + /// semantically similar to , but obviously does no checks at runtime public struct RangeStruct where T : unmanaged, IComparable { public T Start; @@ -13,7 +14,7 @@ namespace BizHawk.Common public T EndInclusive; } - /// represents a closed, inclusive range of () + /// represents a closed range of (class invariant: ) public interface Range where T : unmanaged, IComparable { T Start { get; } @@ -21,49 +22,16 @@ namespace BizHawk.Common T EndInclusive { get; } } - public class RangeImpl : Range where T : unmanaged, IComparable + /// represents a closed range of which can be grown or shrunk (class invariant: ) + public class MutableRange : Range where T : unmanaged, IComparable { - protected RangeStruct r; + private RangeStruct r; - /// . < ., or is float/double and either bound is NaN - internal RangeImpl(RangeStruct range) - { - r = ValidatedOrThrow(range); - } - - /// < , or is / and either bound is - public RangeImpl(T start, T endInclusive) : this(new RangeStruct { Start = start, EndInclusive = endInclusive }) {} - - public T Start => r.Start; - - public T EndInclusive => r.EndInclusive; - - internal RangeStruct CopyStruct() => r; - - protected static RangeStruct ValidatedOrThrow(RangeStruct range) where T1 : unmanaged, IComparable - { - if (range is RangeStruct fr && (float.IsNaN(fr.Start) || float.IsNaN(fr.EndInclusive)) - || range is RangeStruct dr && (double.IsNaN(dr.Start) || double.IsNaN(dr.EndInclusive))) - { - throw new ArgumentException("range bound is NaN", nameof(range)); - } - if (range.EndInclusive.CompareTo(range.Start) < 0) throw new ArgumentException("range end < start", nameof(range)); - return range; - } - } - - /// represents a closed, inclusive range of () which can be grown or shrunk - /// inheriting reduces code duplication in - public class MutableRange : RangeImpl where T : unmanaged, IComparable - { - /// - internal MutableRange(RangeStruct range) : base(range) {} - - /// - public MutableRange(T start, T endInclusive) : base(new RangeStruct { Start = start, EndInclusive = endInclusive }) {} + /// + public MutableRange(T start, T endInclusive) => Overwrite(start, endInclusive); /// (from setter) > - public new T Start + public T Start { get => r.Start; set => r.Start = r.EndInclusive.CompareTo(value) < 0 @@ -72,7 +40,7 @@ namespace BizHawk.Common } /// (from setter) < - public new T EndInclusive + public T EndInclusive { get => r.EndInclusive; set => r.EndInclusive = value.CompareTo(r.Start) < 0 @@ -80,15 +48,31 @@ namespace BizHawk.Common : value; } - public void Overwrite(T start, T endInclusive) => r = ValidatedOrThrow(new RangeStruct { Start = start, EndInclusive = endInclusive }); + /// < , or is / and either bound is + public void Overwrite(T start, T endInclusive) + { + var range = new RangeStruct { Start = start, EndInclusive = endInclusive }; + if (range.EndInclusive.CompareTo(range.Start) < 0) throw new ArgumentException("range end < start", nameof(range)); + if (range is RangeStruct fr && (float.IsNaN(fr.Start) || float.IsNaN(fr.EndInclusive)) + || range is RangeStruct dr && (double.IsNaN(dr.Start) || double.IsNaN(dr.EndInclusive))) + { + throw new ArgumentException("range bound is NaN", nameof(range)); + } + r = range; + } } /// contains most of the logic for ranges - /// non-generic overloads are used where the method requires an increment or decrement + /// + /// 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 string EXCL_RANGE_ARITH_EXC_TEXT = "exclusive range end is min value of integral type"; + private const ulong MIN_LONG_NEGATION_AS_ULONG = 9223372036854775808UL; + /// 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 @@ -97,27 +81,33 @@ namespace BizHawk.Common : 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 byte Count(this Range range) => (byte) (range.EndInclusive - range.Start + (byte) 1U); + public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); - public static uint Count(this Range range) => (uint) (1L + range.EndInclusive - range.Start); + /// 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) => throw new NotImplementedException("TODO fancy math"); + /// + 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 byte Count(this Range range) => (byte) (1 + range.EndInclusive - range.Start); + public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); - public static ushort Count(this Range range) => (ushort) (1 + range.EndInclusive - range.Start); + 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 + 1U; + /// + public static ulong Count(this Range range) => range.EndInclusive - range.Start + 1UL; - public static ushort Count(this Range range) => (ushort) (range.EndInclusive - range.Start + (ushort) 1U); + public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1); - /// TODO is this faster or slower than the yield return algorithm used in ? - public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, range.Count()).Select(i => (byte) i); + 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) @@ -157,9 +147,9 @@ namespace BizHawk.Common yield return l; } - public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, range.Count()).Select(i => (sbyte) i); + 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, range.Count()).Select(i => (short) 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) { @@ -175,87 +165,85 @@ namespace BizHawk.Common yield return l; } - public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, range.Count()).Select(i => (ushort) i); + public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (ushort) i); - public static Range GetImmutableCopy(this MutableRange range) where T : unmanaged, IComparable => new MutableRange(range.CopyStruct()); + public static Range GetImmutableCopy(this MutableRange range) where T : unmanaged, IComparable => GetMutableCopy(range); - public static MutableRange GetMutableCopy(this Range range) where T : unmanaged, IComparable => range is RangeImpl impl - ? new MutableRange(impl.CopyStruct()) // copied by value when using the implementations in this file - : new MutableRange(range.Start, range.EndInclusive); + 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 MutableRangeToExcluding(this byte start, byte endExclusive) => endExclusive == byte.MinValue + /// + public static MutableRange MutableRangeToExclusive(this byte start, byte endExclusive) => endExclusive == byte.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, (byte) (endExclusive - 1U)); - /// - public static MutableRange MutableRangeToExcluding(this int start, int endExclusive) => endExclusive == int.MinValue + /// + public static MutableRange MutableRangeToExclusive(this int start, int endExclusive) => endExclusive == int.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, endExclusive - 1); - /// - public static MutableRange MutableRangeToExcluding(this long start, long endExclusive) => endExclusive == long.MinValue + /// + public static MutableRange MutableRangeToExclusive(this long start, long endExclusive) => endExclusive == long.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, endExclusive - 1L); - /// - public static MutableRange MutableRangeToExcluding(this sbyte start, sbyte endExclusive) => endExclusive == sbyte.MinValue + /// + public static MutableRange MutableRangeToExclusive(this sbyte start, sbyte endExclusive) => endExclusive == sbyte.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, (sbyte) (endExclusive - 1)); - /// - public static MutableRange MutableRangeToExcluding(this short start, short endExclusive) => endExclusive == short.MinValue + /// + public static MutableRange MutableRangeToExclusive(this short start, short endExclusive) => endExclusive == short.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, (short) (endExclusive - 1)); - /// - public static MutableRange MutableRangeToExcluding(this uint start, uint endExclusive) => endExclusive == uint.MinValue + /// + public static MutableRange MutableRangeToExclusive(this uint start, uint endExclusive) => endExclusive == uint.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, endExclusive - 1U); - /// - public static MutableRange MutableRangeToExcluding(this ulong start, ulong endExclusive) => endExclusive == ulong.MinValue + /// + public static MutableRange MutableRangeToExclusive(this ulong start, ulong endExclusive) => endExclusive == ulong.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, endExclusive - 1UL); - /// - public static MutableRange MutableRangeToExcluding(this ushort start, ushort endExclusive) => endExclusive == ushort.MinValue + /// + public static MutableRange MutableRangeToExclusive(this ushort start, ushort endExclusive) => endExclusive == ushort.MinValue ? throw new ArgumentException(EXCL_RANGE_ARITH_EXC_TEXT, nameof(endExclusive)) : new MutableRange(start, (ushort) (endExclusive - 1U)); - /// - public static Range RangeTo(this T start, T endInclusive) where T : unmanaged, IComparable => new RangeImpl(start, endInclusive); + /// + public static Range RangeTo(this T start, T endInclusive) where T : unmanaged, IComparable => start.MutableRangeTo(endInclusive); - /// - public static Range RangeToExcluding(this byte start, byte endExclusive) => MutableRangeToExcluding(start, endExclusive); + /// + 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 RangeToExcluding(this int start, int endExclusive) => MutableRangeToExcluding(start, endExclusive); + public static Range RangeToExclusive(this int start, int endExclusive) => MutableRangeToExclusive(start, endExclusive); - /// - public static Range RangeToExcluding(this long start, long endExclusive) => MutableRangeToExcluding(start, endExclusive); + /// + public static Range RangeToExclusive(this long start, long endExclusive) => MutableRangeToExclusive(start, endExclusive); - /// - public static Range RangeToExcluding(this sbyte start, sbyte endExclusive) => MutableRangeToExcluding(start, endExclusive); + /// + public static Range RangeToExclusive(this sbyte start, sbyte endExclusive) => MutableRangeToExclusive(start, endExclusive); - /// - public static Range RangeToExcluding(this short start, short endExclusive) => MutableRangeToExcluding(start, endExclusive); + /// + public static Range RangeToExclusive(this short start, short endExclusive) => MutableRangeToExclusive(start, endExclusive); - /// - public static Range RangeToExcluding(this uint start, uint endExclusive) => MutableRangeToExcluding(start, endExclusive); + /// + public static Range RangeToExclusive(this uint start, uint endExclusive) => MutableRangeToExclusive(start, endExclusive); - /// - public static Range RangeToExcluding(this ulong start, ulong endExclusive) => MutableRangeToExcluding(start, endExclusive); + /// + public static Range RangeToExclusive(this ulong start, ulong endExclusive) => MutableRangeToExclusive(start, endExclusive); - /// - public static Range RangeToExcluding(this ushort start, ushort endExclusive) => MutableRangeToExcluding(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; } -} \ No newline at end of file +}