在读取和求和位的函数中发现错误

问题描述 投票:0回答:1

我在 VB.Net (.NET Framework 4.8) 中编写了一个函数,该函数从给定位置开始从字节数组中读取特定位数并对结果求和。示例:

NextBits({255, 255, 255}, 0, 24)
返回 765,因为 255 + 255 + 255。

如果读取了一些位并且这些位跨越了字节边界,则返回一个新字节,并将指定位作为最高有效位。示例:

NextBits({0, 15, 200}, 12, 5)
返回 248,因为从 0000 0000 0000 1111 1000 0000 读取了 5 位,这些变为 1111 1000 (248)。

我最初从用 C 编写的 Stackoverflow answer 复制了该函数。在该版本中,结果数组是通过引用函数传递的 - 但是,我需要结果字节的总和。该功能似乎有几个bug,所以我做了两点改进:

  1. 如果 startposition + numBits 仍在单个字节内,我将调用另一个函数。这可行,但我觉得它不太优雅。
  2. VB.net 中的中间结果与 C 中的不同,因此我添加了 Math.Min()。在 C 中,
    tmp |= tmp2 >> (8 - offset);
    在某些情况下会导致 0,但在 VB.net 中它是 1,所以我添加了 Math.Min()。 (C 的行为不同,我必须做出一些调整)。

现在的问题是我昨天发现了一个bug:在测试用例中,

NextBits({48, 2, 250}, 0, 24)
,它应该返回300,因为所有3个字节都被读取了(48 + 2 + 250),但结果是254。这是因为Math我添加的 .Min() 导致了问题。之前的所有测试用例都工作正常。

编辑 10 月 8 日:我刚刚确定问题实际上在

If CUInt(tmp) << CInt(8UI - CUInt(offset)) > 255UI Then
行中。在最后一个测试用例中,代码进入 If 块 3 次,而在其他测试用例中,代码进入
Else
一次,然后进入
If
块两次。

我做了一个测试项目:

Option Strict On
Module Module1

    Sub Main()
        ' 1111 1111  1111 1111  1111 1111
        '--------------------------------
        ' 255           255         255

        Dim nextBitsResult1 As UInteger = NextBits({255, 255, 255}, 0, 23)
        Debug.WriteLine("NextBits 764 = " & nextBitsResult1.ToString())


        ' 0000 0000  0000 0001  0000 0010
        '--------------------------------
        '         0          1          2

        Dim nextBitsResult2 As UInteger = NextBits({0, 1, 2}, 0, 24)
        Debug.WriteLine("NextBits 3 = " & nextBitsResult2.ToString())


        ' 0000 0000  0000 1111  1100 1000
        '                 ____  _
        '--------------------------------
        '         0         15        200

        Dim nextBitsResult3 As UInteger = NextBits({0, 15, 200}, 12, 5)
        Debug.WriteLine("NextBits 248 = " & nextBitsResult3.ToString())


        ' 0000 0000  0001 1111  0000 0000
        '               _ ____
        '--------------------------------
        '         0         31          0

        'Dim nextBitsResult4 As UInteger = NextBits({0, 31, 0}, 11, 5)
        'Debug.WriteLine("NextBits 31 = " & nextBitsResult4.ToString())


        ' 0011 0000  0000 0010  1111 1010
        '--------------------------------
        '        48          2        250

        Dim nextBitsResult5 As UInteger = NextBits({48, 2, 250}, 0, 24)
        Debug.WriteLine("NextBits 300 = " & nextBitsResult5.ToString())
    End Sub

    ''' <summary>
    ''' Reads the next <b>n</b> bits from a byte array starting at a specified bit position and sums their values.<br></br>
    ''' If n bits are to be read across bytes, a new byte is returned, such that the bits of the last byte are considered the leftmost
    ''' (most significant) bits of the result byte.<br></br>
    ''' Serves as a look ahead.
    ''' </summary>
    ''' <param name="byteArray"></param>
    ''' <param name="startPosition">The start position (0-based) in bits from which to begin reading.</param>
    ''' <param name="numBits">The number of bits to read from the byte array.</param>
    ''' <param name="dest">To specify a position in a temporary output array. This parameter is not relevant for end users.</param>
    ''' <returns></returns>
    Friend Function NextBits(byteArray() As Byte, startPosition As Integer, numBits As Integer, Optional dest As Integer = 0) As UInt32
        ' https://stackoverflow.com/a/50899946

        If IsWithinOneByte(startPosition, numBits) Then
            Dim idx As Integer = startPosition \ 8
            'Return AnotherFunction(byteArray(idx), startPosition, numBits)
        End If

        Dim bitmask As Integer = -128
        Dim len As Integer = numBits
        Dim b As Byte() = New Byte(byteArray.Length - 1) {}

        While len > 0
            Dim idx As Integer = startPosition \ 8
            Dim offset As Integer = startPosition Mod 8
            Dim tmp As Byte = byteArray(idx) << offset
            Dim next_bits As Integer = offset + len - 8

            If len > 8 Then
                next_bits += 1
            End If

            If next_bits < 0 Then
                ' Don't even need all of the current byte -> remove trailing bits
                tmp = CByte(tmp And (bitmask >> (len - 1)))
            ElseIf next_bits > 0 Then
                ' Need to include part of next byte
                Dim tmp2 As Byte = CByte(byteArray(idx + 1) And (bitmask >> (next_bits - 1)))
                If offset <> 0 Then
                    tmp = tmp Or (tmp2 >> (8 - offset))
                Else
                    tmp = Math.Min(tmp, tmp2 >> (8 - offset))
                End If
            End If

            ' Determine byte index and offset in output byte array
            idx = dest \ 8
            offset = dest Mod 8
            b(idx) = b(idx) Or tmp << (8 - offset)
            If CUInt(tmp) << CInt(8UI - CUInt(offset)) > 255UI Then ' In the original C code, this check is redundant because if the value exceeds 255, the assignment b(idx + 1) = 0 effectively handles the overflow. (In C, there are no exceptions)
                If idx + 1 < b.Length Then ' The original C code did write an array index too far.
                    b(idx + 1) = 0
                End If
            Else
                If idx + 1 < b.Length Then
                    b(idx + 1) = b(idx + 1) Or (tmp << (8 - offset))
                End If
            End If

            ' Update start position and length for next pass
            If len > 8 Then
                len -= 8
                dest += 8
                startPosition += 8
            Else
                len -= len
            End If

        End While

        Dim ret As UInt32 = CUInt(b.Sum(Function(x As UInt32) x))
        Return ret
    End Function

    Private Function IsWithinOneByte(startPosition As Integer, numBits As Integer) As Boolean
        Dim startByte As Integer = startPosition \ 8
        Dim endByte As Integer = (startPosition + numBits - 1) \ 8
        Return startByte = endByte
    End Function
End Module

输出:

下一个比特 764 = 764
下一个比特 3 = 3
下一个比特 248 = 248
下一个比特 300 = 254

vb.net bit-manipulation
1个回答
0
投票
Function NextBits(byteArray() As Byte, startPosition As Integer, numBits As Integer)

    Dim s_offs = startPosition \ 8
    Dim s_mod = startPosition Mod 8

    Dim endPosition = startPosition + numBits
    Dim e_offs = endPosition \ 8
    Dim e_mod = endPosition Mod 8

    Dim sum = 0

    If s_mod = 0 Then
        sum = byteArray.Skip(s_offs).Take(e_offs - s_offs).Sum(Function(b) b)
        If e_mod <> 0 Then
            sum += byteArray(e_offs) And (&HFF << (8 - e_mod)) And &HFF
        End If
    ElseIf s_mod >= e_mod Then
        sum = Enumerable.Range(s_offs, e_offs - s_offs - 1).Sum(Function(i) _
            ((byteArray(i) << s_mod) Or (byteArray(i + 1) >> (8 - s_mod))) And &HFF)
        sum += ((byteArray(e_offs - 1) << s_mod) Or (byteArray(e_offs) >> (8 - s_mod))) _
            And (&HFF << (s_mod - e_mod)) And &HFF
    Else
        sum = Enumerable.Range(s_offs, e_offs - s_offs).Sum(Function(i) _
            ((byteArray(i) << s_mod) Or (byteArray(i + 1) >> (8 - s_mod))) And &HFF)
        sum += byteArray(e_offs) And (&HFF << (8 - (e_mod - s_mod))) And &HFF
    End If

    Return sum

End Function
© www.soinside.com 2019 - 2024. All rights reserved.