/// <summary> /// StreamWrite takes a native unsigned integer value value depending on OS, /// plus a number of bits and writes the least significant ValueBits of that value /// into the bit field array. /// The variant that takes a TEncodedBitFieldDescriptor will perform automatic /// native to encoded null value conversion and deal with subtracting the minvalue /// from the descriptor in the encoded value. /// The variant that take an exact number of value bits is a raw version of the write method that /// just writes the given value in the given bits. /// </summary> /// <param name="value"></param> /// <param name="descriptor"></param> public void StreamWrite(long value, EncodedBitFieldDescriptor descriptor) { // Writing occurs in three stages: // 1: Fill the remaining unwritten bits in the element referenced by FStreamWritePos // 2: If there are still more than N_BITS_TO_READ_AT_A_TIME remaining bits to be written, then write // individual elements from value into Storage until the remaining number of bits // to be written is less than N_BITS_TO_READ_AT_A_TIME // 3: If there are still (less than N_BITS_TO_READ_AT_A_TIME) bits to be written, then write the remainder // of the bits into the most significant bits of the next empty element in Storage int ValueBits = descriptor.RequiredBits; if (ValueBits == 0) // There's nothing to do! { return; } value = descriptor.Nullable && value == descriptor.NativeNullValue ? descriptor.EncodedNullValue - descriptor.MinValue : value - descriptor.MinValue; // Be paranoid! Ensure there are no bits set in the high order bits above the least significant valueBits in Value value &= (1L << ValueBits) - 1; int StoragePointer = StreamWriteBitPos >> BIT_LOCATION_TO_BLOCK_SHIFT; int AvailBitsInCurrentStorageElement = N_BITS_TO_READ_AT_A_TIME - unchecked ((byte)(StreamWriteBitPos & BITS_REMAINING_IN_STORAGE_BLOCK_MASK)); // Write initial bits into storage element if (AvailBitsInCurrentStorageElement >= ValueBits) { Storage[StoragePointer] |= unchecked ((ulong)value) << (AvailBitsInCurrentStorageElement - ValueBits); StreamWriteBitPos += ValueBits; // Advance the current bit position pointer; return; } // There are more bits than can fit in AvailBitsInCurrentStorageElement // Step 1: Fill remaining bits int RemainingBitsToWrite = ValueBits - AvailBitsInCurrentStorageElement; Storage[StoragePointer] |= unchecked ((ulong)value) >> RemainingBitsToWrite; /* When using long elements, there can never be a value stored that is larger that the storage element * // Step 2: Write whole elements * while (RemainingBitsToWrite > N_BITS_TO_READ_AT_A_TIME) * { * RemainingBitsToWrite -= N_BITS_TO_READ_AT_A_TIME; * Storage[++StoragePointer] = (byte)(value >> RemainingBitsToWrite); // Peel of the next element and place it into storage * } */ // Step 3: Write remaining bits into next element in Storage if (RemainingBitsToWrite > 0) // Mask out the bits we want... { Storage[StoragePointer + 1] = (unchecked ((ulong)value) & (((ulong)1 << RemainingBitsToWrite) - 1)) << (N_BITS_TO_READ_AT_A_TIME - RemainingBitsToWrite); } StreamWriteBitPos += ValueBits; // Advance the current bit position pointer; }
/// <summary> /// ReadBitField reads a single bit-field from the bitfield array. The bitfield is /// identified by the bit location and number of bits in the value to be read. /// Once the value has been read the value of bitLocation is set to the next bit after /// the end of the value just read. /// The variant that takes a TEncodedBitFieldDescriptor will perform automatic /// encoded to native null value conversion and deal with adding the minvalue /// from the descriptor in the read value. /// The variant that take an exact number of value bits is a raw version of the read method that /// just reads the value from the given bits. /// </summary> /// <param name="bitLocation"></param> /// <param name="descriptor"></param> /// <returns></returns> public long ReadBitField(ref int bitLocation, EncodedBitFieldDescriptor descriptor) { // Reading occurs in three stages: // 1: Read the remaining bits in the element referenced by bitLocation // 2: If there are still more than N_BITS_TO_READ_AT_A_TIME remaining bits to be read, then read // individual elements from Storage into Result until the remaining number of bits // to be read is less than N_BITS_TO_READ_AT_A_TIME // 3: If there are still (less than N_BITS_TO_READ_AT_A_TIME) bits to be read, then read the remainder // of the bits from the most significant bits of the next element in Storage int valueBits = descriptor.RequiredBits; if (valueBits == 0) // There's nothing to do! { return(descriptor.AllValuesAreNull ? descriptor.NativeNullValue : descriptor.MinValue); } int BlockPointer = bitLocation >> BIT_LOCATION_TO_BLOCK_SHIFT; int RemainingBitsInCurrentStorageBlock = N_BITS_TO_READ_AT_A_TIME - (bitLocation & BITS_REMAINING_IN_STORAGE_BLOCK_MASK); long Result; // Read initial bits from storage element if (RemainingBitsInCurrentStorageBlock >= valueBits) { Result = unchecked ((long)(Storage[BlockPointer] >> (RemainingBitsInCurrentStorageBlock - valueBits)) & ((1L << valueBits) - 1)); } else { // There are more bits than can fit in RemainingBitsInCurrentStorageElement // Step 1: Fill remaining bits Result = unchecked ((long)Storage[BlockPointer] & ((1 << RemainingBitsInCurrentStorageBlock) - 1)); int BitsToRead = valueBits - RemainingBitsInCurrentStorageBlock; /* When using long elements, there can never be a value stored that is larger that the storage element * // Step 2: Read whole elements * while (BitsToRead > BitFieldArray.N_BITS_TO_READ_AT_A_TIME) * { * BitsToRead -= BitFieldArray.N_BITS_TO_READ_AT_A_TIME; * Result = (Result << BitFieldArray.N_BITS_TO_READ_AT_A_TIME) | Storage[++BlockPointer]; // Add the next element from storage and put it in result * } */ // Step 3: Read remaining bits from next block in Storage Result = unchecked ((long)((unchecked ((ulong)Result) << BitsToRead) | (Storage[BlockPointer + 1] >> (N_BITS_TO_READ_AT_A_TIME - BitsToRead)))); } // Compute the true result of the read by taking nullability and the offset of MinValue into account Result = descriptor.Nullable && Result == descriptor.EncodedNullValue - descriptor.MinValue ? descriptor.NativeNullValue : Result + descriptor.MinValue; bitLocation += valueBits; // Advance the current bit position pointer; return(Result); }
/// <summary> /// Writes a consolidated vector of bit fields. /// </summary> /// <param name="bitLocation">The start bit index in the BitFieldArray to begin writing from</param> /// <param name="numValues">The number of values to be written to the vector</param> /// <param name="values">The array of values to provide the values written to the vector. This array must be at least as large as the number of value requested from the vector</param> public void WriteBitFieldVector(ref int bitLocation, EncodedBitFieldDescriptor descriptor, int numValues, long[] values) { if (values == null || numValues > values.Length) { throw new ArgumentException($"Supplied values array is null or not large enough to provide value vector of {numValues} values"); } // Check the bit field array allocated storage can receive enough values if (bitLocation + numValues * descriptor.RequiredBits > NumBits) { throw new ArgumentException($"Insufficient storage present to satisfy a write for {numValues} values from bit location {bitLocation}"); } // Writing occurs in three stages: // 1: Fill the remaining unwritten bits in the element referenced by FStreamWritePos // 2: If there are still more than N_BITS_TO_READ_AT_A_TIME remaining bits to be written, then write // individual elements from value into Storage until the remaining number of bits // to be written is less than N_BITS_TO_READ_AT_A_TIME // 3: If there are still (less than N_BITS_TO_READ_AT_A_TIME) bits to be written, then write the remainder // of the bits into the most significant bits of the next empty element in Storage int valueBits = descriptor.RequiredBits; if (valueBits == 0) // There's nothing to do! { return; } int StoragePointer = bitLocation >> BIT_LOCATION_TO_BLOCK_SHIFT; ulong blockValue = Storage[StoragePointer]; int AvailBitsInCurrentStorageElement = N_BITS_TO_READ_AT_A_TIME - (StreamWriteBitPos & BITS_REMAINING_IN_STORAGE_BLOCK_MASK); long valueMask = (1L << valueBits) - 1; for (int i = 0; i < numValues; i++) { long value = descriptor.Nullable && values[i] == descriptor.NativeNullValue ? descriptor.EncodedNullValue - descriptor.MinValue : values[i] - descriptor.MinValue; // Be paranoid! Ensure there are no bits set in the high order bits above the least significant valueBits in Value value &= valueMask; // Write initial bits into storage element if (AvailBitsInCurrentStorageElement >= valueBits) { blockValue |= (unchecked ((ulong)value) << (AvailBitsInCurrentStorageElement - valueBits)); StreamWriteBitPos += valueBits; // Advance the current bit position pointer; AvailBitsInCurrentStorageElement -= valueBits; continue; } // There are more bits than can fit in AvailBitsInCurrentStorageElement // Step 1: Fill remaining bits int RemainingBitsToWrite = valueBits - AvailBitsInCurrentStorageElement; blockValue |= (unchecked ((ulong)value) >> RemainingBitsToWrite); Storage[StoragePointer++] = blockValue; AvailBitsInCurrentStorageElement = N_BITS_TO_READ_AT_A_TIME - RemainingBitsToWrite; // Step 3: Write remaining bits into next element in Storage if (RemainingBitsToWrite > 0) // Mask out the bits we want... { blockValue = (unchecked ((ulong)value) & ((1UL << RemainingBitsToWrite) - 1)) << AvailBitsInCurrentStorageElement; } } if (StoragePointer < Storage.Length) { Storage[StoragePointer] = blockValue; } bitLocation += valueBits * numValues; // Advance the current bit position pointer; }
/// <summary> /// Reads a consolidated vector of bit fields. /// </summary> /// <param name="descriptor"></param> /// <param name="bitLocation">The start bit index in the BitFieldArray to begin reading from</param> /// <param name="numValues">The number of values to be read from the vector</param> /// <param name="values">The array of values to receive the values read from the vector. This array must be at least as large as the number of value requested from the vector</param> public void ReadBitFieldVector(ref int bitLocation, EncodedBitFieldDescriptor descriptor, int numValues, long[] values) { // Check the array is big enough if (values == null || numValues > values.Length) { throw new ArgumentException($"Supplied values array is null or not large enough to accept value vector of {numValues} values"); } int valueBits = descriptor.RequiredBits; // Check the bit field array allocated storage contains enough values if (bitLocation + numValues * valueBits > NumBits) { throw new ArgumentException($"Insufficient values present to satisfy a read for {numValues} values from bit location {bitLocation}"); } // Reading occurs in three stages: // 1: Read the remaining bits in the element referenced by bitLocation // 2: If there are still more than N_BITS_TO_READ_AT_A_TIME remaining bits to be read, then read // individual elements from Storage into Result until the remaining number of bits // to be read is less than N_BITS_TO_READ_AT_A_TIME // 3: If there are still (less than N_BITS_TO_READ_AT_A_TIME) bits to be read, then read the remainder // of the bits from the most significant bits of the next element in Storage if (valueBits == 0) // There's nothing to do! { return; } int BlockPointer = bitLocation >> BIT_LOCATION_TO_BLOCK_SHIFT; int RemainingBitsInCurrentStorageBlock = N_BITS_TO_READ_AT_A_TIME - (bitLocation & BITS_REMAINING_IN_STORAGE_BLOCK_MASK); // Read the first block containing the first value ulong blockValue = Storage[BlockPointer]; ulong valueMask = (1UL << valueBits) - 1; for (int i = 0; i < numValues; i++) { if (RemainingBitsInCurrentStorageBlock >= valueBits) { values[i] = (long)((blockValue >> (RemainingBitsInCurrentStorageBlock - valueBits)) & valueMask); RemainingBitsInCurrentStorageBlock -= valueBits; } else { // Work out the bits to be read from the next block int BitsToRead = valueBits - RemainingBitsInCurrentStorageBlock; // There are more bits than can fit in RemainingBitsInCurrentStorageElement // Step 1: Fill remaining bits long Result = unchecked ((long)(blockValue & ((1UL << RemainingBitsInCurrentStorageBlock) - 1)) << BitsToRead); blockValue = Storage[++BlockPointer]; RemainingBitsInCurrentStorageBlock = N_BITS_TO_READ_AT_A_TIME - BitsToRead; // Step 3: Read remaining bits from next block in Storage values[i] = Result | unchecked ((long)(blockValue >> RemainingBitsInCurrentStorageBlock)); } // Compute the true result of the read by taking nullability and the offset of MinValue into account values[i] = descriptor.Nullable && values[i] == descriptor.EncodedNullValue - descriptor.MinValue ? descriptor.NativeNullValue : values[i] + descriptor.MinValue; } bitLocation += valueBits * numValues; // Advance the current bit position pointer; }