17

有没有任何已知的方法来计算两个格雷码的加法(可能还有减法),而不必将两个格雷码转换为常规二进制,执行二进制加法然后将结果转换回格雷码?我设法编写了递增和递减函数,但加法和减法似乎更少记录并且更难编写。

4

3 回答 3

11
于 2014-01-04T19:57:01.777 回答
6

我接受了@Sebastian Dressler 的回答,因为建议的算法确实有效。为了完整起见,我在此提出算法的相应 C99 实现(与 C++ 兼容):

// lhs and rhs are encoded as Gray codes
unsigned add_gray(unsigned lhs, unsigned rhs)
{
    // e and f, initialized with the parity of lhs and rhs
    // (0 means even, 1 means odd)
    bool e = __builtin_parity(lhs);
    bool f = __builtin_parity(rhs);

    unsigned res = 0u;
    for (unsigned i = 0u ; i < CHAR_BIT * sizeof(unsigned) ; ++i)
    {
        // Get the ith bit of rhs and  lhs
        bool lhs_i = (lhs >> i) & 1u;
        bool rhs_i = (rhs >> i) & 1u;

        // Copy e and f (see {in parallel} in the original paper)
        bool e_cpy = e;
        bool f_cpy = f;

        // Set the ith bit of res
        unsigned res_i = (e_cpy & f_cpy) ^ lhs_i ^ rhs_i;
        res |= (res_i << i);

        // Update e and f
        e = (e_cpy & (!f_cpy)) ^ lhs_i;
        f = ((!e_cpy) & f_cpy) ^ rhs_i;
    }
    return res;
}

注意:__builtin_parity是一个编译器内在函数(GCC 和 Clang),它返回整数中设置位数的奇偶校验(如果内在函数不存在,还有其他方法可以手动计算)。格雷码是偶数,当它有偶数个设置位时。该算法仍然可以改进,但这种实现相当忠实于原始算法。如果您想了解有关优化实现的详细信息,可以查看此代码审查问答

于 2014-11-06T13:54:05.697 回答
3

我最近设计了一种新算法来添加两个格雷码。不幸的是,它仍然比简单的双重转换解决方案慢,也比 Harold Lucal 的算法(接受答案中的算法)慢。但是任何新的问题解决方案都是受欢迎的,对吧?

// lhs and rhs are encoded as Gray codes
unsigned add_gray(unsigned lhs, unsigned rhs)
{
    // Highest power of 2 in lhs and rhs
    unsigned lhs_base = hyperfloor(lhs);
    unsigned rhs_base = hyperfloor(rhs);

    if (lhs_base == rhs_base) {
        // If lhs and rhs are equal, return lhs * 2
        if (lhs == rhs) {
            return (lhs << 1u) ^ __builtin_parity(lhs);
        }
        // Else return base*2 + (lhs - base) + (rhs - base)
        return (lhs_base << 1u) ^ add_gray(lhs_base ^ lhs, lhs_base ^ rhs);
    }

    // It's easier to operate from the greatest value
    if (lhs_base < rhs_base) {
        swap(&lhs, &rhs);
        swap(&lhs_base, &rhs_base);
    }

    // Compute lhs + rhs
    if (lhs == lhs_base) {
        return lhs ^ rhs;
    }

    // Compute (lhs - base) + rhs
    unsigned tmp = add_gray(lhs ^ lhs_base, rhs);
    if (hyperfloor(tmp) < lhs_base) {
        // Compute base + (lhs - base) + rhs
        return lhs_base ^ tmp;
    }
    // Here, hyperfloor(lhs) == hyperfloor(tmp)
    // Compute hyperfloor(lhs) * 2 + ((lhs - hyperfloor(lhs)) + rhs) - hyperfloor(lhs)
    return (lhs_base << 1u) ^ (lhs_base ^ tmp);
}

该算法使用以下效用函数以便牵引正常工作:

// Swap two values
void swap(unsigned* a, unsigned* b)
{
    unsigned temp = *a;
    *a = *b;
    *b = temp;
}

// Isolate the most significant bit
unsigned isomsb(unsigned x)
{
    for (unsigned i = 1u ; i <= CHAR_BIT * sizeof(unsigned) / 2u ; i <<= 1u) {
        x |= x >> i;
    }
    return x & ~(x >> 1u);
}

// Return the greatest power of 2 not higher than
// x where x and the power of 2 are encoded in Gray
// code
unsigned hyperfloor(unsigned x)
{
    unsigned msb = isomsb(x);
    return msb | (msb >> 1u);
}

那么它是怎样工作的?

我不得不承认,对于“简单”的东西来说,这是一堵代码墙。它主要基于对格雷码中位模式的观察;也就是说,我没有正式证明任何事情,但是我还没有找到算法不起作用的情况(如果我不考虑溢出,它不会处理溢出)。以下是用于构建算法的主要观察结果,假设一切都是格雷码:

  • 2 * n = (n << 1) ⊕ 奇偶校验 (n)
  • 如果 a 是 2 的幂且 a > b,则 a ⊕ b = a + b
  • 因此,ia 是 2 的幂且 a < b,则 a ⊕ b = a - b。这仅在 b < 2 * a 时才有效。
  • 如果 a 和 b 具有相同的超地板但不相等,则 a + b = (hyperfloor(a) << 1) ⊕ ((hyperfloor(a) ⊕ a) + (hyperfloor(b) ⊕ b))。

基本上,这意味着我们知道如何乘以 2,如何将 2 的幂加到较小的格雷码上,以及如何从大于 2 的幂但小于下一个的格雷码中减去 2 的幂2 的幂。其他一切都是技巧,因此我们可以根据相等的值或 2 的幂进行推理。

如果您想了解更多详细信息/信息,您还可以查看Code Review 上的此问答,该问答提出了算法的现代 C++ 实现以及一些优化(作为奖励,这里有一些不错的 MathJax 方程,我们在这里没有: D)。

于 2015-04-08T08:09:03.130 回答