如何选择bitflag值?

时间:2009-02-04 22:22:55

标签: language-agnostic code-generation bit-fields bitflags

我有一组选项,一些是正交的(可以任意组合组合),一些是独占的(只允许一组中的一个),并且需要选择一组enum值以便它们可以与逐位or组合并使用逐位and提取。我希望or - 无效组合是可检测的。

是否有工具用于生成此类enums

为了清晰起见而编辑

我正在寻找能够利用某些标志组合无效以减少使用的位数的事实。我能够检测错误的要求是软的。我需要知道如果事情被搞砸了会使用什么。

我正在使用C#,但任何解决方案都应该有用。

示例模式是:

0011 00
0101 00
1001 00
0110 00
1010 00
1100 00

0000 01
0000 10

获得6个独占标志和一个2到6位的正交对

快速测试表明,5位给出9个值,6位给出20,......

7 个答案:

答案 0 :(得分:5)

我知道这样做的最好的通用方法不是一个常规的工具:定义像这样的位标志列表:

FLAG_1                    0x00000001
FLAG_2                    0x00000002
FLAG_3                    0x00000004
FLAG_4                    0x00000008
FLAG_5                    0x00000010
FLAG_6                    0x00000020

这很容易使用,因为数字继续向左移动1,2,4,8模式。

编辑:回应评论。好吧,如果你真的想要一个bitflags与独占枚举的组合,你基本上要做的是将位列表的一部分分割出来作为数字空间。所以你可以取两位0x1和0x2,现在你可以使用这两位代表0-3。类似的东西:

OPT_1_VAL_1               0x00000000
OPT_1_VAL_2               0x00000001
OPT_1_VAL_3               0x00000002
OPT_1_VAL_4               0x00000003
FLAG_1                    0x00000004
FLAG_2                    0x00000008
FLAG_3                    0x00000010
FLAG_4                    0x00000020

您使用的屏蔽逻辑必须更复杂。要查找标记,您可以执行if(settings& FLAG_1),但是如果((settings& OPT_1_VAL_3)== OPT_1_VAL_3),您可以执行选项空格。

答案 1 :(得分:3)

我不知道有什么工具,但这里有一个让特殊位枚举更容易制作的技巧:

public enum Critters
{
   Amorphous = 0,
   Sloth =     1 << 0,
   Armadillo = 1 << 1,
   Weasel =    1 << 2,
   Crab =      1 << 3,
   Partridge = 1 << 4,
   Parakeet =  1 << 5,
   Rhino =     1 << 6
};

答案 2 :(得分:3)

要表示“{strong>独占”一组n选项(即必须选择一个),我们至少需要ceil(log2(n))位。例如,选项k可以用基数k中的2来表示。

要表示“正交”一组n个选项(即可以选择大小0, 1, ..., n的任意组合),我们至少需要n位。例如,选项k0, k1, k2可以用二进制数表示,除了位0, 1, 2之外,其位为零。

因此,为了同时表示多个选项集,我们将每个选项集所需的位数(取决于它是“独占”还是“正交”)相加,以获得所需的总位数。

简而言之,要选择枚举值

  • “独家”选项k使用k << r
  • “orthogonal”选项k0, k1, ..., k{n-1}使用0x1 << r, 0x1 << (r+1), ..., 0x1 << (r+n-1)

其中offset r是前面选项集使用的位数。


如何自动化此构造的示例,在Java中:

/**
 * Construct a set of enum values, for the given sizes and types 
 * (exclusive vs orthogonal) of options sets.
 *
 * @param optionSetSizes
 *     number of elements in each option set
 * @param isOptionSetExclusive
 *     true if corresponding option set is exclusive, false if
 *     orthogonal
 * @returns
 *     array of m elements representing the enum values, where 
 *     m is the sum of option set sizes. The enum values are 
 *     given in the order of the option sets in optionSetSizes 
 *     and isOptionSetExclusive.
 */ 
int[] constructEnumValues(
        int[] optionSetSizes, 
        boolean[] isOptionSetExclusive)
{
    assert optionSetSizes.length == isOptionSetExclusive.length;

    // determine length of the return value
    int m = 0; 
    for (int i = 0; i < optionSetSizes.length; i++) m += optionSetSizes[i];
    int[] vals = new int[m];

    int r = 0; // number of bits used by the preceding options sets
    int c = 0; // counter for enum values used 

    for (int i = 0; i < optionSetSizes.length; i++)
    {
        // size of this option set
        int n = optionSetSizes[i];                   

        // is this option set exclusive?
        boolean exclusive = isOptionSetExclusive[i]; 

        for (int k = 0; k < n; k++)
        {
            vals[c] = (exclusive) ? (k << r) : (0x1 << (r + k));
            c++;
        }

        r += (exclusive) ? (int) Math.ceil(Math.log(n)/Math.log(2)) : n; 
    } 

    return vals;
}

答案 3 :(得分:2)

  

...需要选择一组枚举值   这样他们就可以合并......

真的需要手动选择吗?例如,Java有EnumSet为你做脏工作,并为你提供一个Set接口来操作这些标志。

答案 4 :(得分:2)

使用2的幂,使每个标志对应一个位位置。

答案 5 :(得分:1)

您可以使用标准枚举(在C#中)来实现此目的。要完成此操作,您需要设置FlagsAttribute,然后专门对值进行编号。代码看起来像这样:

[Flags]
public enum AvailableColours {
    black = 1,
    red = 2,
    green = 4,
    blue = 8,
    white = 16,
}

然后,标准的按位运算符将按预期工作。

[编辑] 嗯,好的,你想生成可能的组合,对吧?您的要求非常具体,如果有任何工具接近您想要的,我会非常惊讶。我想你将不得不自己动手。我假设你想要这些作为字符串,对吗?以下是一些实用程序代码,至少可以帮助您入门:

public const int BITS_IN_BYTE = 8;
public const int BYTES_IN_INT = sizeof(int);
public const int BITS_IN_INT = BYTES_IN_INT * BITS_IN_BYTE;

/// <summary>
/// Display the bits in an integer
/// </summary>
/// <param name="intToDisplay">The integer to display</param>
/// <returns>A string representation of the bits</returns>
public string IntToBitString(int intToDisplay) {
    StringBuilder sb = new StringBuilder();
    AppendBitString(intToDisplay, sb);
    return sb.ToString();
}

/// <summary>
/// Displays the bits in an integer array
/// </summary>
/// <param name="intsToDisplay">Arrau to display</param>
/// <returns>String representation of the bits</returns>
public string IntArrayToBitString(int[] intsToDisplay) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < intsToDisplay.Length -1; i++) {
        AppendBitString(intsToDisplay[i], sb);
        sb.Append(' ');
    }
    if (intsToDisplay.Length - 1 > 0)
        AppendBitString(intsToDisplay[intsToDisplay.Length - 1], sb);
    return sb.ToString();
}

private void AppendBitString(int intToAppend, StringBuilder sb) {
    for (int j = BITS_IN_INT - 1; j >= 0; j--) {
        sb.Append((intToAppend >> j) & 1);
        if (j % 4 == 0 && j > 1)
            sb.Append(' ');
    }
}

/// <summary>
/// Creates an integer from a bit string. This method can be used
/// to explicitly set bits in an integer during testing.
/// </summary>
/// <example>
/// int i = bitUtil.IntFromBitString("0000 0000 0000 0100");
/// </example>
/// <param name="bitString">String representing the individual bits</param>
/// <returns></returns>
public int IntFromBitString(String bitString) {
    int returnInt = 0;
    int currentBitPos = bitString.Length;
    for (int i = bitString.Length - 1; i >= 0; i--) {
        char c = bitString[i];
        if (Char.IsWhiteSpace(c)) continue;

        if (c == '1') {
            returnInt |= 1 << (bitString.Length - currentBitPos);
        }
        currentBitPos--;
    }
    return returnInt;
}

/// <summary>
/// Tests the status of an individual bit in and integer. It is 0 based starting from the most
/// significant bit. 
/// </summary>
/// <param name="bits">The integer to test</param>
/// <param name="pos">The position we're interested in</param>
/// <returns>True if the bit is set, false otherwise</returns>
public bool IsBitOn(int bits, int pos) {
    int shiftAmnt = (BITS_IN_INT - 1) - pos;
    return ((bits >> shiftAmnt) & 1) == 1;
}

/// <summary>
/// Calculates the number of integers (as in an array of ints) required to
/// store a given number of bits
/// </summary>
/// <param name="bitsNeeded">The total count of required bits</param>
/// <returns>The number of integers required to represent a given bit count</returns>
public int RequiredSizeOfIntArray(int bitsNeeded) {
    return (bitsNeeded / BITS_IN_INT) + (((bitsNeeded % BITS_IN_INT) == 0) ? 0 : 1);
}

/// <summary>
/// Calculates which array element would hold the individual bit for a given bit position
/// </summary>
/// <param name="bitPos">The position of the interesting bit</param>
/// <returns>An index into an array of integers</returns>
public int ArrayPositionForBit(int bitPos) {
    return bitPos / BITS_IN_INT;
}

/// <summary>
/// Sets an individual bit to a given value
/// </summary>
/// <param name="bits">The integer containing the bits</param>
/// <param name="pos">The position in the integer to set</param>
/// <param name="isSet">True for on, False for off</param>
public void SetBit(ref int bits, int pos, bool isSet) {
    int posToSet = (BITS_IN_INT - 1) - pos;
    if (isSet)
        bits |= 1 << posToSet;
    else
        bits &= ~(1 << posToSet);
}

/// <summary>
/// Converts an array of integers into a comma seperated list
/// of hexidecimal values.
/// </summary>
/// <param name="bits">The array of integers</param>
/// <returns>String format</returns>
public String IntArrayToHexString(int[] bits) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bits.Length - 1; i++) {
        sb.Append(bits[i].ToString("X"));
        sb.Append(',');
    }
    if (bits.Length > 0) {
        sb.Append(bits[bits.Length - 1].ToString("X"));
    }
    return sb.ToString();
}

/// <summary>
/// Parses a comma seperated list of hexidecimal values and
/// returns an array of integers for those values
/// </summary>
/// <param name="hexString">Comma seperated hex values</param>
/// <returns>integer array</returns>
public int[] HexStringToIntArray(String hexString) {
    string[] hexVals = hexString.Split(new char[] {','});
    int[] retInts = new int[hexVals.Length];
    for (int i = 0; i < hexVals.Length; i++) {
        retInts[i] = Int32.Parse(hexVals[i], System.Globalization.NumberStyles.HexNumber);
    }
    return retInts;
}

答案 6 :(得分:1)

您确定需要使用位字段吗?

根据我的经验,具有一组布尔数据成员的类几乎总是最佳选择。

我曾经听说过使用位字段而不是更大的布尔值(通常是一个字节)的唯一论据是它应该更快。与所有优化一样,在不测量性能的情况下进行此操作是一个坏主意。

一旦将其封装在一个类中,如果您决定更改表示以进行优化,则可以在不需要执行大量其他代码的情况下执行此操作。