格雷码添加

Gray codes addition

本文关键字:添加 雷码      更新时间:2023-10-16

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

在#6下的这份文档中,有一种串行格雷码添加算法(直接复制;注意,xor):

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;

这将格雷码字A和B相加,形成格雷码字S。操作数奇偶校验为PA和PB,和奇偶校验为PS。这在内部传播两个进位E和F,并产生两个外部进位CE和CF

不幸的是,它没有说明任何关于减法的内容,但我认为,当你可以对负数进行编码时,你可以使用加法。

我接受了@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),它返回整数中集合位数的奇偶性(如果内部函数不存在,还有其他方法可以手动计算)。当格雷码具有偶数个设置位时,它就是偶数。该算法仍然可以改进,但这种实现相当忠实于原始算法。如果你想了解优化实现的细节,你可以看看这个问答;代码审查。

我最近设计了一种添加两个格雷码的新算法。不幸的是,它仍然比天真的双重转换解决方案慢,也比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
  • 因此,i a是2的幂,并且a<b、 则aŞb=a-b。只有当b<2*a
  • 如果a和b具有相同的超地板但不相等,则a+b=(超地板(a)<lt;1) (超地板(a)Şa)+(超地板

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

如果你想要更多的细节/信息,你也可以查看这个问答;一篇关于代码的评论,它提出了该算法的现代C++实现以及一些优化(作为奖励,还有一些很好的MathJax方程,我们在这里没有:D)。