Skip to content

Commit

Permalink
Make Int64Interval half-open (end is exclusive, like BCL Range)
Browse files Browse the repository at this point in the history
  • Loading branch information
YoshiRulz committed Jul 10, 2024
1 parent 2d5824a commit acf0ec5
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 18 deletions.
35 changes: 20 additions & 15 deletions src/BizHawk.Common/Ranges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,40 +32,42 @@ public uint Count()
}

/// <summary>
/// represents a closed interval (a.k.a. range) of <see cref="long"><c>s64</c>s</see>
/// (class invariant: <see cref="Start"/> <see cref="EndInclusive"/>)
/// represents a half-open interval (a.k.a. range) of <see cref="long"><c>s64</c>s</see>
/// (class invariant: <see cref="Start"/> &lt; <see cref="EndExclusive"/>)
/// </summary>
public sealed class Int64Interval
{
private const ulong MIN_LONG_NEGATION_AS_ULONG = 9223372036854775808UL;

public readonly long Start;

public readonly long EndInclusive;
public readonly long EndExclusive;

/// <inheritdoc cref="Int32Interval(int,int)"/>
internal Int64Interval(long start, long endInclusive)
/// <exception cref="ArgumentOutOfRangeException"><paramref name="endExclusive"/> ≤ <paramref name="start"/></exception>
internal Int64Interval(long start, long endExclusive)
{
if (endInclusive < start) throw new ArgumentOutOfRangeException(paramName: nameof(endInclusive), actualValue: endInclusive, message: "interval end < start");
if (endExclusive <= start) throw new ArgumentOutOfRangeException(paramName: nameof(endExclusive), actualValue: endExclusive, message: "interval end <= start");
Start = start;
EndInclusive = endInclusive;
EndExclusive = endExclusive;
}

/// <inheritdoc cref="Int32Interval.Contains"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(long value)
=> Start <= value && value <= EndInclusive;
=> Start <= value && value < EndExclusive;

/// <inheritdoc cref="Int32Interval.Count"/>
public ulong Count()
=> (Contains(0L)
? (Start == long.MinValue ? MIN_LONG_NEGATION_AS_ULONG : (ulong) -Start) + (ulong) EndInclusive
: (ulong) (EndInclusive - Start)
) + 1UL;
=> Contains(0L)
? (Start == long.MinValue ? MIN_LONG_NEGATION_AS_ULONG : (ulong) -Start) + (ulong) EndExclusive
: (ulong) (EndExclusive - Start);
}

public static class RangeExtensions
{
private static ArithmeticException ExclusiveRangeMaxValExc
=> new("inclusive range end is max. value of integral type");

private static ArithmeticException ExclusiveRangeMinValExc
=> new("exclusive range end is min value of integral type");

Expand All @@ -81,10 +83,13 @@ public static int ConstrainWithin(this int value, Int32Interval range)
public static Int32Interval RangeTo(this int start, int endInclusive)
=> new(start: start, endInclusive: endInclusive);

/// <inheritdoc cref="Int64Interval(long,long)"/>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="endInclusive"/> &lt; <paramref name="start"/></exception>
/// <exception cref="ArithmeticException"><paramref name="endInclusive"/> is max. value of integral type (therefore <c>endExclusive</c> is unrepresentable)</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Int64Interval RangeTo(this long start, long endInclusive)
=> new(start: start, endInclusive: endInclusive);
=> endInclusive == long.MaxValue
? throw ExclusiveRangeMaxValExc
: new(start: start, endExclusive: endInclusive + 1L);

/// <exception cref="ArgumentOutOfRangeException"><paramref name="endExclusive"/> ≤ <paramref name="start"/> (empty ranges where <paramref name="start"/> = <paramref name="endExclusive"/> are not permitted)</exception>
/// <exception cref="ArithmeticException"><paramref name="endExclusive"/> is min value of integral type (therefore <paramref name="endExclusive"/> ≤ <paramref name="start"/>)</exception>
Expand All @@ -99,7 +104,7 @@ public static Int32Interval RangeToExclusive(this int start, int endExclusive)
public static Int64Interval RangeToExclusive(this long start, long endExclusive)
=> endExclusive == long.MinValue
? throw ExclusiveRangeMinValExc
: new(start: start, endInclusive: endExclusive - 1L);
: new(start: start, endExclusive: endExclusive);

/// <returns>true iff <paramref name="value"/> is strictly contained in <paramref name="range"/> (<paramref name="value"/> is considered to be OUTSIDE the range if it's exactly equal to either bound)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public virtual void BulkPeekByte(Int64Interval addresses, byte[] values)

using (this.EnterExit())
{
for (var i = addresses.Start; i <= addresses.EndInclusive; i++)
for (var i = addresses.Start; i < addresses.EndExclusive; i++)
{
values[i - addresses.Start] = PeekByte(i);
}
Expand All @@ -127,7 +127,7 @@ public virtual void BulkPeekUshort(Int64Interval addresses, bool bigEndian, ush
if (values is null) throw new ArgumentNullException(paramName: nameof(values));

var start = addresses.Start;
var end = addresses.EndInclusive + 1;
var end = addresses.EndExclusive;

if ((start & 1) != 0 || (end & 1) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");
Expand All @@ -151,7 +151,7 @@ public virtual void BulkPeekUint(Int64Interval addresses, bool bigEndian, uint[]
if (values is null) throw new ArgumentNullException(paramName: nameof(values));

var start = addresses.Start;
var end = addresses.EndInclusive + 1;
var end = addresses.EndExclusive;

if ((start & 3) != 0 || (end & 3) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");
Expand Down

0 comments on commit acf0ec5

Please sign in to comment.