简体   繁体   English

在ushort中设置比特范围

[英]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). 假设我有一个ushort值,我想设置位1到4(假设0是LSB,15是MSB)。

In C++ you could define a struct that mapped out specific bits: 在C ++中,您可以定义一个映射出特定位的结构:

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. 然后你可以直接为'TheMeat'赋值。 I'm looking to do something similar in C#. 我想在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). 还需要确定rangeValueToAssign不超过最大大小(假设值从0到最大值无符号)。 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. 因此,如果范围是1到4,则这是4位,范围将从0到15.如果超出这些限制,则抛出异常。

I didnt find anything in the BitConverter class that could do something like this. 我没有在BitConverter类中找到任何可以执行此类操作的内容。 Best I could think of was using manaul shift operators. 我能想到的最好的就是使用manaul移位运算符。 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. 会导致'new_val'设置为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). 没有for循环或Math.Pow(这是非常慢,比Sin / Cos等慢)。

As for generic - sorry, that won't work. 至于通用 - 抱歉,这是行不通的。 There is no base type for numbers in C# (or .NET), so this is simply impossible. C#(或.NET)中的数字没有基类型,所以这根本不可能。 It looks like you're trying to use generics like template functions in C++ - don't be fooled by the similar looks; 看起来你正试图在C ++中使用像模板函数这样的泛型 - 不要被类似的外观所迷惑; 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). 但是,在C#中,总是使用int(或者如果你需要那么长)可能更惯用。

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. 如果您想要这种类型的访问,请考虑使用BitVector32BitArray ,或熟悉按位算术。 You can do explicit layout of structs in C# (causing a union), but I don't think it is to your advantage. 可以在C#中明确布局结构(导致联合),但我不认为这对您有利。 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. 对于info,按位运算符主要在uint / int / ulong / long - 而不是更小的整数类型。

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. 我唯一想到的是来自.NET 1.1的旧BitArray 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);
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM