Skip to content

Commit ee50e30

Browse files
committed
Refactor BitSetStructure and test for negative to positive span values
1 parent ccc7201 commit ee50e30

3 files changed

Lines changed: 39 additions & 17 deletions

File tree

Src/FastData.Tests/FastDataGeneratorTests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,21 @@ public void Generate_Overloads_Int32_Work()
169169
AssertKeysAndValues(generator, keys, values);
170170
}
171171

172+
[Fact]
173+
public void Generate_Auto_UsesBitSetForNegativeRanges()
174+
{
175+
int[] keys = [-2, -1, 0, 2];
176+
FastDataConfig config = new FastDataConfig();
177+
178+
ContextCaptureGenerator generator = new ContextCaptureGenerator();
179+
FastDataGenerator.Generate(keys, config, generator);
180+
181+
BitSetContext<int, byte> ctx = Assert.IsType<BitSetContext<int, byte>>(generator.Context);
182+
Assert.True(ctx.Values.IsEmpty);
183+
Assert.Single(ctx.BitSet);
184+
Assert.Equal(23UL, ctx.BitSet[0]);
185+
}
186+
172187
[Fact]
173188
public void GenerateKeyed_HashTablePerfect_ReordersValuesToMatchSlots()
174189
{
@@ -298,4 +313,4 @@ private static void AssertKeysAndValues<TKey, TValue>(ContextCaptureGenerator ge
298313
Assert.True(ctx.Keys.Span.SequenceEqual(keys));
299314
Assert.True(ctx.Values.Span.SequenceEqual(values));
300315
}
301-
}
316+
}

Src/FastData/FastDataGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ private static string GenerateNumericInternal<TKey, TValue>(ReadOnlyMemory<TKey>
228228
if (props.IsConsecutive && values.IsEmpty)
229229
return GenerateWrapper(tempState, new RangeStructure<TKey, TValue>(props));
230230

231-
if (props.Range <= fdCfg.BitSetStructureMaxRange && keySpan.Length / (double)props.Range >= fdCfg.BitSetStructureMinDensity)
231+
if ((keyType != KeyType.Single && keyType != KeyType.Double) && props.Range <= fdCfg.BitSetStructureMaxRange && keySpan.Length / (double)props.Range >= fdCfg.BitSetStructureMinDensity)
232232
return GenerateWrapper(tempState, new BitSetStructure<TKey, TValue>(props, keyType));
233233

234234
// For small amounts of data, logic is the fastest. However, it increases the assembly size, so we want to try some special cases first.
@@ -518,4 +518,4 @@ private static void Benchmark(byte[] data, int iterations, Candidate candidate)
518518

519519
private readonly record struct TempStringState<TKey, TValue>(ReadOnlyMemory<TKey> Keys, ReadOnlyMemory<TValue> Values, FastDataConfig Config, ICodeGenerator Generator, StringKeyProperties StringKeyProperties, HashDetails HashDetails, string TrimPrefix, string TrimSuffix);
520520
private readonly record struct TempNumericState<TKey, TValue>(ReadOnlyMemory<TKey> Keys, ReadOnlyMemory<TValue> Values, FastDataConfig Config, ICodeGenerator Generator, NumericKeyProperties<TKey> NumericKeyProperties, HashDetails HashDetails, KeyType KeyType);
521-
}
521+
}

Src/FastData/Internal/Structures/BitSetStructure.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ internal sealed class BitSetStructure<TKey, TValue>(NumericKeyProperties<TKey> p
99
{
1010
public BitSetContext<TKey, TValue> Create(ReadOnlyMemory<TKey> keys, ReadOnlyMemory<TValue> values)
1111
{
12-
int range = (int)props.Range + 1;
13-
ulong[] bitset = new ulong[(range + 63) / 64];
14-
TValue[]? denseValues = values.IsEmpty ? null : new TValue[range];
12+
if (keyType is KeyType.Single or KeyType.Double)
13+
throw new InvalidOperationException("Floating point values are not supported for BitSets");
14+
1515
ReadOnlySpan<TKey> keySpan = keys.Span;
1616
ReadOnlySpan<TValue> valueSpan = values.Span;
1717

18+
int range = (int)(props.Range + 1);
19+
ulong[] bitset = new ulong[(range + 63) / 64];
20+
TValue[]? denseValues = values.IsEmpty ? null : new TValue[range];
21+
22+
Func<TKey, long> func = GetLongConverter(keyType);
23+
long minKey = func(props.MinKeyValue);
24+
1825
for (int i = 0; i < keySpan.Length; i++)
1926
{
20-
ulong offset = GetOffset(keyType, keySpan[i], props.MinKeyValue);
27+
ulong offset = (ulong)(func(keySpan[i]) - minKey);
2128
int word = (int)(offset >> 6);
2229
bitset[word] |= 1UL << (int)(offset & 63);
2330

@@ -28,17 +35,17 @@ public BitSetContext<TKey, TValue> Create(ReadOnlyMemory<TKey> keys, ReadOnlyMem
2835
return new BitSetContext<TKey, TValue>(bitset, denseValues);
2936
}
3037

31-
private static ulong GetOffset(KeyType keyType, TKey key, TKey min) => keyType switch
38+
private static Func<TKey, long> GetLongConverter(KeyType keyType) => keyType switch
3239
{
33-
KeyType.Char => (char)(object)key - (uint)(char)(object)min,
34-
KeyType.SByte => (ulong)((long)(sbyte)(object)key - (sbyte)(object)min),
35-
KeyType.Byte => (ulong)((byte)(object)key - (byte)(object)min),
36-
KeyType.Int16 => (ulong)((long)(short)(object)key - (short)(object)min),
37-
KeyType.UInt16 => (ulong)((ushort)(object)key - (ushort)(object)min),
38-
KeyType.Int32 => (ulong)((long)(int)(object)key - (int)(object)min),
39-
KeyType.UInt32 => (uint)(object)key - (uint)(object)min,
40-
KeyType.Int64 => (ulong)((long)(object)key - (long)(object)min),
41-
KeyType.UInt64 => (ulong)(object)key - (ulong)(object)min,
40+
KeyType.SByte => static key => (sbyte)(object)key,
41+
KeyType.Int16 => static key => (short)(object)key,
42+
KeyType.Int32 => static key => (int)(object)key,
43+
KeyType.Int64 => static key => (long)(object)key,
44+
KeyType.Byte => static key => (byte)(object)key,
45+
KeyType.UInt16 => static key => (ushort)(object)key,
46+
KeyType.UInt32 => static key => (uint)(object)key,
47+
KeyType.UInt64 => static key => (long)(object)key,
48+
KeyType.Char => static key => (ushort)(object)key,
4249
_ => throw new InvalidOperationException($"Unsupported key type: {keyType}")
4350
};
4451
}

0 commit comments

Comments
 (0)