Use generic math in Util.IntUtil

This commit is contained in:
Alex Barney 2022-12-10 11:12:43 -07:00
parent d47e0ebedc
commit b7625af148
7 changed files with 398 additions and 51 deletions

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
</packageSources>
</configuration>

View File

@ -39,14 +39,14 @@ public class DeviceOperator : IDeviceOperator
private static Span<byte> GetSpan(OutBuffer buffer, long size)
{
Assert.True(IntUtil.IsIntValueRepresentableAsInt(size));
Assert.True(IntUtil.IsIntValueRepresentable<int, long>(size));
return buffer.Buffer.Slice(0, (int)size);
}
private static ReadOnlySpan<byte> GetSpan(InBuffer buffer, long size)
{
Assert.True(IntUtil.IsIntValueRepresentableAsInt(size));
Assert.True(IntUtil.IsIntValueRepresentable<int, long>(size));
return buffer.Buffer.Slice(0, (int)size);
}

View File

@ -386,7 +386,7 @@ public readonly struct Result : IEquatable<Result>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong SetBitsValueLong(int value, int bitsOffset, int bitsCount)
{
return ((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset;
return unchecked(((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset);
}
/// <summary>

View File

@ -1,42 +1,189 @@
namespace LibHac.Util;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace LibHac.Util;
public static class IntUtil
{
// Todo: Use generic math once C# 11 is out
public static bool IsIntValueRepresentableAsLong(ulong value)
{
return value <= long.MaxValue;
}
private static bool IsSignedType<T>() where T : INumber<T> => unchecked(T.IsNegative(T.Zero - T.One));
public static bool IsIntValueRepresentableAsULong(long value)
// Todo .NET 8: Remove NoInlining. .NET 7's JIT doesn't inline everything properly
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsIntValueRepresentable<TTo, TFrom>(TFrom value)
where TTo : IMinMaxValue<TTo>, INumber<TTo>
where TFrom : IMinMaxValue<TFrom>, INumber<TFrom>
{
return value >= 0;
}
public static bool IsIntValueRepresentableAsInt(long value)
{
return value >= int.MinValue && value <= int.MaxValue;
}
public static bool IsIntValueRepresentableAsUInt(long value)
{
return value >= uint.MinValue && value <= uint.MaxValue;
}
public static bool CanAddWithoutOverflow(long x, long y)
{
if (y >= 0)
if (IsSignedType<TFrom>())
{
return x <= long.MaxValue - y;
if (IsSignedType<TTo>())
{
return IsIntValueRepresentableImplSToS<TTo, TFrom>(value);
}
else
{
return IsIntValueRepresentableImplSToU<TTo, TFrom>(value);
}
}
else
{
return x >= unchecked(long.MinValue - y);
if (IsSignedType<TTo>())
{
return IsIntValueRepresentableImplUToS<TTo, TFrom>(value);
}
else
{
return IsIntValueRepresentableImplUToU<TTo, TFrom>(value);
}
}
}
public static bool CanAddWithoutOverflow(ulong x, ulong y)
// Methods for the 4 signed/unsigned TTo/TFrom permutations.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIntValueRepresentableImplSToS<TTo, TFrom>(TFrom value)
where TTo : IMinMaxValue<TTo>, INumber<TTo>
where TFrom : IMinMaxValue<TFrom>, INumber<TFrom>
{
return x <= ulong.MaxValue - y;
if (long.CreateTruncating(TTo.MinValue) <= long.CreateTruncating(TFrom.MinValue) &&
long.CreateTruncating(TFrom.MaxValue) <= long.CreateTruncating(TTo.MaxValue))
{
return true;
}
return long.CreateTruncating(TTo.MinValue) <= long.CreateTruncating(value) &&
long.CreateTruncating(value) <= long.CreateTruncating(TTo.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIntValueRepresentableImplUToU<TTo, TFrom>(TFrom value)
where TTo : IMinMaxValue<TTo>, INumber<TTo>
where TFrom : IMinMaxValue<TFrom>, INumber<TFrom>
{
if (ulong.CreateTruncating(TTo.MinValue) <= ulong.CreateTruncating(TFrom.MinValue) &&
ulong.CreateTruncating(TFrom.MaxValue) <= ulong.CreateTruncating(TTo.MaxValue))
{
return true;
}
return ulong.CreateTruncating(TTo.MinValue) <= ulong.CreateTruncating(value) &&
ulong.CreateTruncating(value) <= ulong.CreateTruncating(TTo.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIntValueRepresentableImplSToU<TTo, TFrom>(TFrom value)
where TTo : IMinMaxValue<TTo>, INumber<TTo>
where TFrom : IMinMaxValue<TFrom>, INumber<TFrom>, IComparisonOperators<TFrom, TFrom, bool>
{
if (value < TFrom.Zero)
{
return false;
}
return IsIntValueRepresentableImplUToU<TTo, TFrom>(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIntValueRepresentableImplUToS<TTo, TFrom>(TFrom value)
where TTo : IMinMaxValue<TTo>, INumber<TTo>
where TFrom : IMinMaxValue<TFrom>, INumber<TFrom>
{
return ulong.CreateTruncating(value) <= ulong.CreateTruncating(TTo.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CanAddWithoutOverflow<T>(T x, T y) where T : IMinMaxValue<T>, INumber<T>
{
if (y >= T.Zero)
{
return x <= T.MaxValue - y;
}
else
{
return x >= T.MinValue - y;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CanSubtractWithoutOverflow<T>(T x, T y) where T : IMinMaxValue<T>, INumber<T>
{
if (y >= T.Zero)
{
return x >= T.MinValue + y;
}
else
{
return x <= T.MaxValue + y;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CanMultiplyWithoutOverflow<T>(T x, T y) where T : IMinMaxValue<T>, INumber<T>
{
if (x == T.Zero || y == T.Zero)
return true;
if (x > T.Zero)
{
if (y > T.Zero)
{
return y <= T.MaxValue / x;
}
else
{
return y >= T.MinValue / x;
}
}
else
{
if (y > T.Zero)
{
return x >= T.MinValue / y;
}
else
{
return y >= T.MaxValue / x;
}
}
}
public static bool TryAddWithoutOverflow<T>(out T value, T x, T y) where T : IMinMaxValue<T>, INumber<T>
{
if (CanAddWithoutOverflow(x, y))
{
value = x + y;
return true;
}
else
{
value = default;
return false;
}
}
public static bool TrySubtractWithoutOverflow<T>(out T value, T x, T y) where T : IMinMaxValue<T>, INumber<T>
{
if (CanSubtractWithoutOverflow(x, y))
{
value = x - y;
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryMultiplyWithoutOverflow<T>(out T value, T x, T y) where T : IMinMaxValue<T>, INumber<T>
{
if (CanMultiplyWithoutOverflow(x, y))
{
value = x * y;
return true;
}
else
{
value = default;
return false;
}
}
}

View File

@ -47,7 +47,7 @@ public class FlatMapKeyValueStoreTests
for (int i = 0; i < count; i++)
{
byte[] value = new byte[startingSize + i];
value.AsSpan().Fill((byte)count);
value.AsSpan().Fill(unchecked((byte)count));
values[i] = value;
}

View File

@ -11,23 +11,26 @@ public struct Random
public Random(ulong seed)
{
ulong x = seed;
ulong z = x + 0x9e3779b97f4a7c15;
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
x = z ^ (z >> 31);
z = (x += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
_state1 = z ^ (z >> 31);
_state2 = x;
unchecked
{
ulong x = seed;
ulong z = x + 0x9e3779b97f4a7c15;
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
x = z ^ (z >> 31);
z = (x += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
_state1 = z ^ (z >> 31);
_state2 = x;
}
}
ulong Next()
{
ulong s0 = _state1;
ulong s1 = _state2;
ulong result = BitOperations.RotateLeft(s0 + s1, 17) + s0;
ulong result = unchecked(BitOperations.RotateLeft(s0 + s1, 17) + s0);
s1 ^= s0;
_state1 = BitOperations.RotateLeft(s0, 49) ^ s1 ^ (s1 << 21);
@ -43,8 +46,11 @@ public struct Random
throw new ArgumentOutOfRangeException(nameof(minValue));
}
long range = (long)maxValue - minValue;
return (int)((uint)Next() * (1.0 / uint.MaxValue) * range) + minValue;
unchecked
{
long range = (long)maxValue - minValue;
return (int)((uint)Next() * (1.0 / uint.MaxValue) * range) + minValue;
}
}
public void NextBytes(Span<byte> buffer)
@ -58,7 +64,7 @@ public struct Random
for (int i = bufferUlong.Length * sizeof(ulong); i < buffer.Length; i++)
{
buffer[i] = (byte)Next();
buffer[i] = unchecked((byte)Next());
}
}
}

View File

@ -0,0 +1,200 @@
using LibHac.Util;
using Xunit;
namespace LibHac.Tests.Util;
public class IntUtilTests
{
[Theory]
[InlineData(0x80, 0x7F, true)]
[InlineData(0x80, 0x80, false)]
public void CanAddWithoutOverflow_Byte(byte x, byte y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(x, y));
Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(y, x));
}
[Theory]
[InlineData(-0x8000, -1, false)]
[InlineData(-0x8000, 0, true)]
[InlineData(0x4000, 0x3FFF, true)]
[InlineData(0x4000, 0x4000, false)]
[InlineData(-0x4000, -0x4000, true)]
public void CanAddWithoutOverflow_Short(short x, short y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(x, y));
Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(y, x));
}
[Theory]
[InlineData(0x80, 0x80, true)]
[InlineData(0x80, 0x81, false)]
public void CanSubtractWithoutOverflow_Byte(byte x, byte y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanSubtractWithoutOverflow(x, y));
}
[Theory]
[InlineData(-0x8000, 1, false)]
[InlineData(-0x8000, 0, true)]
[InlineData(-0x4000, 0x4000, true)]
[InlineData(-0x4000, 0x4001, false)]
[InlineData(0x4000, -0x4000, false)]
public void CanSubtractWithoutOverflow_Short(short x, short y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanSubtractWithoutOverflow(x, y));
}
[Theory]
[InlineData(0xFF, 0, true)]
[InlineData(0x55, 3, true)]
[InlineData(0x40, 4, false)]
public void CanMultiplyWithoutOverflow_Byte(byte x, byte y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(x, y));
Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(y, x));
}
[Theory]
[InlineData(0x7FFF, 0, true)]
[InlineData(+0x1249, +7, true)]
[InlineData(-0x1249, +7, true)]
[InlineData(-0x1249, -7, true)]
[InlineData(+0x1249, -7, true)]
[InlineData(+0x2000, +4, false)]
[InlineData(-0x2000, +4, true)]
[InlineData(-0x2000, -4, false)]
[InlineData(+0x2000, -4, true)]
[InlineData(-0x2001, +4, false)]
[InlineData(+0x2001, -4, false)]
[InlineData(-0x8000, -1, false)]
[InlineData(-0x7FFF, -1, true)]
public void CanMultiplyWithoutOverflow_Short(short x, short y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(x, y));
Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(y, x));
}
[Theory]
[InlineData(0x5555555555555555, 3, true)]
[InlineData(0x4000000000000000, 4, false)]
public void CanMultiplyWithoutOverflow_Ulong(ulong x, ulong y, bool expectedResult)
{
Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(x, y));
Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(y, x));
}
[Theory]
[InlineData(0, true)]
[InlineData(-1, false)]
[InlineData(unchecked((long)0x8111111100000000), false)]
[InlineData(long.MinValue, false)]
[InlineData(long.MaxValue, true)]
public void IsIntValueRepresentable_LongToUlong(long value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<ulong, long>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(5, true)]
[InlineData(long.MaxValue, true)]
[InlineData(long.MaxValue + 1LU, false)]
[InlineData(ulong.MaxValue, false)]
public void IsIntValueRepresentable_ULongToLong(ulong value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<long, ulong>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(-1, true)]
[InlineData(int.MinValue, true)]
[InlineData(int.MaxValue, true)]
[InlineData(int.MinValue - 1L, false)]
[InlineData(int.MaxValue + 1L, false)]
public void IsIntValueRepresentable_LongToInt(long value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<int, long>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(-1, true)]
[InlineData(short.MinValue, true)]
[InlineData(short.MaxValue, true)]
[InlineData(short.MinValue - 1L, false)]
[InlineData(short.MaxValue + 1L, false)]
public void IsIntValueRepresentable_LongToShort(long value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<short, long>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(-1, false)]
[InlineData(uint.MaxValue, true)]
[InlineData(uint.MaxValue + 1L, false)]
public void IsIntValueRepresentable_LongToUint(long value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<uint, long>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(-1, false)]
[InlineData(ushort.MaxValue, true)]
[InlineData(ushort.MaxValue + 1L, false)]
public void IsIntValueRepresentable_LongToUshort(long value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<ushort, long>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(-1, false)]
[InlineData(short.MinValue, false)]
[InlineData(short.MaxValue, true)]
public void IsIntValueRepresentable_ShortToUshort(short value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<ushort, short>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(0xFFFFF000, false)]
[InlineData(0xFFFF7FFF, false)]
[InlineData(short.MaxValue, true)]
[InlineData(short.MaxValue + 1, false)]
public void IsIntValueRepresentable_UintToShort(uint value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<short, uint>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(uint.MaxValue, true)]
[InlineData((ulong)uint.MaxValue + 1, false)]
public void IsIntValueRepresentable_UlongToUint(ulong value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<uint, ulong>(value);
Assert.Equal(expectedResult, actualResult);
}
[Theory]
[InlineData(0, true)]
[InlineData(uint.MaxValue, true)]
public void IsIntValueRepresentable_UintToUlong(uint value, bool expectedResult)
{
bool actualResult = IntUtil.IsIntValueRepresentable<ulong, uint> (value);
Assert.Equal(expectedResult, actualResult);
}
}