如何在 C# 中从字节 [] 中读取具有偏移量和位大小的 int

How to read an int from a byte[] with an offset and a size of bits in C#

我需要一个将偏移量和位大小作为参数的函数,以从字节数组中读取 int 值。

int GetInt(byte[] data, int bitOffset, int bitSize)

例如,我有以下字节数组:

66 DC 00 00 6A DC 00 00
66 DC 00 00 58 DC 00 00
54 DC 00 00 50 DC 00 00
4C DC 00 00 00 00 00 00
00 00 00 00 00 00 00 08
F0 FF FF 9F F4 7F 20 9A
91 EB 85 88 3F 6E 00 80
3D 6E 00 80 3B 6E 00 00

位相同:

01100110  00111011  00000000  00000000  01010110  00111011  00000000  00000000
01100110  00111011  00000000  00000000  00011010  00111011  00000000  00000000
00101010  00111011  00000000  00000000  00001010  00111011  00000000  00000000
00110010  00111011  00000000  00000000  00000000  00000000  00000000  00000000
00000000  00000000  00000000  00000000  00000000  00000000  00000000  00010000
00001111  11111111  11111111  11111001  00101111  11111110  00000100  01011001
10001001  11010111  10100001  00010001  11111100  01110110  00000000  00000001
10111100  01110110  00000000  00000001  11011100  01110110  00000000  00000000

如何最有效地确保以下函数具有这些 return 值?:

var a = GetInt(data, 0, 32); // a = 56422
var b = GetInt(data, 313, 11); // b = 4

编辑:此处字节为 C# 数组:

new byte[] { 0x66, 0xDC, 0x00, 0x00, 0x6A, 0xDC, 0x00, 0x00, 0x66, 0xDC, 0x00, 0x00, 0x58, 0xDC, 0x00, 0x00, 0x54, 0xDC, 0x00, 0x00, 0x50, 0xDC, 0x00, 0x00, 0x4C, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xF0, 0xFF, 0xFF, 0x9F, 0xF4, 0x7F, 0x20, 0x9A, 0x91, 0xEB, 0x85, 0x88, 0x3F, 0x6E, 0x00, 0x80, 0x3D, 0x6E, 0x00, 0x80, 0x3B, 0x6E, 0x00, 0x00 }

编辑 2: 我也已经实现了自己的解决方案,我可以用它在此处获取此 post 的所有值。我只是对我的解决方案非常不满意,因为我不想每次都将数组传递给 BitArray。读取一个文件,这个函数调用了几十万次

public static int GetdInt(this byte[] data, int bitOffset, int bitSize)
{
    var bits = new BitArray(data);

    var output = 0;

    for(var bitIndex = 0; bitIndex < bitSize; bitIndex++)
    {
        var bit = bits.Get(bitOffset + bitIndex) ? 1 : 0;
        output |= bit << bitIndex;
    }

    return output;
}

C# 90 字节

    int GetInt(byte[] d,int o,int b)=>Enumerable.Range(o,b).Sum(i=>(d[i/8]>>i%8)%2>0?1<<i-o:0);

我以代码高尔夫的形式写这篇文章是为了取笑你的问题读起来像代码高尔夫,而且你没有表现出你自己的任何尝试;)

这是对未来回答者的单元测试。 (OP 这可能对你也有帮助):

[TestMethod]
public void Test()
{
    var bytes = new byte[]
    {
        0x66, 0xDC, 0x00, 0x00, 0x6A, 0xDC, 0x00, 0x00,
        0x66, 0xDC, 0x00, 0x00, 0x58, 0xDC, 0x00, 0x00,
        0x54, 0xDC, 0x00, 0x00, 0x50, 0xDC, 0x00, 0x00,
        0x4C, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
        0xF0, 0xFF, 0xFF, 0x9F, 0xF4, 0x7F, 0x20, 0x9A,
        0x91, 0xEB, 0x85, 0x88, 0x3F, 0x6E, 0x00, 0x80,
        0x3D, 0x6E, 0x00, 0x80, 0x3B, 0x6E, 0x00, 0x00
    };
    RunTest(0, 32, 56422);
    RunTest(313, 11, 4);

    void RunTest(int offset, int bitSize, int expected)
    {
        var actual = GetInt(bytes, offset, bitSize);
        Assert.AreEqual(actual, expected);
    }
}

编辑:因为你在这里展示了你自己的尝试是一个非 code-golfed 答案:

//first write a function that gets a bit value from the byte[]
bool GetBitFromByteArray(byte[] data, int bitNumber)
{
    //8 bits per byte.
    const int sizeOfByte = 8;
    
    var byteNumber = bitNumber / sizeOfByte;//index within the byte array. Integer division always rounds down
    var bitNumberWithinTheByte = bitNumber % sizeOfByte;//bit index within that byte

    //now write a function that gets a bit value from a byte
    return GetBitFromByte(data[byteNumber], bitNumberWithinTheByte);
}

bool GetBitFromByte(byte byteValue, int bitNumber)
{
    //bit shift so that the bit in question is in the least significant place
    var shifted = byteValue >> bitNumber;

    //mod 2 checks if the least significant bit is 0 or 1
    return shifted % 2 > 0;
}

int GetInt(byte[] data, int offset, int bitCount)
{
    //get bit values in order
    var bitValues = new List<bool>(bitCount);
    for (int i = 0; i < bitCount; i++)
    {
        bitValues.Add(GetBitFromByteArray(data, i + offset));
    }

    //sum up the bit values as powers of 2
    var intValue = 0;
    for (int i = 0; i < bitCount; i++)
    {
        var bitValue = bitValues[i];
        if (bitValue) intValue += 1 << i;//1<<i is equivalent to 2^i
    }

    return intValue;
}

如果您担心性能和数组分配,代码高尔夫答案实际上会更好。

这是一种可能的策略,读取字节 one-by-one 直到读取了足够的位,然后丢弃多余的部分。通过了两个测试用例,但 IMO 测试还不够,再做一些。

static int GetInt(byte[] data, int bitOffset, int bitSize)
{
    // first chunk is special, lower bits are discarded immediately
    // to prevent trying to put more than 32 bits in `result`
    // when `bitSize == 32` and `bitOffset != 0`
    int byteOffset = bitOffset >> 3;
    int result = data[byteOffset] >> (bitOffset & 7);
    int resultOffset = 8 - (bitOffset & 7);
    // the "rest", whole bytes are read from data and put into their place in the result
    while (resultOffset < bitSize)
    {
        byteOffset++;
        result |= data[byteOffset] << resultOffset;
        resultOffset += 8;
    }
    // in general too many bits have been read at this point, discard excess
    return result & (int)(uint.MaxValue >> -bitSize);
}

还有其他可能的策略。例如,使用 BitConverter class(或其他技巧)读取整个 intlong,然后进行一些移位和屏蔽以从中提取目标位范围.这有可能更有效,但有一些不幸的细节需要处理,当目标范围没有穿过数组的末尾,但包含它们的 byte-aligned 整数会。

bitSize = 0 时,最后丢弃多余位的方式无法正常工作,我认为支持它并不有趣,但可以通过将其作为特殊情况处理来轻松完成。与 (1 << bitSize) - 1 不同,我使用的方式确实适用于 32 位。

解决方案使用BitArray and BitConverter

int GetInt(byte[] data, int bitOffset, int bitSize)
{
    var bits = new BitArray(data).RightShift(bitOffset);
    bits.Length = bitSize;
    var bytes = new byte[4];
    bits.CopyTo(bytes, 0);
    return BitConverter.ToInt32(bytes);
}