如您所知,我一直在使用格雷代码,并试图在现代C ++中设计一个好的gray_code类。我最终将发布实现的几个部分,但是我想回顾的主要是附加功能。无论如何,这是类要点的代码:如您所见,gray_code<T>只是无符号内置类型T的包装。主要操作是T类型的构造和转换;借助公共变量value可以访问基础值。此类的目的是易于使用。多亏了转换运算符,无论何时可以使用相应的整数,都可以使用gray_code。我的目标之一是仅在gray_code运算比将格雷码转换为相应的整数,对整数执行运算并将整数转换回格雷码的速度更快时提供操作。转换确实非常快,因此比双转换快的操作很难编写。我确实写了其中的一些,但未包括在其中(比较,按位运算,转换为bool,递增和递减;您可以假定其余函数在gray_code s之间都可以正常工作),而我目前正在努力进行加法运算。

我的加法算法基于RW Doran(我是唯一想到Dorian Gray的人)在本文中提供的伪代码算法,并归因于Harold Lucal。这是原始的伪代码加法算法(表示xor):

template<typename Unsigned>
struct gray_code
{
    static_assert(std::is_unsigned<Unsigned>::value,
                  "gray code only supports built-in unsigned integers");

    // Underlying unsigned integer
    using value_type = Unsigned;

    // Variable containing the gray code
    value_type value;

    ////////////////////////////////////////////////////////////
    // Constructors operations

    // Default constructor
    constexpr gray_code() noexcept:
        value(0u)
    {}

    /**
     * @brief Construction from an unsigned integer.
     *
     * The integer is converted to gray code. The value
     * is preserved. The representation is not.
     *
     * @param value Unsigned integer to convert
     */
    constexpr explicit gray_code(value_type value) noexcept:
        value( (value >> 1) ^ value )
    {}

    ////////////////////////////////////////////////////////////
    // Assignment operations

    /**
     * @brief Assigns an unsigned integer.
     *
     * It works the same way as the equivalent
     * constructor does.
     */
    auto operator=(value_type other) & noexcept
        -> gray_code&
    {
        value = (other >> 1) ^ other;
        return *this;
    }

    ////////////////////////////////////////////////////////////
    // Conversion operations

    /**
     * @brief Conversion to the underlying type.
     *
     * See http://www.dspguru.com/dsp/tricks/gray-code-conversion
     */
    operator value_type() const noexcept
    {
        value_type res = value;
        for (value_type mask = std::numeric_limits<value_type>::digits / 2
             ; mask ; mask >>= 1)
        {
            res ^= res >> mask;
        }
        return res;
    }
};


我将算法翻译成C ++。这是朴素的实现(您可以在此处找到相应的C版本):

procedure add (n: integer; A,B:word; PA,PB:bit;
               var S:word; var PS:bit; var CE, CF:bit);
var i: integer; E, F, T: bit;
begin
   E := PA; F := PB;
   for i:= 0 to n-1 do begin {in parallel, using previous inputs}
       S[i] := (E and F) ⊕ A[i] ⊕ B[i];
       E := (E and (not F)) ⊕ A[i];
       F := ((not E) and F) ⊕ B[i];
   end;
   CE := E; CF := F;
end;


该算法有效。但是,它不如将格雷码转换为整数,执行常规加法并将结果转换回格雷码那样有效。


如果需要,这里是is_odd的实现,尽管它不会增加太多的开销,因为它只执行一次。格雷码的奇偶校验对应于设置的位数的奇偶校验。该函数在可能的情况下使用编译器内部函数(在某些体系结构上可能像在奇偶校验位上那样简单)和\ $ O(\ logn)\ $算法对位数进行计数。

template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
    -> gray_code<Unsigned>
{
    // parity of lhs and rhs
    bool lhs_p = is_odd(lhs);
    bool rhs_p = is_odd(rhs);

    gray_code<Unsigned> res{};
    for (Unsigned i{} ; i < std::numeric_limits<Unsigned>::digits ; ++i)
    {
        // Get the ith bit of rhs and lhs
        bool lhs_i = (lhs.value >> i) & 1u;
        bool rhs_i = (rhs.value >> i) & 1u;

        // Copy lhs_p and rhs_p (see {in parallel} in the original algorithm)
        bool lhs_p_cpy = lhs_p;
        bool rhs_p_cpy = rhs_p;

        // Set the ith bit of res
        Unsigned res_i = (lhs_p_cpy & rhs_p_cpy) ^ lhs_i ^ rhs_i;
        res |= (res_i << i);

        // Update e and f
        lhs_p = (lhs_p_cpy & (!rhs_p_cpy)) ^ lhs_i;
        rhs_p = ((!lhs_p_cpy) & rhs_p_cpy) ^ rhs_i;
    }
    return res;
}


注意:如果您发现此问与答很有趣,并且在另一种添加格雷码的算法中很有趣,那么我将对自定义算法进行另一次问答,该算法基于\ $ 2 \ $分解的幂来添加格雷码。 br />

评论

可能会遵循以下地址(格雷码基础和格雷码):扩展,应用和算术:底线是,可以进行超前进位的格雷[代码]加法器,但要付出可观的代价,并且无论采用哪种方式查看它,也是一个很大的性能损失。不是我们想要结尾的音符。

#1 楼

循环不变代码运动

这是大多数编译器执行的优化的名称:当他们检测到实际上不依赖于循环状态的代码时,便将其移出循环。它不像通常的不变式那样明显,但让我们看一下这行:

Unsigned res_i = (lhs_p_cpy & rhs_p_cpy) ^ lhs_i ^ rhs_i;


lhs_irhs_i对应于\ $ i_ {th} \ $位lhsrhs的这些位与循环无关(循环不会更改它们),并且可以在运行循环之前通过执行lhs ^ rhs一次计算所有这些位。因此,我们可以通过将其移出循环来简化功能。如果我们将它们直接存储在res中,我们甚至可以将对res的分配简化为一个简单的res ^= res_i << i:不幸的是,lhs_irhs_i也被用来计算新的lhs_prhs_p的值,因此我们无论如何都无法将它们从循环主体中删除。如果我们要进一步优化算法,我们还需要其他东西,例如...

更有效的奇偶校验算法

由于仅使用is_odd,因此几乎没有任何改变在算法的最开始,但是当无法使用__builtin_parity时,其后备实现不是最佳的。实际上,无符号整数的奇偶校验可以通过对每个位进行xor运算而获得,而不必计算设置的位数。整个xor的位运算可以使用以下算法“并行”完成,而不是一点一点地进行:

template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
    -> gray_code<Unsigned>
{
    // parity of lhs and rhs
    bool lhs_p = is_odd(lhs);
    bool rhs_p = is_odd(rhs);

    gray_code<Unsigned> res = lhs ^ rhs;
    for (Unsigned i{} ; i < std::numeric_limits<Unsigned>::digits ; ++i)
    {
        // Get the ith bit of rhs and lhs
        bool lhs_i = (lhs.value >> i) & 1u;
        bool rhs_i = (rhs.value >> i) & 1u;

        // Copy lhs_p and rhs_p (see {in parallel} in the original algorithm)
        bool lhs_p_cpy = lhs_p;
        bool rhs_p_cpy = rhs_p;

        // Set the ith bit of res
        Unsigned res_i = lhs_p_cpy & rhs_p_cpy;
        res ^= res_i << i;

        // Update e and f
        lhs_p = (lhs_p_cpy & (!rhs_p_cpy)) ^ lhs_i;
        rhs_p = ((!lhs_p_cpy) & rhs_p_cpy) ^ rhs_i;
    }
    return res;
}


该算法将xor的无符号整数的前半部分与后半部分相乘,然后对结果重复操作...,直到xor仅剩两位为止,从而导致比填充计数更有效的操作。该页面甚至提供了算法的更微妙的实现,以“跳过”最后的迭代。我并没有真正使用魔术数字来研究建议的实现,因为我希望我的代码仍然可以使用任何大小的整数。

摆脱临时成员

可能最慢的灰色加法是for循环的主体。为了找到可能的优化方法,我决定进一步去除lhs_p_cpyrhs_p_cpy,它们是计算lhs_p的新值所需的rhs_prhs_p的副本。通过简单地用rhs_p_cpy替换它很容易摆脱rhs_p,但是摆脱lhs_p_cpy却比较困难,因为lhs_p已经同时更新了。毫不费力地,下面的循环体是我们可以获得的最好的循环体:

for (std::size_t i = std::numeric_limits<Unsigned>::digits / 2u ; i ; i >>= 1u)
{
    code.value ^= (code.value >> i);
}
return bool(code.value & 1u);


因此,我决定构建一些真值表。在下表中,\ $ lhs_ {old} \ $和\ $ rhs_ {old} \ $是更新前lhs_prhs_p的可能值,而\ $ lhs_ {new} \ $和\ $ rhs_ {new} \ $是更新后相同变量的值。我对lhs_irhs_i没有任何兴趣,因为它们都不用于计算\ $ lhs_ {new} \ $和\ $ rhs_ {new} \ $;因此,在剩下的反射中,我将忽略它们。

\ begin {array} {| cc | cc |}
\ hline
lhs_ {old}和rhs_ { old}&lhs_ {new}&rhs_ {new} \\
\ hline
0&0&0&0 \\
0&1&0&1 \\
1&0&1&0 \\
1&1&0&0 \\
\ hline
\ end {array}

我们可以在\ $ lhs_ {new} \ $的计算中轻松地用\ $ lhs_ {old} \ $替换lhs_p_cpy,但是由于更新已经发生,因此我们不能用它来计算\ $ rhs_ {new} \ $。我最初想摆脱lhs_p_cpy的想法是“我们可以仅用\ $ rhs_ {old} \ $和\ $ lhs_ {new} \ $来计算\ $ rhs_ {new} \ $吗?”。查看上面的真值表,似乎不可能。尝试在\ $ lhs_ {new} \ $之前计算\ $ rhs_ {new} \ $也不能解决问题。

但是,我们已经在循环中计算了另一个值:res_i。因此,我在表中注入了\ $ res_i \ $,并研究了如何处理它:

\ begin {array} {| cc | c | cc |}
\ hline
lhs_ {old}和rhs_ {old}&res_i = lhs_ {old} \ land rhs_ {old}和lhs_ {new}&rhs_ {new} \\
\ hline
0& 0&0&0&0 \\
0&1&0&0&1 \\
1&0&0&1&0 \\
1&1&1&0 &0 \\
\ hline
\ end {array}

我们可以从此“真值表”中推断出\ $ rhs_ {old} \ land \ lnot lhs_ {old } = rhs_ {old} \ land \ lnot res_i \ $。如果我们将新发现重新注入代码中,则可以使用它完全摆脱lhs_p_cpy

Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;

bool lhs_p_cpy = lhs_p;
bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
lhs_p = (lhs_p_cpy & not rhs_p) ^ lhs_i;
rhs_p = (rhs_p & not lhs_p_cpy) ^ rhs_i;


通过这种方式,res_i也可以用于计算\ $ lhs_ {new} \ $,以使代码看起来更加对称(即使不会提高性能):

Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;

bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
lhs_p = (lhs_p & not rhs_p) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;


启用所有优化后,此代码虽然比以前的版本要快一点,但是仍然远不如将格雷码转换回常规整数,执行整数加法并将结果转换回格雷码的版本那样高效。嘿,如果不以各种可能的方式扭曲它,我们就无法优化这种算法!这就是为什么,我们还将尝试.​​..

找到更好的最终条件

当前,该算法在结束之前循环遍历res中的每个位。可能可以更快地结束循环。让我们看一下一个较大的旧真值表,该表将用于其余的答案。为了简洁起见,我使用原始伪代码中的名称来命名列。我希望与我编写的代码的相似之处不要太难(\ $ A = lhs_i \ $,\ $ B = rhs_i \ $,\ $ E_ {old} = lhs_p \ $,\ $ F_ {old } = rhs_p \ $,\ $ S = A \ oplus B \ oplus res_i \ $,\ $ E_ {new} = lhs_p'\ $,\ $ F_ {new} = rhs_p'\ $)

\ begin {array} {| cccc | cc | cc |}
\ hline
A&B和E_ {old}和F_ {old}和A \ oplus B&S和E_ {new }&F_ {new} \\
\ hline
0&0&0&0&0&0&0&0 \\
0&0&0&1&0&0 &0&1 \\
0&0&1&0&0&0&1&0 \\
0&0&1&1&1&0&1&0&0 \\
0&1&0&0&1&1&0&1 \\
0&1&0&1&1&1&0&0 \\
0&1&1& 0&1&1&1&1 \\
0&1&1&1&1&0&0&1 \\
\ hline
1&0&0&0& 1&1&1&0 \\
1&0&0&1&1&1&1&1 \\
1&0&1&0&1&1&0&0 \ \
1&0&1&1&1&0&1&0 \\
1&1&0&0&0&0&1&1 \\
1&1 &0&1&0&0&1&0 \\
1&1&1&0&0&0&0&0&1 \\
1&1&1&1&0&1 &1&1 \\
\ hline
\ end {array}

查看该表的第一行,我们可以看到,当\ $ A = B = E_ {old} = F_ {old} = 0 \ $时,我们得到\ $ S = E_ {new} = F_ {new} = 0 \ $,这意味着由于我们的结果从一开始就用\ $ A \ oplus B \ $填充,因此当满足上述条件时,我们可以停止循环(所有值等于\ $ 0 \ $ ),因为随后的位S始终为\ $ 0 \ $。一种简单的方法是在代码中右移“消耗” lhsrhs,以便它们最终达到零,并通过按位OR改变循环条件(在这里,为简单起见,仅for循环): br />
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;

bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;


虽然这可以降低算法的复杂性并极大地改善较小值的情况,但对于循环条件,lhs | rhs | lhs_p | rhs_p本身仍然是一堆操作。我们的目标是在所有这些变量均为\ $ 0 \ $时停止循环。我们可以做的一件事是在循环之前找到lhsrhs之间的位具有最高的置位位和循环,直到该位被消耗为止。由于它们都以相同的速度被“消耗”,当消耗了设置位最高的一个时,则意味着另外一个已被消耗,因此我们只需要检查两者中最大的是否为\ $ 0即可。 \ $。通过简单地检查哪个具有最大的价值,我们可以轻松找到哪个具有最高的价值。这是经过修改的算法:

for (Unsigned i{} ;
     lhs | rhs | lhs_p | rhs_p ;
     ++i, lhs >>= 1u, rhs >>= 1u)
{
    Unsigned res_i = lhs_p & rhs_p;
    res ^= res_i << i;

    bool lhs_i = lhs.value & 1u;
    bool rhs_i = rhs.value & 1u;
    lhs_p = (lhs_p & not res_i) ^ lhs_i;
    rhs_p = (rhs_p & not res_i) ^ rhs_i;
}


更好地了解真值表

从我们构建的大真值表中,我们可以看到,当\ $ A = B = 0 \ $时,\ $ S = 1 \ $的唯一时间是\ $ E_ {old} = F_ {old} = 1 \ $。但是,当\ $ A = B = 0 \ $时,我们还可以看到\ $ E_ {new} \ $和\ $ F_ {new} \ $永远不能同时为\ $ 1 \ $。这意味着,一旦lhsrhs被消耗掉,且仅当res(lhs_p & rhs_p)时,true才能再改变一次。因此,我们可以从循环条件中提取lhs_p | rhs_p部分,并在循环后精确地应用一个res分配(我使用了while循环,因为for循环变得有点杂乱):

template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
    -> gray_code<Unsigned>
{
    // Make sure lhs.value has the highest set bit
    if (lhs.value < rhs.value)
    {
        std::swap(lhs.value, rhs.value);
    }

    // parity of lhs and rhs
    bool lhs_p = is_odd(lhs);
    bool rhs_p = is_odd(rhs);

    gray_code<Unsigned> res = lhs ^ rhs;
    for (Unsigned i{} ;
         lhs | lhs_p | rhs_p ;
         ++i, lhs >>= 1u, rhs >>= 1u)
    {
        Unsigned res_i = lhs_p & rhs_p;
        res ^= res_i << i;

        bool lhs_i = lhs.value & 1u;
        bool rhs_i = rhs.value & 1u;
        lhs_p = (lhs_p & not res_i) ^ lhs_i;
        rhs_p = (rhs_p & not res_i) ^ rhs_i;
    }
    return res;
}


有时候,更多的东西更少

当前,我们使用这样一个事实,即在某个时候lhsrhs最终将变为\ $ 0 \ $,并且在此之后只能进行一次修改这点。但是,我们目前不使用在某个点rhslhs是\ $ 0 \ $的事实。实际上,我们可以编写两个循环,而不是一个循环:一个循环运行直到lhs.valuerhs.value中的最小一个为\ $ 0 \ $,另一个循环运行直到另一个达到\ $ 0 \ $。第二个循环可以简化,因为我们知道一个rhsstd::swap中的最小值)在迭代过程中为\ $ 0 \ $:

template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
    -> gray_code<Unsigned>
{
    // Make sure lhs.value has the highest set bit
    if (lhs.value < rhs.value)
    {
        std::swap(lhs, rhs);
    }

    // parity of lhs and rhs
    bool lhs_p = is_odd(lhs);
    bool rhs_p = is_odd(rhs);

    gray_code<Unsigned> res = lhs ^ rhs;
    Unsigned i{};
    while (lhs)
    {
        Unsigned res_i = lhs_p & rhs_p;
        res ^= res_i << i;

        bool lhs_i = lhs.value & 1u;
        bool rhs_i = rhs.value & 1u;
        lhs_p = (lhs_p & not res_i) ^ lhs_i;
        rhs_p = (rhs_p & not res_i) ^ rhs_i;

        ++i;
        lhs >>= 1u;
        rhs >>= 1u;
    }
    // Last value in case lhs_p and rhs_p are 1
    res ^= (lhs_p & rhs_p) << i;
    return res;
}


重新思考逻辑

第二个循环可以重新思考为“当res_i为\ $ 0 \ $时会发生什么,而为\ $ 1 \ $时会发生什么”?在代码方面,应用了一些简化后,产生了以下循环:

template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
    -> gray_code<Unsigned>
{
    // Make sure lhs.value has the highest set bit
    if (lhs.value < rhs.value)
    {
        std::swap(lhs, rhs);
    }

    // parity of lhs and rhs
    bool lhs_p = is_odd(lhs);
    bool rhs_p = is_odd(rhs);

    gray_code<Unsigned> res = lhs ^ rhs;
    Unsigned i{};
    while (rhs)
    {
        Unsigned res_i = lhs_p & rhs_p;
        res ^= res_i << i;

        bool lhs_i = lhs.value & 1u;
        bool rhs_i = rhs.value & 1u;
        lhs_p = (lhs_p & not res_i) ^ lhs_i;
        rhs_p = (rhs_p & not res_i) ^ rhs_i;

        ++i;
        lhs >>= 1u;
        rhs >>= 1u;
    }

    // We know that rhs is 0 now, let's get rid of it
    while (lhs)
    {
        Unsigned res_i = lhs_p & rhs_p;
        res ^= res_i << i;

        bool lhs_i = lhs.value & 1u;
        lhs_p = (lhs_p & not res_i) ^ lhs_i;
        rhs_p = rhs_p & not res_i;

        ++i;
        lhs >>= 1u;
    }

    // Last value in case lhs_p and rhs_p are 1
    res ^= (lhs_p & rhs_p) << i;
    return res;
}


尽管此算法由于分支实际上通常比上一个算法慢,但它允许实现一些有趣的事情:分支之一始终将rhs_p设置为false,并且在rhs_p == false时无法执行。另一个分支不会更新rhs_p,这意味着该循环可以被理解为“在rhs_p == true时执行任务”,因此我们可以按如下方式重写原始的第二个循环(而不是新的转换后的第二个循环):

while (lhs)
{
    Unsigned res_i = lhs_p & rhs_p;
    bool lhs_i = lhs.value & 1u;
    if (res_i)
    {
        res ^= res_i << i;
        lhs_p = lhs_i;
        rhs_p = false;
    }
    else
    {
        lhs_p ^= lhs_i;
    }

    ++i;
    lhs >>= 1u;
}


已经应用了许多简化,因为我们知道在此循环中rhs_p == true。与第一个循环相比,我们最终在算法中有了第二个循环,这几乎是免费的,这意味着当我们将最高置位高的数字与最高置位小的数字相加时,其执行速度可能比以前的版本快得多。另请注意,由于最后一个循环在rhs_p == false结束时结束,因此rhs_p & lhs_p始终是false是后循环后操作,因此我们可以完全删除它。
res更新lhs_p,直到rhs_pfalse为止。但是,rhs_p设置为not lhs_p,这意味着res已更新为lhs_p,而lhs_p除最后一次迭代外为\ $ 0 \ $。换句话说,res仅有意义地更新一次,因为当我们到达lhs_p == 1时,我们在下一次迭代后立即离开循环。因此,我们可以调整循环以从中踢出res更新:

while (rhs_p)
{
    res ^= lhs_p << i;
    rhs_p = not lhs_p;
    lhs_p = lhs.value & 1u;

    ++i;
    lhs >>= 1u;
}


依次组合,这些优化为我们提供了以下算法:

if (rhs_p)
{
    while (not lhs_p)
    {
        lhs_p = lhs.value & 1u;
        ++i;
        lhs >>= 1u;
    }
    res ^= lhs_p << i;
}


虽然这是自从回答开始以来算法最通用和最优化的版本,但它仍然比将格雷码转换为规则整数,执行规则加法并将结果转换回格雷码慢一个数量级。不过,很高兴看到该解决方案可以推广和优化多少。我希望它可以为将来有兴趣添加两个格雷码的人们提供想法:)

#2 楼

我所有的评论都非常少:

干代码:

您的构造函数/赋值运算符不是DRY。

constexpr explicit gray_code(value_type value) noexcept:
    value( (value >> 1) ^ value )
{}

auto operator=(value_type other) & noexcept
    -> gray_code&
{
    value = (other >> 1) ^ other;
    return *this;
}


如果发生变化,则必须在多个位置修改代码。您应该将工作包装在一个函数中。这两种方法都使用:

constexpr explicit gray_code(value_type value) noexcept:
    value( convertValueToGray(value) )
{}

auto operator=(value_type other) & noexcept
    -> gray_code&
{
    value = convertValueToGray(other);
    return *this;
}


新的返回类型:

不确定我是否喜欢新的返回样式:
我个人认为这不太清楚(但这可能是我,我需要习惯)。就我个人而言,我目前仅在派生返回类型时使用新样式。

auto operator=(value_type other) & noexcept
    -> gray_code&


当不派生返回类型时,我仍然喜欢旧样式。

gray_code& operator=(value_type other) & noexcept


但这没什么大不了的。项目/公司样式指南将指示首选的内容。

公共成员变量:

您的值是公共的:
不确定这是否是有意的?您有分配在转入时进行转换的分配,还有一个转换运算符在转入时调整值。您是否真的要允许在未经监督的情况下访问原始值?

struct gray_code
{
    value_type value;


成本转换

转换回value_type似乎是有点沉重:

operator value_type() const noexcept
{
    value_type res = value;
    for (value_type mask = std::numeric_limits<value_type>::digits / 2
         ; mask ; mask >>= 1)
    {
        res ^= res >> mask;
    }
    return res;
}


如果这样做超过两次,可能会变得很昂贵。为什么不预先准备并将其存储在可变成员中。

// Variable containing the gray code
value_type          value;
mutable value_type  convertedValue; // Created on construction/assignment.
                                    // Marked as mutable to indicate that
                                    // it is not part of the state of the
                                    // object but rather a cached value.

operator value_type() const noexcept  {return convertedValue;}


我个人认为您的代码对无用的注释有点沉重。

评论


\ $ \ begingroup \ $
最后一个&是* this的ref限定词。它确保不能将运算符=与一个临时值一起使用,例如(a + b)= c。
\ $ \ endgroup \ $
–莫文
2014年11月7日13:48

\ $ \ begingroup \ $
关于可变值,运算符++,-,>> =,<< =,&=,| =和^ =,因此存储第二个值并始终通过更改正确获取值可能会很昂贵。在此,用户有责任知道何时要将值转换回整数。我本可以明确地进行转换操作,以免发生随机转换。
\ $ \ endgroup \ $
–莫文
2014年11月7日13:51

\ $ \ begingroup \ $
暴露价值是一种设计选择。对原始值进行处理时,许多位提示都可以更快,而快速是目标之一。因此,我认为公开类的内部没有问题:它们很有用,并且清楚地表明在用户的背后没有任何可疑的事情发生。
\ $ \ endgroup \ $
–莫文
2014年11月7日13:56

\ $ \ begingroup \ $
如果您故意让值成员处于开放状态而被位操作所困扰,那么可变成员技巧就不会有用。
\ $ \ endgroup \ $
–马丁·约克
2014年11月7日14:08

#3 楼

可以从主循环中删除一些操作
(有些机器没有“ AndNot-instruction”):

// process bits from least significant to most in shorter operand
while (0 < rhs) {
    Unsigned res_i = lhs_p & rhs_p;
    res ^= res_i << i;

    lhs_p ^= res_i ^ lhs;
    rhs_p = (rhs_p ^ res_i ^ rhs) & 1;

    ++i;
    lhs >>= 1u;
    rhs >>= 1u;
}
// remaining bits of longer operand
if (0 == rhs_p)
    return res;
lhs_p &= 1;


(而期望的运行长度用于均匀分布)位模式(1 +)小于1,)无需在循环中处理其余位:

    if (0 != lhs_p)
        return res ^= lhs_p << i;
    Unsigned bit = lhs ^ (lhs & (lhs-1));
    return res ^ (bit << (i+1));


我尝试将奇偶校验向左移动向右移动操作数:毫不奇怪,差异可以忽略不计。

    Unsigned bit = 1;
    for ( ; bit <= rhs && bit != 0 ; bit <<= 1) {
        long both = lhs_p&rhs_p;
        res ^= both;
        lhs_p = (lhs_p ^ both ^ lhs) << 1;
        rhs_p = ((rhs_p ^ both ^ rhs) & bit) << 1;  
    }
    if (0 == rhs_p)
        return res;
    lhs_p &= bit;
    if (0 != lhs_p)
        return res ^= lhs_p;
    bit = lhs ^ (lhs & (lhs-bit));
    return res ^ (bit << 1);


确定后,就可以对种群计数和奇偶校验的位黑客变种进行编码,而无需过多考虑类型大小:

br />

评论


\ $ \ begingroup \ $
不是现代C ++的部件(有待改进)有历史-我今天才发现“我的” C ++环境接受C ++ 11-如果使用标记告知…
\ $ \ endgroup \ $
–灰胡子
16年11月14日在12:26

\ $ \ begingroup \ $
嗯,几天前我正在清理该代码,我想到确实有可能摆脱上一个循环。看起来我不必自己寻找解决方案,谢谢:)
\ $ \ endgroup \ $
–莫文
16-11-14在21:35

\ $ \ begingroup \ $
关于改进的奇偶校验算法,我前段时间写了一篇文章,逐步解释了这些技巧(仍然与格雷码相关)。不过,对于位技巧算法,我仍然更喜欢类型无关的解决方案。
\ $ \ endgroup \ $
–莫文
16年11月14日在21:36

\ $ \ begingroup \ $
(请注意,无循环可能不会产生明显的优势-可读性和速度方面都没有。)一个借口试图在“较短的操作数的最低至最高有效位”中“反转移位方向” -loop”(顺便说一句:在该变体中,其余位的处理看起来更好)是看是否可以在生成奇偶校验时反转移位方向(以最高/更高有效位为单位累加奇偶校验)来添加反映二进制编码整数的速度快于与位数成正比的速度。与bin2gray相比,可以改善混乱状态。
\ $ \ endgroup \ $
–灰胡子
16-11-14在23:03