【问题标题】:Count array elements where there are significant 0 bits (below the highest 1 bit)计算有有效 0 位(最高 1 位以下)的数组元素
【发布时间】:2020-04-27 18:00:31
【问题描述】:

指定了一个 3 字节的数组。计算任何一个后有一个零的字节数。即最重要的1 下方的位并非都是1

{00000100, 00000011, 00001000} - 对于这个数组,答案是 2。

我的代码给出了 1,但它不正确;如何解决?

#include <iostream>
#include <bitset>
using namespace std;

int main() {
    int res = 0, res1 = 0;
    _int8 arr[3] = { 4, 3, 8 };
__asm {
    mov ecx, 3 
    mov esi, 0
    start_outer:
        mov bx, 8 
        mov al, arr[esi]
    start_inner :
        shl al, 1 
        jnb zero 
        jc one 
    one :
        dec bx к
        test bx, bx
        jnz start_inner 
        jmp end_ 
    zero :
        dec bx 
        test bx, bx
        jz end_
        inc res 
        shl al, 1 
        jnb was_zero 
        jc start_inner 
    was_zero :
        dec bx 
        dec res 
        jmp start_inner 
    end_ :
        inc esi
        loop start_outer
}
cout << res << endl;
system("pause");
}

【问题讨论】:

  • "计算一个后面的零的个数" 哪个1?为什么预期的答案是 2?
  • 答案是 2,因为在数组 (00000100, 00000011, 00001000) 中,第一项 00000100 在一个后至少有一个零,00000011 在一个后有 0 个零,00001000 有, 再一次, 至少在一个 1 之后有一个 0。
  • @zx485 这是一种可能性,而且是有道理的,但它并不能真正与问题中的描述相提并论。
  • @Michael 是的,这就是你的想法不正确的原因。我需要计算 1 之后的所有零。zx485 解释得很好,程序应该如何计算。
  • 根据附加信息,您想从计数中排除的值似乎是 (2^N)-1 表单上的所有值,其中 N=0..8。您应该能够相当有效地检查这一点(例如if ((value &amp; (value + 1)) == 0))。

标签: c++ assembly visual-c++ x86 masm


【解决方案1】:

下次尝试。

请下次尝试解释得更好。很多人不明白你的问题。反正。我希望我现在明白了。

我将解释一个字节所使用的算法。稍后在程序中,我们将运行简单的外循环 3 次,以处理所有值。而且,我当然会在汇编程序中显示结果。而且,这是许多可能的解决方案之一。

我们可以观察到以下几点: 您的声明 “计算任何一个后有一个零的字节数。” 意味着您要计算一个字节中一位从 1 到 0 的转换次数。而这一点,如果我们查看从 msb 到 lsb 的位。所以,从左到右。

如果我们反之亦然,那么如果我们从右到左,我们也可以计算从 0 到 1 的转换次数。

从 0 到 1 的转换总是可以通过将新值与否定的旧值“与”来计算。示例:

OldValue NewValue NotOldValue And
       0 0        1           0
       0 1        1           1   --> Rising edge
       1 0        0           0
       1 1        0           0

我们也可以用文字说,如果旧的、先前的值没有被设置,而新的值被设置了,那么我们有一个上升沿。

如果我们向右移动字节,我们可以查看一个位(一个字节)。然后,新的值(新的最低位)将是 LSB。我们记得旧的前一点,并进行测试。然后我们设置 old = new,再次读取新值,进行测试等等。我们对所有位都这样做。

在 C++ 中,这可能如下所示:

#include <iostream>
#include <bitset>

using byte = unsigned char;

byte countForByte(byte b) {
    // Initialize counter variable to 0
    byte counter{};

    // Get the first old value. The lowest bit of the orignal array entry
    byte oldValue = b & 1;

    // Check all 8 bits
    for (int i=0; i<8; ++i) {

        // Calculate a new value. First shift to right
        b = b >> 1;

        // Then mask out lowest bit
        byte newValue = b & 1;

        // Now apply our algorithm. The result will always be 0 or one. Add to result
        counter += (newValue & !oldValue);

        // The next old value is the current value from this time
        oldValue = newValue;
    } 
    return counter;
}

int main() {
    unsigned int x;
    std::cin >> x;
    std::cout << std::bitset<8>(x).to_string() << "\n";
    byte s = countForByte(x);
    std::cout << static_cast<int>(s) << '\n';
    return 0;
}

因此,无论出于何种原因,您都需要汇编程序中的解决方案。同样在这里,您需要告诉人们您为什么想要它,您使用什么编译器以及您使用什么目标微处理器。否则,人们怎么能给出正确答案呢?

无论如何。这里是 X86 架构的解决方案。通过 MS VS2019 测试。

#include <iostream>

int main() {
    int res = 0;
    unsigned char arr[3] = { 139, 139, 139 };

    __asm {
            mov esi, 0;         index in array
            mov ecx, 3;         We will work with 3 array values
DoArray:
            mov ah, arr[esi];   Load array value at index
            mov bl, ah;         Old Value
            and bl, 1;          Get lowest bit of old value

            push ecx;           Save loop Counter for outer loop
            mov ecx, 7;         7Loop runs to get the result for one byte
DoTest:
            shr ah, 1;          This was the original given byte
            mov al, ah;         Get the lowest bit from the new shifted value
            and al, 1;          This is now new value
            not bl;             Invert the old value
            and bl, al;         Check for rising edge
            movzx edi, bl
            add res, edi;       Calculate new result
            mov bl, al;         Old value = new value
            loop DoTest

            inc esi;            Next index in array
            pop ecx;            Get outer loop counter

            loop DoArray;       Outer loop
    }
    std::cout << res << '\n';
    return 0;
}

对于这项工作,我想要 100 次投票和一个接受的答案。 . .

【讨论】:

    【解决方案2】:

    基本上,用户@Michael 已经给出了正确答案。所以所有的功劳都归他所有。

    你可以在这里找到很多关于堆栈溢出的帖子。但是对于这类活动的一个很好的描述,你可以在“Henry S. Warren, Jr.”的“Hacker's Delight”一书中找到。。我这里有第二版。

    解决方案在第 2 章,“基础”中介绍,然后是“2-1 操作最右位”

    如果您手动检查,哪些值 NOT 满足您的条件,那么您会发现这些是

    0,1,3,7,15,31,63,127,255,

    或者,二进制

    0b0000'0000, 0b0000'0001, 0b0000'0011, 0b0000'0111, 0b0000'1111, 0b0001'1111, 0b0011'1111, 0b0111'1111, 0b1111'1111,

    并且我们检测到这些值对应于 2^n - 1。并且,根据“Hacker's Delight”,我们可以通过简单的公式找到

    (x & (x + 1)) != 0
    

    所以,我们可以把它翻译成下面的代码:

    #include <iostream>
    
    int main() {
        unsigned char arr[3];
    
        unsigned int x, y, z;
        std::cin >> x >> y >> z;
    
        arr[0] = static_cast<unsigned char>(x);
        arr[1] = static_cast<unsigned char>(y);
        arr[2] = static_cast<unsigned char>(z);
    
        unsigned char res = ((arr[0] & (arr[0] + 1)) != 0) +  ((arr[1] & (arr[1] + 1)) != 0) + ((arr[2] & (arr[2] + 1)) != 0);
    
        std::cout << static_cast<unsigned int>(res) << '\n';
        return 0;
    }
    

    非常重要。您不需要汇编代码。优化编译器几乎总是会胜过您的手写代码。

    您可以在Compiler Explorer 上查看许多不同的版本。在这里您可以看到,您的带有静态值的代码示例将被完全优化掉。编译器会在编译时简单地计算一切,并简单地显示 2 作为结果。所以,警告。编译器资源管理器将向您展示不同编译器和所选硬件生成的汇编语言。如果你愿意,你可以拿走。

    另外请注意:上面的草图算法不需要任何分支。除非你想迭代一个数组/向量。为此,您可以编写一个小的 lambda 并使用 C++ 标准库中的算法。

    C++解决方案

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <numeric>
    #include <iterator>
    
    int main() {
    
        // Define Lambda to check conditions
        auto add = [](const size_t& sum, const unsigned char& x) -> size_t {
            return sum + static_cast<size_t>(((x & (x + 1)) == 0) ? 0U : 1U); };
    
        // Vector with any number of test values
        std::vector<unsigned char> test{ 4, 3, 8 };
    
        // Calculate and show result
        std::cout << std::accumulate(test.begin(), test.end(), 0U, add) << '\n';
    
        return 0;
    }
    

    【讨论】:

    • 如果您要链接godbolt.org,请在此处链接到您的代码。这就是为什么它有永久链接按钮来制作短链接或完整链接。 (最好使用完整链接以避免链接失效。)
    • 问题是您计算“正确”数字的数量(满足此条件 (x & (x + 1)) != 0),但我需要计算 1 之后的所有零. 例如,如果我有一个数字 139(二进制为 10001011),我将其计为 2(因为我们在 1 之后有两个零)。然后我再次(例如)第二个数字 139 和第三个数字 139。总答案是 6。“就像将整个数组视为单个 24 位值并计算零的总数”,实际上我需要编码在汇编中(不在 C++ 中)
    • @EmilyFitz:您在 cmets 中说 zx485 正确解释了如何获得结果!现在你说的毕竟不是真的。请用这个新的更好的解释来编辑您的问题,您需要的算法到底是什么。显然我对问题的编辑(基于您的 cmets)毕竟不正确,问题中的原始措辞毕竟更接近您想要的,但似乎并不完全匹配。
    猜你喜欢
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-07
    • 2015-03-25
    • 1970-01-01
    相关资源
    最近更新 更多