需要使用按位运算的上升然后下降的快速函数

时间:2012-04-11 02:06:28

标签: c# bit-manipulation

我需要一个整数值函数,它返回一个递增序列,直到达到给定的最大值,然后再次下降。我称之为Ziggurat:

0 1 2 3 4 5 6 6 5 4 3 2 1 0

步骤一个接一个。最大值(在序列中间)必须出现两次。在0到2 * max范围之外,我不在乎会发生什么。

我希望功能快 - 没有分支。我更喜欢按位操作。

作为一个不起作用的例子,这是我的金字塔函数,以及我对绝对值的实现:

private static readonly int LONG_ABS_MASK_SHIFT = sizeof(long) * 8 - 1;

/// <summary>
/// Compute the Absolute value of a long without branching.
/// 
/// Note: This will deviate from Math.Abs for Int64.MinValue, where the .NET library would throw an exception.
/// The most negative number cannot be made positive.
/// </summary>
/// <param name="v">Number to transform.</param>
/// <returns>Absolute value of v.</returns>
public static long Abs(this long v)
{
    long mask = v >> LONG_ABS_MASK_SHIFT;
    return (v ^ mask) - mask;
}

public static long Pyramid(this long N, long max)
{
    return max - (max - N).Abs();
}

此金字塔函数创建的序列如0 1 2 3 4 5 6 5 4 3 2 1 0

请注意,中间数字只出现一次。

我有一个想法是将一个查找表存储为long或BigInteger中的连续位块,并将它们移位并屏蔽掉它们,但是对于长序列来说,这会占用太多内存。但它只使用很少的指令。

3 个答案:

答案 0 :(得分:3)

试试这个:

public static long Pyramid2(this long N, long max)
{
    return N.Pyramid(max + 1) + ((max - N) >> -1);
}

结果:

0 1 2 3 4 五 6 6 五 4 3 2 1 0


结果如下:

                  \ N=0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
  N.Pyramid(6 + 1)    0  1  2  3  4  5  6  7  6  5  4  3  2  1  0 -1 -2 -3 -4
+ ((max - N) >> -1)   0  0  0  0  0  0  0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
=                     0  1  2  3  4  5  6  6  5  4  3  2  1  0 -1 -2 -3 -4 -5

Pyramid 是问题中定义的原始 Pyramid 方法。

答案 1 :(得分:2)

功能:

public static long Ziggurat(this long N, long max)
{
  N = max - N;
  return max - (N ^ N >> -1);
}

没有循环,测试或分支。只需要四次操作;两个是按位的。


但是,您的问题存在问题。您指定输出:

0 1 2 3 4 5 6 6 5 4 3 2 1 0

此序列有14个元素。但是,您还将函数的范围指定为“0到2 * max”,其中只有13个元素!我假设范围为零到2 * max + 1是可以接受的。

而且,这是一个基于控制台的测试:

static void Main(string[] args)
{
  long max = 6;
  for (long i = 0; i <= 2 * max + 1; i++)
  {
    Console.Write("{0} ", i.Ziggurat(max));
  }
}

输出:

0 1 2 3 4 5 6 6 5 4 3 2 1 0

N = max - N会使输入值从数字行的最大值开始向下计数,以使范围0 through 13变为6 through -7

请注意以下事项:

  • -1的按位补码为0(~11111111 = 00000000)。
  • -7的按位补码为6(~11111001 = 00000110)。

因此,如果我们从6计算到-7并补充所有负值,我们会得到包含两个零的序列:

6 5 4 3 2 1 0 0 1 2 3 4 5 6

第二行中的子表达式N ^ N >> -1补充N,但仅当N为负数时才会补充。

第二行max - …中表达式的剩余部分将上面的序列反转为所需的输出:

0 1 2 3 4 5 6 6 5 4 3 2 1 0

答案 2 :(得分:1)

public static IEnumerable<long> getPyramid(long maxValue)
{
    for(long i = 0; i <= maxValue; i++)
    {
        yield return i;
    }

    for(long i = maxValue; i >=0; i--)
    {
        yield return i;
    }
}

有人可能会用Concat Enumerable.Range以及可能的选择/反向或类似的东西处理整个事情,但它可能会稍微低效,因为我不知道琐碎让它倒计时的方式。反向将比循环的屈服更“工作”,并且select(执行maxvalue减去Enumerable.Range的当前迭代)将进行一堆额外的算术,所有这些都是为了避免几行代码。

即:

public static IEnumerable<long getPyramid(long maxValue)
{
  return Enumerable.Range(0, maxValue)
  .Concat(Enumerable.Range(0, maxValue).Select(num => maxValue - num));
}