Add Pack/Unpack bits

Allows a bool list (no more than 64 in length) to be packed to an integer, which can be unpacked again
This commit is contained in:
Oliver Booth 2022-04-28 09:47:48 +01:00
parent b743adb445
commit e08274189a
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
10 changed files with 606 additions and 0 deletions

View File

@ -0,0 +1,56 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
namespace X10D.Tests.Collections;
[TestClass]
public class BoolListTests
{
[TestMethod]
public void Pack8Bit_Should_Pack_Correctly()
{
var array = new[] {true, false, true, false, true, false, true, false};
Assert.AreEqual(85, array.Pack8Bit()); // 01010101
}
[TestMethod]
public void Pack16Bit_Should_Pack_Correctly()
{
var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true};
Assert.AreEqual(2901, array.Pack16Bit()); // 101101010101
}
[TestMethod]
public void Pack32Bit_Should_Pack_Correctly()
{
var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true};
Assert.AreEqual(2901, array.Pack32Bit()); // 101101010101
}
[TestMethod]
public void Pack64Bit_Should_Pack_Correctly()
{
var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true};
Assert.AreEqual(2901, array.Pack64Bit()); // 101101010101
}
[TestMethod]
public void Pack_ShouldThrow_GivenLargeArray()
{
bool[] array = Enumerable.Repeat(false, 65).ToArray();
Assert.ThrowsException<ArgumentException>(() => array.Pack8Bit());
Assert.ThrowsException<ArgumentException>(() => array.Pack16Bit());
Assert.ThrowsException<ArgumentException>(() => array.Pack32Bit());
Assert.ThrowsException<ArgumentException>(() => array.Pack64Bit());
}
[TestMethod]
public void Pack_ShouldThrow_GivenNull()
{
bool[]? array = null;
Assert.ThrowsException<ArgumentNullException>(() => array!.Pack8Bit());
Assert.ThrowsException<ArgumentNullException>(() => array!.Pack16Bit());
Assert.ThrowsException<ArgumentNullException>(() => array!.Pack32Bit());
Assert.ThrowsException<ArgumentNullException>(() => array!.Pack64Bit());
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
namespace X10D.Tests.Collections;
[TestClass]
public class ByteTests
{
[TestMethod]
public void UnpackBits_ShouldUnpackToArrayCorrectly()
{
bool[] bits = ((byte)0b11010100).UnpackBits();
Assert.AreEqual(8, bits.Length);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
}
[TestMethod]
public void UnpackBits_ShouldUnpackToSpanCorrectly()
{
Span<bool> bits = stackalloc bool[8];
((byte)0b11010100).UnpackBits(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
}
[TestMethod]
public void UnpackBits_ShouldRepackEqually()
{
Assert.AreEqual(0b11010100, ((byte)0b11010100).UnpackBits().Pack8Bit());
}
[TestMethod]
public void UnpackBits_ShouldThrow_GivenTooSmallSpan()
{
Assert.ThrowsException<ArgumentException>(() =>
{
Span<bool> bits = stackalloc bool[0];
((byte)0b11010100).UnpackBits(bits);
});
}
}

View File

@ -0,0 +1,67 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
namespace X10D.Tests.Collections;
[TestClass]
public class Int16Tests
{
[TestMethod]
public void UnpackBits_ShouldUnpackToArrayCorrectly()
{
bool[] bits = ((short)0b11010100).UnpackBits();
Assert.AreEqual(16, bits.Length);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 16; index++)
{
Assert.IsFalse(bits[index]);
}
}
[TestMethod]
public void UnpackBits_ShouldUnpackToSpanCorrectly()
{
Span<bool> bits = stackalloc bool[16];
((short)0b11010100).UnpackBits(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 16; index++)
{
Assert.IsFalse(bits[index]);
}
}
[TestMethod]
public void UnpackBits_ShouldRepackEqually()
{
Assert.AreEqual(0b11010100, ((short)0b11010100).UnpackBits().Pack16Bit());
}
[TestMethod]
public void UnpackBits_ShouldThrow_GivenTooSmallSpan()
{
Assert.ThrowsException<ArgumentException>(() =>
{
Span<bool> bits = stackalloc bool[0];
((short)0b11010100).UnpackBits(bits);
});
}
}

View File

@ -0,0 +1,67 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
namespace X10D.Tests.Collections;
[TestClass]
public class Int32Tests
{
[TestMethod]
public void UnpackBits_ShouldUnpackToArrayCorrectly()
{
bool[] bits = 0b11010100.UnpackBits();
Assert.AreEqual(32, bits.Length);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 32; index++)
{
Assert.IsFalse(bits[index]);
}
}
[TestMethod]
public void UnpackBits_ShouldUnpackToSpanCorrectly()
{
Span<bool> bits = stackalloc bool[32];
0b11010100.UnpackBits(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 32; index++)
{
Assert.IsFalse(bits[index]);
}
}
[TestMethod]
public void UnpackBits_ShouldRepackEqually()
{
Assert.AreEqual(0b11010100, 0b11010100.UnpackBits().Pack32Bit());
}
[TestMethod]
public void UnpackBits_ShouldThrow_GivenTooSmallSpan()
{
Assert.ThrowsException<ArgumentException>(() =>
{
Span<bool> bits = stackalloc bool[0];
0b11010100.UnpackBits(bits);
});
}
}

View File

@ -0,0 +1,72 @@
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Collections;
using X10D.Linq;
namespace X10D.Tests.Collections;
[TestClass]
public class Int64Tests
{
[TestMethod]
public void UnpackBits_ShouldUnpackToArrayCorrectly()
{
bool[] bits = 0b11010100L.UnpackBits();
Assert.AreEqual(64, bits.Length);
Trace.WriteLine(Convert.ToString(0b11010100L, 2));
Trace.WriteLine(string.Join("", bits.Select(b => b ? "1" : "0")));
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 64; index++)
{
Assert.IsFalse(bits[index], index.ToString());
}
}
[TestMethod]
public void UnpackBits_ShouldUnpackToSpanCorrectly()
{
Span<bool> bits = stackalloc bool[64];
0b11010100L.UnpackBits(bits);
Assert.IsFalse(bits[0]);
Assert.IsFalse(bits[1]);
Assert.IsTrue(bits[2]);
Assert.IsFalse(bits[3]);
Assert.IsTrue(bits[4]);
Assert.IsFalse(bits[5]);
Assert.IsTrue(bits[6]);
Assert.IsTrue(bits[7]);
for (var index = 8; index < 64; index++)
{
Assert.IsFalse(bits[index], index.ToString());
}
}
[TestMethod]
public void UnpackBits_ShouldRepackEqually()
{
Assert.AreEqual(0b11010100L, 0b11010100L.UnpackBits().Pack64Bit());
}
[TestMethod]
public void UnpackBits_ShouldThrow_GivenTooSmallSpan()
{
Assert.ThrowsException<ArgumentException>(() =>
{
Span<bool> bits = stackalloc bool[0];
0b11010100L.UnpackBits(bits);
});
}
}

View File

@ -0,0 +1,123 @@
namespace X10D.Collections;
/// <summary>
/// Collection-related extension methods for <see cref="IReadOnlyList{T}" /> of <see cref="bool" />.
/// </summary>
public static class BoolListExtensions
{
/// <summary>
/// Packs a collection of booleans into a <see cref="byte" />.
/// </summary>
/// <param name="source">The collection of booleans to pack.</param>
/// <returns>An 8-bit unsigned integer containing the packed booleans.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception>
/// <author>Alpha Anar</author>
public static byte Pack8Bit(this IReadOnlyList<bool> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.Count > 8)
{
throw new ArgumentException("Source cannot contain more than than 8 elements.", nameof(source));
}
unsafe
{
fixed (bool* p = source.ToArray())
{
return (byte)(*(ulong*)p * 72624976668147840L >> 56); // evil fucking bit hack
}
}
}
/// <summary>
/// Packs a collection of booleans into a <see cref="short" />.
/// </summary>
/// <param name="source">The collection of booleans to pack.</param>
/// <returns>A 16-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 16 elements.</exception>
public static short Pack16Bit(this IReadOnlyList<bool> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.Count > 16)
{
throw new ArgumentException("Source cannot contain more than than 16 elements.", nameof(source));
}
short result = 0;
for (var i = 0; i < source.Count; i++)
{
result |= (short)(source[i] ? 1 << i : 0);
}
return result;
}
/// <summary>
/// Packs a collection of booleans into a <see cref="int" />.
/// </summary>
/// <param name="source">The collection of booleans to pack.</param>
/// <returns>A 32-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception>
public static int Pack32Bit(this IReadOnlyList<bool> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.Count > 32)
{
throw new ArgumentException("Source cannot contain more than than 32 elements.", nameof(source));
}
var result = 0;
for (var i = 0; i < source.Count; i++)
{
result |= source[i] ? 1 << i : 0;
}
return result;
}
/// <summary>
/// Packs a collection of booleans into a <see cref="long" />.
/// </summary>
/// <param name="source">The collection of booleans to pack.</param>
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
public static long Pack64Bit(this IReadOnlyList<bool> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.Count > 64)
{
throw new ArgumentException("Source cannot contain more than than 64 elements.", nameof(source));
}
var result = 0L;
for (var i = 0; i < source.Count; i++)
{
result |= source[i] ? 1L << i : 0;
}
return result;
}
}

View File

@ -0,0 +1,44 @@
namespace X10D.Collections;
/// <summary>
/// Collection-related extension methods for <see cref="byte" />.
/// </summary>
public static class ByteExtensions
{
private const int Size = sizeof(byte) * 8;
/// <summary>
/// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with length 8.</returns>
public static bool[] UnpackBits(this byte value)
{
Span<bool> buffer = stackalloc bool[Size];
value.UnpackBits(buffer);
return buffer.ToArray();
}
/// <summary>
/// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
/// <author>Alpha Anar</author>
public static void UnpackBits(this byte value, Span<bool> destination)
{
if (destination.Length < Size)
{
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
}
unsafe
{
fixed (bool* p = destination)
{
*(ulong*)p = ((ulong)value * 567382630219905) & 72340172838076673UL; // evil fucking bit hack
}
}
}
}

View File

@ -0,0 +1,40 @@
namespace X10D.Collections;
/// <summary>
/// Collection-related extension methods for <see cref="short" />.
/// </summary>
public static class Int16Extensions
{
private const int Size = sizeof(short) * 8;
/// <summary>
/// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with length 16.</returns>
public static bool[] UnpackBits(this short value)
{
Span<bool> buffer = stackalloc bool[Size];
value.UnpackBits(buffer);
return buffer.ToArray();
}
/// <summary>
/// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
public static void UnpackBits(this short value, Span<bool> destination)
{
if (destination.Length < Size)
{
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
}
for (var index = 0; index < Size; index++)
{
destination[index] = (value & (1 << index)) != 0;
}
}
}

View File

@ -0,0 +1,40 @@
namespace X10D.Collections;
/// <summary>
/// Collection-related extension methods for <see cref="int" />.
/// </summary>
public static class Int32Extensions
{
private const int Size = sizeof(int) * 8;
/// <summary>
/// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with length 32.</returns>
public static bool[] UnpackBits(this int value)
{
Span<bool> buffer = stackalloc bool[Size];
value.UnpackBits(buffer);
return buffer.ToArray();
}
/// <summary>
/// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
public static void UnpackBits(this int value, Span<bool> destination)
{
if (destination.Length < Size)
{
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
}
for (var index = 0; index < Size; index++)
{
destination[index] = (value & (1 << index)) != 0;
}
}
}

View File

@ -0,0 +1,40 @@
namespace X10D.Collections;
/// <summary>
/// Collection-related extension methods for <see cref="long" />.
/// </summary>
public static class Int64Extensions
{
private const int Size = sizeof(long) * 8;
/// <summary>
/// Unpacks this 64-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with length 64.</returns>
public static bool[] UnpackBits(this long value)
{
Span<bool> buffer = stackalloc bool[Size];
value.UnpackBits(buffer);
return buffer.ToArray();
}
/// <summary>
/// Unpacks this 64-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
public static void UnpackBits(this long value, Span<bool> destination)
{
if (destination.Length < Size)
{
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
}
for (var index = 0; index < Size; index++)
{
destination[index] = (value & (1L << index)) != 0;
}
}
}