Set Range of Bits in a ushort
Lets say I have a ushort value that I would like to set bits 1 to 4 inclusive (assuming 0 is the LSB and 15 is the MSB).
In C++ you could define a struct that mapped out specific bits:
struct KibblesNBits
{
unsigned short int TheStart: 1;
unsigned short int TheMeat: 4;
unsigned short int TheRest: 11;
}
Then you could assign a value to 'TheMeat' directly. I'm looking to do something similar in C#. Ideally, I would like a funcion definition that looked like this:
public ModValue SetRange<ModValue, RangeValue>(ModValue valueToMod, int startIndex, int endIndex, RangeValue rangeValueToAssign)
It would also need to valide that the rangeValueToAssign does not exceed the maximum size (assuming values are unsigned from 0 to max). So if the range is from 1 to 4, this is 4 bits, range would be from 0 to 15. If it is outside these limits, throw an exception.
I didnt find anything in the BitConverter class that could do something like this. Best I could think of开发者_开发技巧 was using manaul shift operators. Is there a better way to do this?
Edit: A non generic version might look something like this:
public static ushort SetRange(ushort valueToMod, int startIndex, int endIndex, ushort rangeValueToAssign)
{
// Determine max value
ushort max_value = Convert.ToUInt16(Math.Pow(2.0, (endIndex - startIndex) + 1.0) - 1);
if(rangeValueToAssign > max_value) throw new Exception("Value To Large For Range");
// Shift the value and add it to the orignal (effect of setting range?)
ushort value_to_add = (ushort)(rangeValueToAssign << startIndex);
return (ushort)(valueToMod + value_to_add);
}
Where:
ushort new_val = SetRange(120, 1, 2, 3);
would result in 'new_val' being set to 126.
public static int SetRange(int num, int from, int to, int value)
{
if (from < 0 || from > to || to >= 32) throw new ArgumentException("from/to are not valid");
if (value >= (2 << (to - from)) && (to - from < 31)) throw new ArgumentException("value is too large");
return num & ~(((2 << to) - 1) - ((1 << from) - 1)) | (value << from);
}
No for-loops or Math.Pow (which is amazingly slow, way slower than Sin/Cos etc).
As for generic - sorry, that won't work. There is no base type for numbers in C# (or .NET), so this is simply impossible. It looks like you're trying to use generics like template functions in C++ - don't be fooled by the similar looks; they are completely different.
If you must have different types, I'd suggest overloads instead.
public static int SetRange(int num, int from, int to, int value)
{
if (from < 0 || from > to || to >= 32) throw new ArgumentException("from/to are not valid");
if (value >= (2 << (to - from)) && (to - from < 31)) throw new ArgumentException("value is too large");
return num & ~(((2 << to) - 1) - ((1 << from) - 1)) | (value << from);
}
public static ushort SetRange(ushort num, int from, int to, ushort value)
{
if (from < 0 || from > to || to >= 16) throw new ArgumentException("from/to are not valid");
if (value >= (2 << (to - from))) throw new ArgumentException("value is too large");
return (ushort) (num & ~(((2 << to) - 1) - ((1 << from) - 1)) | (value << from));
}
However, in C# it might be more idiomatic to just always use int (or long if you need that).
The fixed type answers here helped me get to a generic solution as originally requested. Here's the final code (with a getter bonus).
/// <summary>Gets the bit array value from the specified range in a bit vector.</summary>
/// <typeparam name="T">The type of the bit vector. Must be of type <see cref="IConvertible"/>.</typeparam>
/// <param name="bits">The bit vector.</param>
/// <param name="startIdx">The zero-based start index of the bit range to get.</param>
/// <param name="count">The number of sequential bits to fetch starting at <paramref name="startIdx"/>.</param>
/// <returns>The value of the requested bit range.</returns>
public static T GetBits<T>(T bits, byte startIdx, byte count) where T : IConvertible
{
if (startIdx >= (Marshal.SizeOf(typeof(T)) * 8)) throw new ArgumentOutOfRangeException(nameof(startIdx));
if (count + startIdx > (Marshal.SizeOf(typeof(T)) * 8)) throw new ArgumentOutOfRangeException(nameof(count));
return (T)Convert.ChangeType((bits.ToInt64(null) >> startIdx) & ((1 << count) - 1), typeof(T));
}
/// <summary>Sets the bit values at the specified range in a bit vector.</summary>
/// <typeparam name="T">The type of the bit vector. Must be of type <see cref="IConvertible"/>.</typeparam>
/// <typeparam name="TValue">The type of the value. Must be of type <see cref="IConvertible"/>.</typeparam>
/// <param name="bits">The bit vector.</param>
/// <param name="startIdx">The zero-based start index of the bit range to set.</param>
/// <param name="count">The number of sequential bits to set starting at <paramref name="startIdx"/>.</param>
/// <param name="value">The value to set within the specified range of <paramref name="bits"/>.</param>
public static void SetBits<T, TValue>(ref T bits, byte startIdx, byte count, TValue value) where T : IConvertible where TValue : IConvertible
{
if (startIdx >= (Marshal.SizeOf(typeof(T)) * 8)) throw new ArgumentOutOfRangeException(nameof(startIdx));
if (count + startIdx > (Marshal.SizeOf(typeof(T)) * 8)) throw new ArgumentOutOfRangeException(nameof(count));
var val = value.ToInt64(null);
if (val >= (1 << count)) throw new ArgumentOutOfRangeException(nameof(value));
bits = (T)Convert.ChangeType(bits.ToInt64(null) & ~(((1 << count) - 1) << startIdx) | (val << startIdx), typeof(T));
}
If you want that type of access, consider a BitVector32
or BitArray
, or get familiar with bitwise arithmetic. You can do explicit layout of structs in C# (causing a union), but I don't think it is to your advantage. It is primarily aimed at interop scenarios.
For info, the bitwise operators work mainly on uint
/int
/ulong
/long
- not so much the smaller integer types.
I don't know of any language feature (apart from manually shifting bits around) that would achieve this.
The only thing that comes to my mind is the old BitArray from .NET 1.1. But you can only manipulate individual bits, not ranges of bits.
So, no.
Here is a fix for your update:
public static ushort SetRange(ushort valueToMod, int startIndex, int endIndex, ushort rangeValueToAssign)
{
// Determine max value
ushort max_value = Convert.ToUInt16(Math.Pow(2.0, (endIndex - startIndex) + 1.0) - 1);
if(rangeValueToAssign > max_value) throw new Exception("Value To Large For Range");
// Clear our bits where we want to "Set" the value for
for( int i=startIndex; i<endIndex; i++ )
valueToMod &= ~(1<<i);
// Shift the value and add it to the orignal (effect of setting range?)
ushort value_to_add = (ushort)(rangeValueToAssign << startIndex);
return (ushort)(valueToMod + value_to_add);
}
精彩评论