diff --git a/csharp/Platform.Collections.Tests/BitStringTests.cs b/csharp/Platform.Collections.Tests/BitStringTests.cs index 988deca8..8cecf634 100644 --- a/csharp/Platform.Collections.Tests/BitStringTests.cs +++ b/csharp/Platform.Collections.Tests/BitStringTests.cs @@ -142,6 +142,124 @@ public static void BitParallelVectorXorTest() w.Xor(v); }); } + + [Fact] + public static void LengthIncreaseTest() + { + var bitString = new BitString(32); + bitString[5] = true; + bitString[15] = true; + bitString[31] = true; + + Assert.Equal(32, bitString.Length); + + // Increase length + bitString.Length = 64; + Assert.Equal(64, bitString.Length); + + // Original bits should be preserved + Assert.True(bitString[5]); + Assert.True(bitString[15]); + Assert.True(bitString[31]); + + // New bits should be false + for (int i = 32; i < 64; i++) + { + Assert.False(bitString[i]); + } + } + + [Fact] + public static void LengthDecreaseTest() + { + var bitString = new BitString(64); + bitString[5] = true; + bitString[15] = true; + bitString[25] = true; + bitString[45] = true; + bitString[55] = true; + + // Decrease length + bitString.Length = 32; + Assert.Equal(32, bitString.Length); + + // Bits within new length should be preserved + Assert.True(bitString[5]); + Assert.True(bitString[15]); + Assert.True(bitString[25]); + } + + [Fact] + public static void LengthSetToZeroTest() + { + var bitString = new BitString(32); + bitString[5] = true; + bitString[15] = true; + + bitString.Length = 0; + Assert.Equal(0, bitString.Length); + } + + [Fact] + public static void LengthCrossWordBoundaryTest() + { + var bitString = new BitString(63); // Just under 64 (1 word) + bitString[62] = true; + + // Increase to cross word boundary + bitString.Length = 128; // 2 words + Assert.Equal(128, bitString.Length); + Assert.True(bitString[62]); + + // Set bit in second word + bitString[100] = true; + Assert.True(bitString[100]); + + // Decrease back + bitString.Length = 63; + Assert.Equal(63, bitString.Length); + Assert.True(bitString[62]); + } + + [Fact] + public static void LengthPartialWordMaskingTest() + { + var bitString = new BitString(70); // 1 full word + 6 bits in second word + + // Set bits in both words + bitString[63] = true; // Last bit of first word + bitString[64] = true; // First bit of second word + bitString[69] = true; // Last valid bit + + Assert.True(bitString[63]); + Assert.True(bitString[64]); + Assert.True(bitString[69]); + + // Decrease to middle of second word - should mask off bit 69 + bitString.Length = 67; // Should keep first 3 bits of second word + + Assert.Equal(67, bitString.Length); + Assert.True(bitString[63]); + Assert.True(bitString[64]); + + // Increase back and check that bit 69 is cleared (masked off) + bitString.Length = 70; + Assert.False(bitString[69]); // Should be false because it was masked off + } + + [Fact] + public static void LengthSameValueTest() + { + var bitString = new BitString(32); + bitString[5] = true; + bitString[15] = true; + + bitString.Length = 32; // Set to same value + + Assert.Equal(32, bitString.Length); + Assert.True(bitString[5]); + Assert.True(bitString[15]); + } private static void TestToOperationsWithSameMeaning(Action test) { const int n = 5654; diff --git a/csharp/Platform.Collections/BitString.cs b/csharp/Platform.Collections/BitString.cs index 4b0b36ac..e061b9a8 100644 --- a/csharp/Platform.Collections/BitString.cs +++ b/csharp/Platform.Collections/BitString.cs @@ -59,34 +59,76 @@ public long Length return; } Ensure.Always.ArgumentInRange(value, GetValidLengthRange(), nameof(Length)); - // Currently we never shrink the array + if (value > _length) { - var words = GetWordsCountFromIndex(value); + // Expanding the bit string + var newWords = GetWordsCountFromIndex(value); var oldWords = GetWordsCountFromIndex(_length); - if (words > _array.LongLength) + + if (newWords > _array.LongLength) { - var copy = new long[words]; + // Need to expand the array + var copy = new long[newWords]; Array.Copy(_array, copy, _array.LongLength); _array = copy; } - else + + // Clear any new words that were added + if (newWords > oldWords) { - // What is going on here? - Array.Clear(_array, (int)oldWords, (int)(words - oldWords)); + Array.Clear(_array, (int)oldWords, (int)(newWords - oldWords)); } - // What is going on here? - var mask = (int)(_length % 64); - if (mask > 0) + + // Mask off any extra bits in the last word of the old length + // This ensures that bits beyond the old length are cleared + var oldBitsInLastWord = (int)(_length % 64); + if (oldBitsInLastWord > 0 && oldWords > 0) { - _array[oldWords - 1] &= (1L << mask) - 1; + var lastOldWordIndex = oldWords - 1; + _array[lastOldWordIndex] &= (1L << oldBitsInLastWord) - 1; } } else { - // Looks like minimum and maximum positive words are not updated - throw new NotImplementedException(); + // Shrinking the bit string + var newWords = GetWordsCountFromIndex(value); + var oldWords = GetWordsCountFromIndex(_length); + + // Clear any words that are now beyond the new length + if (newWords < oldWords) + { + Array.Clear(_array, (int)newWords, (int)(oldWords - newWords)); + } + + // Mask off any extra bits in the last word of the new length + var newBitsInLastWord = (int)(value % 64); + if (newBitsInLastWord > 0 && newWords > 0) + { + var lastNewWordIndex = newWords - 1; + _array[lastNewWordIndex] &= (1L << newBitsInLastWord) - 1; + } + else if (value == 0) + { + // Special case: length is 0, clear everything + if (_array.Length > 0) + { + Array.Clear(_array, 0, (int)Math.Min(_array.Length, oldWords)); + } + } + + // Update the borders to reflect the new state + if (value == 0) + { + MarkBordersAsAllBitsReset(); + } + else + { + // Recalculate borders since we may have cleared some set bits + TryShrinkBorders(); + } } + _length = value; } } diff --git a/experiments/LengthPropertyTests.cs b/experiments/LengthPropertyTests.cs new file mode 100644 index 00000000..d8d8909f --- /dev/null +++ b/experiments/LengthPropertyTests.cs @@ -0,0 +1,149 @@ +using System; +using Xunit; +using Platform.Collections; + +namespace Platform.Collections.Tests +{ + public class LengthPropertyTests + { + [Fact] + public void Length_IncreaseFromZero_ShouldWork() + { + var bitString = new BitString(0); + Assert.Equal(0, bitString.Length); + + // Should be able to increase length + bitString.Length = 64; + Assert.Equal(64, bitString.Length); + + // All bits should be false initially + for (int i = 0; i < 64; i++) + { + Assert.False(bitString[i]); + } + } + + [Fact] + public void Length_IncreaseFromNonZero_ShouldPreserveExistingBits() + { + var bitString = new BitString(32); + + // Set some bits + bitString[5] = true; + bitString[15] = true; + bitString[25] = true; + + // Increase length + bitString.Length = 64; + Assert.Equal(64, bitString.Length); + + // Original bits should be preserved + Assert.True(bitString[5]); + Assert.True(bitString[15]); + Assert.True(bitString[25]); + + // New bits should be false + for (int i = 32; i < 64; i++) + { + Assert.False(bitString[i]); + } + } + + [Fact] + public void Length_DecreaseLength_ShouldWork() + { + var bitString = new BitString(64); + + // Set some bits + bitString[5] = true; + bitString[15] = true; + bitString[25] = true; + bitString[45] = true; + bitString[55] = true; + + // Decrease length + bitString.Length = 32; + Assert.Equal(32, bitString.Length); + + // Bits within new length should be preserved + Assert.True(bitString[5]); + Assert.True(bitString[15]); + Assert.True(bitString[25]); + + // Should not be able to access bits beyond new length + Assert.Throws(() => bitString[45]); + Assert.Throws(() => bitString[55]); + } + + [Fact] + public void Length_SetToSameValue_ShouldNotChangeAnything() + { + var bitString = new BitString(32); + bitString[5] = true; + bitString[15] = true; + + bitString.Length = 32; // Set to same value + + Assert.Equal(32, bitString.Length); + Assert.True(bitString[5]); + Assert.True(bitString[15]); + } + + [Fact] + public void Length_CrossWordBoundaries_ShouldWork() + { + var bitString = new BitString(63); // Just under 64 (1 word) + bitString[62] = true; + + // Increase to cross word boundary + bitString.Length = 128; // 2 words + Assert.Equal(128, bitString.Length); + Assert.True(bitString[62]); + + // Set bit in second word + bitString[100] = true; + Assert.True(bitString[100]); + + // Decrease back + bitString.Length = 63; + Assert.Equal(63, bitString.Length); + Assert.True(bitString[62]); + } + + [Fact] + public void Length_SetToZero_ShouldWork() + { + var bitString = new BitString(64); + bitString[5] = true; + bitString[15] = true; + + bitString.Length = 0; + Assert.Equal(0, bitString.Length); + + // Should not be able to access any bits + Assert.Throws(() => bitString[0]); + } + + [Fact] + public void Length_HandlePartialWordMasking_ShouldWork() + { + var bitString = new BitString(70); // 1 full word + 6 bits in second word + + // Set bits in both words + bitString[63] = true; // Last bit of first word + bitString[64] = true; // First bit of second word + bitString[69] = true; // Last valid bit + + // Decrease to middle of second word + bitString.Length = 67; // Should keep first 3 bits of second word + + Assert.Equal(67, bitString.Length); + Assert.True(bitString[63]); + Assert.True(bitString[64]); + Assert.Throws(() => bitString[69]); + + // The bit at index 69 should be cleared from internal storage + // even though we can't access it anymore + } + } +} \ No newline at end of file diff --git a/experiments/LengthTest/LengthTest.csproj b/experiments/LengthTest/LengthTest.csproj new file mode 100644 index 00000000..20bce6d9 --- /dev/null +++ b/experiments/LengthTest/LengthTest.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/experiments/LengthTest/Program.cs b/experiments/LengthTest/Program.cs new file mode 100644 index 00000000..42f70e9e --- /dev/null +++ b/experiments/LengthTest/Program.cs @@ -0,0 +1,81 @@ +using System; +using Platform.Collections; + +class Program +{ + static void Main() + { + Console.WriteLine("Testing BitString Length property behavior"); + + try + { + Console.WriteLine("\n1. Testing Length increase from 32 to 64..."); + var bitString = new BitString(32); + bitString[5] = true; + bitString[15] = true; + Console.WriteLine($"Initial length: {bitString.Length}"); + Console.WriteLine($"Bits set: [5]={bitString[5]}, [15]={bitString[15]}"); + + bitString.Length = 64; // This should work + Console.WriteLine($"After increasing to 64: {bitString.Length}"); + Console.WriteLine($"Bits preserved: [5]={bitString[5]}, [15]={bitString[15]}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error during length increase: {ex.Message}"); + } + + try + { + Console.WriteLine("\n2. Testing Length decrease from 64 to 32..."); + var bitString = new BitString(64); + bitString[5] = true; + bitString[45] = true; + Console.WriteLine($"Initial length: {bitString.Length}"); + Console.WriteLine($"Bits set: [5]={bitString[5]}, [45]={bitString[45]}"); + + bitString.Length = 32; // This currently throws NotImplementedException + Console.WriteLine($"After decreasing to 32: {bitString.Length}"); + Console.WriteLine($"Bit [5] preserved: {bitString[5]}"); + } + catch (NotImplementedException) + { + Console.WriteLine("Length decrease throws NotImplementedException (expected issue)"); + } + catch (Exception ex) + { + Console.WriteLine($"Unexpected error: {ex.Message}"); + } + + try + { + Console.WriteLine("\n3. Testing Length set to 0..."); + var bitString = new BitString(10); + Console.WriteLine($"Initial length: {bitString.Length}"); + + bitString.Length = 0; + Console.WriteLine($"After setting to 0: {bitString.Length}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error setting length to 0: {ex.Message}"); + } + + try + { + Console.WriteLine("\n4. Testing cross-word boundary (63 to 65)..."); + var bitString = new BitString(63); + bitString[62] = true; + Console.WriteLine($"Initial length: {bitString.Length}"); + Console.WriteLine($"Bit [62] set: {bitString[62]}"); + + bitString.Length = 65; + Console.WriteLine($"After increasing to 65: {bitString.Length}"); + Console.WriteLine($"Bit [62] preserved: {bitString[62]}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error with cross-word boundary: {ex.Message}"); + } + } +} diff --git a/experiments/TestLengthBehavior.cs b/experiments/TestLengthBehavior.cs new file mode 100644 index 00000000..6bbe0441 --- /dev/null +++ b/experiments/TestLengthBehavior.cs @@ -0,0 +1,81 @@ +using System; +using Platform.Collections; + +class Program +{ + static void Main() + { + Console.WriteLine("Testing BitString Length property behavior"); + + try + { + Console.WriteLine("\n1. Testing Length increase from 32 to 64..."); + var bitString = new BitString(32); + bitString[5] = true; + bitString[15] = true; + Console.WriteLine($"Initial length: {bitString.Length}"); + Console.WriteLine($"Bits set: [5]={bitString[5]}, [15]={bitString[15]}"); + + bitString.Length = 64; // This should work + Console.WriteLine($"After increasing to 64: {bitString.Length}"); + Console.WriteLine($"Bits preserved: [5]={bitString[5]}, [15]={bitString[15]}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error during length increase: {ex.Message}"); + } + + try + { + Console.WriteLine("\n2. Testing Length decrease from 64 to 32..."); + var bitString = new BitString(64); + bitString[5] = true; + bitString[45] = true; + Console.WriteLine($"Initial length: {bitString.Length}"); + Console.WriteLine($"Bits set: [5]={bitString[5]}, [45]={bitString[45]}"); + + bitString.Length = 32; // This currently throws NotImplementedException + Console.WriteLine($"After decreasing to 32: {bitString.Length}"); + Console.WriteLine($"Bit [5] preserved: {bitString[5]}"); + } + catch (NotImplementedException) + { + Console.WriteLine("Length decrease throws NotImplementedException (expected issue)"); + } + catch (Exception ex) + { + Console.WriteLine($"Unexpected error: {ex.Message}"); + } + + try + { + Console.WriteLine("\n3. Testing Length set to 0..."); + var bitString = new BitString(10); + Console.WriteLine($"Initial length: {bitString.Length}"); + + bitString.Length = 0; + Console.WriteLine($"After setting to 0: {bitString.Length}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error setting length to 0: {ex.Message}"); + } + + try + { + Console.WriteLine("\n4. Testing cross-word boundary (63 to 65)..."); + var bitString = new BitString(63); + bitString[62] = true; + Console.WriteLine($"Initial length: {bitString.Length}"); + Console.WriteLine($"Bit [62] set: {bitString[62]}"); + + bitString.Length = 65; + Console.WriteLine($"After increasing to 65: {bitString.Length}"); + Console.WriteLine($"Bit [62] preserved: {bitString[62]}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error with cross-word boundary: {ex.Message}"); + } + } +} \ No newline at end of file