Search code examples
c++

Issue when calling std::bitset<N>::test


I have created a simple function to add two integral numbers represented with std::bitset<8>. Inside the method I have a result bitset that stores the result of the operation. When calling the std::bitset<N>::test method on the result object that is returned from the function it does not work as expect. It either returns false every time or it does not work entirely as when I am using debugger watch list I get class "std::bitset<8>" has no member "test".

The following code does not work as expected. I cannot call method test on the object r. Although there is no compile time nor runtime error. The result is wrong though as if r.test always returned false.

std::bitset<8> add(const std::bitset<8>& a, const std::bitset<8>& b)
{
    std::bitset<8> r;

    for (size_t i = 0; i < a.size(); i++)
    {
        r[i] = a.test(i) ^ b.test(i) ^ r.test(i);

        if (i < a.size() - 1)
        {
            r[i + 1] = (a.test(i) && b.test(i)) || (r.test(i) && a.test(i)) || (r.test(i) && b.test(i));
        }
    }

    return r;
}

When I make a copy of the result bitset in each loop step it does work as expected. I can call the method test on the object copy.

std::bitset<8> add(const std::bitset<8>& a, const std::bitset<8>& b)
{
    std::bitset<8> r;

    for (size_t i = 0; i < a.size(); i++)
    {
        const auto copy = r;
        r[i] = a.test(i) ^ b.test(i) ^ copy.test(i);

        if (i < a.size() - 1)
        {
            r[i + 1] = (a.test(i) && b.test(i)) || (copy.test(i) && a.test(i)) || (copy.test(i) && b.test(i));
        }
    }

    return r;
}

The code was written on Windows with latest version of Visual Studio Community edition, language standard set to Preview - Features from the Latest C++ Working Draft (/std:c++latest) and built ISO C++23 Standard Library modules.

Here is full snippet of the code that does not work as expected, or at least it does not work as I expected it to work:

#include <bit>
#include <bitset>
#include <iostream>

std::bitset<8> to_bitset(std::int8_t value)
{
    return { static_cast<std::uint8_t>(value) };
}

std::int8_t to_int(const std::bitset<8>& bitset)
{
    return (std::int8_t)bitset.to_ulong();
}

std::bitset<8> add_bad_result(const std::bitset<8>& a, const std::bitset<8>& b)
{
    std::bitset<8> r;

    for (size_t i = 0; i < a.size(); i++)
    {
        r[i] = a.test(i) ^ b.test(i) ^ r.test(i);

        if (i < a.size() - 1)
        {
            r[i + 1] = (a.test(i) && b.test(i)) || (r.test(i) && a.test(i)) || (r.test(i) && b.test(i));
        }
    }

    return r;
}

std::bitset<8> add_good_result(const std::bitset<8>& a, const std::bitset<8>& b)
{
    std::bitset<8> r;

    for (size_t i = 0; i < a.size(); i++)
    {
        const auto copy = r;
        r[i] = a.test(i) ^ b.test(i) ^ copy.test(i);

        if (i < a.size() - 1)
        {
            r[i + 1] = (a.test(i) && b.test(i)) || (copy.test(i) && a.test(i)) || (copy.test(i) && b.test(i));
        }
    }

    return r;
}

int main()
{
    auto a = 7;
    auto b = 1;

    // bad result
    std::cout << add_bad_result(to_bitset(a), to_bitset(b)) << std::endl;
    // output: 00001100

    // good result
    std::cout << add_good_result(to_bitset(a), to_bitset(b)) << std::endl;
    // output: 00001000

    return 0;
}

Solution

  • The bitset works fine. Your logic in the "bad" function doesn't work because the r[i + 1] assignment tests bit r[i] which has already been set to the new value but the check needs to be done with the original boolean value for r[i]. You have to write your logic so that either all assignments happen after all tests or all tested values are retrieved first.

    std::bitset<8> add_1(std::bitset<8> a, std::bitset<8> b)
    {
        std::bitset<8> r;
        for (size_t i = 0; i < a.size(); i++)
        {
            bool add = a[i] ^ b[i] ^ r[i];
            bool carry_out = (a[i] && b[i]) || (r[i] && a[i]) || (r[i] && b[i]);
            r[i] = add;
            if(i + 1 < a.size())
                r[i + 1] = carry_out;
        }
        return r;
    }
    std::bitset<8> add_2(std::bitset<8> a, std::bitset<8> b)
    {
        std::bitset<8> r;
        for (size_t i = 0; i < a.size(); i++)
        {
            bool carry_in = r[i];
            r[i] = a[i] ^ b[i] ^ carry_in;
            if(i + 1 < a.size())
                r[i + 1] = (a[i] && b[i]) || (carry_in && a[i]) || (carry_in && b[i]);
        }
        return r;
    }
    

    Side-notes:

    1. The carry logic can be written as
      carry_out = (a[i] && b[i]) || (carry_in && (a[i] ^ b[i]))
    2. There is no point in passing a bitset<8> as a const reference. Copy will be faster for bitsets up to 64, probably 128 values