格雷码递增函数

时间:2013-07-05 13:47:54

标签: c performance algorithm optimization gray-code

不使用任何外部计数器或其他状态,我正在寻找一个高效函数,该函数采用n位值(32位或左右)并返回{{3中的后续值}}

那是:

int fn(int x)
{
    int y = gray_to_binary(x);
    y = y + 1;
    return binary_to_gray(y);
}

虽然binary_to_gray()函数很简单(x ^ (x >> 1)),但相应的gray_to_binary()根本不是那么简单(log(n)次迭代循环)。

也许有一个更有效的操作序列?无论是标准反射格雷码,还是选择其他格雷码以适应此问题。


旁白:我看到了这个问题的两种可能的解决方案类型 - 一种是选择一种更容易转换为二进制并使用上面给出的形式的代码(或者为了演示更高效的代码)转换为反射代码的二进制代码),另一种是将转换推迟到二进制,并生成一种方法,该方法在不使用二进制增量的情况下遍历格雷码。

在后一种情况下,将结果代码转换为二进制文件可能会特别困难。从实际角度来看,这可能是一个不利因素,但它仍然是一个有趣的事情。


更新:由于有人指出格雷解码只有log(n)个操作(使用两种不同的技术),我花了一些时间试图弄清楚这是不是严格限制事物可以简化的程度。在确定要执行的下一个操作时必须考虑所有位,否则“考虑”位将无法改变,并且函数将在两个值之间振荡。必须以某种方式将输入压缩为可管理的比例,以确定要执行的下一个操作。

要进行log(n-k)操作,可以使用2 k -entry LUT来缩短最后k次操作(评论建议k=32 )。

另一种可以经常减少事物的技术是乘法和位掩码的组合。例如,计算Gray code以实现基于奇偶校验的算法。

从乘法和位掩码的方法来看,似乎可能有空间来发明格雷码,这进一步简化了操作集......但我不认为任何这样的代码是已知的。

4 个答案:

答案 0 :(得分:9)

用于递增格雷码的简单算法:

gray_inc(x):
  if parity of x is even:
    return x xor 1
  if parity of x is odd:
    let y be the rightmost 1 bit in x
    return x xor (y leftshift 1)

找到x的奇偶校验取O(log(k)),其中k是x的位长。但是,上述算法中的每一步都会改变奇偶校验,所以在循环中你可以只改变奇偶校验操作。 (当然,这不符合OP的要求,即不保留任何状态;它需要一点状态。另外,请参阅下文。)

使用标准位攻击查找y为O(1):y = x&-x,其中-是2的补码否定运算符;你也可以把它写成y = x and not (x - 1)

您也可以使用奇偶校验增强型格雷码,后者是带有反奇偶校验位的格雷码(因此增强码的奇偶校验始终为奇数)。在这种情况下,您可以使用以下O(1)算法:

parity_gray_increment(x):
  let y be the rightmost bit in x
  return x xor ((y leftshift 1) or 1)

在上述两种算法中,为了清晰起见,我省略了溢出检查。要使代码周期溢出,请将y leftshift 1替换为y leftshift 1 if y is not the high-order bit, else y。 (在大多数体系结构中,测试可能是if y leftshift 1 is not 0。)或者,如果y太大而无法向左移动,您可能会抛出异常或返回错误。

答案 1 :(得分:4)

根据你的目标,我有三种方式可以使用。

1)一个常见的功能: 编写一个函数来处理您需要支持的最宽泛的格雷码值。遵循@harold建议使用更大的变化和xors的方法:

inline UInt16 graycodeToBinary( UInt16 value )
{
    value ^= (value >> 1);
    value ^= (value >> 2);
    value ^= (value >> 4);
    value ^= (value >> 8);
    return value;
}

根据需要扩展输入数据类型和移位,直到下一个移位量等于或超过数据位数。即使设置和测试一个循环也不如运行这些指令有效。这只会比查找方法稍慢。

2)每个功率为2的功能 与上面相同,但使用graycodeToBinary_8,_16,_32版本。如果您进行批次的小转换以及偶尔非常大的转换,可能会有所帮助。如果使用C ++重载可以自动为您选择合适的版本(并且您可以通过一些模板元编程将其变为荒谬)。

3)查找表: 除非您考虑缓存行为,否则这似乎是一个好主意。如果您不经常使用查找表,那么与上述方法相比,这是不必要的复杂。如果您经常使用查找表,它可能会破坏您的缓存行为(大量分散读取到更大的内存区域)。有一小部分应用程序会变得非常快。此外,您必须创建查找表,因此您可能已经可以使用graycode_to_binary函数。

最后,除了选项1),我很少找到任何用途。我见过一个嵌入式应用程序,它将查找表硬编码到它的ROM中。这很好,因为处理器还没有缓存。

答案 2 :(得分:2)

我在C#中实现了一个似乎有效的算法:

首先你需要整数的奇偶校验。我已经为ulong(64位)实现了它,但您可以轻松地将其修改为任何所需的输出:

public static ulong GetParity (ulong value) {
    value ^= value >> 0x20;
    value ^= value >> 0x10;
    value ^= value >> 0x08;
    value ^= value >> 0x04;
    value &= 0x0f;
    return (0x6996UL >> (int)value) & 0x01;
}

接下来,您需要检查奇偶校验是否均匀(设置的位数是偶数,如果是这种情况,则只需交换最后一位)。如果奇偶校验是奇数,则通常将最低有效设置位左侧的位交换。这可以使用以下方法计算:

public static ulong LeastSignificantBit (ulong value) {
    return value&((~value)+0x01);
}

有一个边界情况:如果最低有效设置位是格雷码的最大位,如果是这种情况,您当然可以不交换左位,而只需将计数器设置为零。

总而言之,您可以使用以下代码:

public static ulong GrayIncrement (ulong original, int bits = 0x40) {
    ulong last = 0x01UL << (bits - 0x01);
    if (GetParity (original) == 0x00) {
        return original ^ 0x01UL;//even parity: swap least significant bit
    } else {
        ulong lbm = LeastSignificantBit(original);
        if (lbm < last) {
            return original ^ (lbm << 0x01);//otherwise swap the bit left to the least significant set bit
        } else {
            return 0x00;//wrap around
        }
    }
}

答案 3 :(得分:1)

来自wiki(http://en.wikipedia.org/wiki/Gray_code#Converting_to_and_from_Gray_code

/*
    The purpose of this function is to convert an unsigned
    binary number to reflected binary Gray code.

    The operator >> is shift right. The operator ^ is exclusive or.
*/
unsigned int binaryToGray(unsigned int num)
{
        return (num >> 1) ^ num;
}

/*
        The purpose of this function is to convert a reflected binary
        Gray code number to a binary number.
*/
unsigned int grayToBinary(unsigned int num)
{
    unsigned int mask;
    for (mask = num >> 1; mask != 0; mask = mask >> 1)
    {
        num = num ^ mask;
    }
    return num;
}