【问题标题】:Find most significant bit of a BigInteger查找 BigInteger 的最高有效位
【发布时间】:2012-04-04 13:18:13
【问题描述】:

我已经阅读了许多用于识别 32 位和 64 位整数的最高有效位的精细算法(包括 SO 上的其他帖子)。但我使用的是 BigIntegers,并且将处理长达 4000 位的数字。 (BigInteger 将把希尔伯特索引保存到希尔伯特空间填充曲线中,该曲线蜿蜒穿过分形深度为 4 的 1000 维超立方体。)但是大部分案例将涉及可以放入 64 位整数内的数字,所以我想要一个最适合常见情况但可以处理极端情况的解决方案。

天真的方式是:

BigInteger n = 234762348763498247634; 
int count = 0; 
while (n > 0) {
    n >>= 1;
    count++;
}

我正在考虑将常见情况转换为 Long,并在这些情况下使用 64 位算法,否则对真正大的数字使用不同的算法。但我不确定转换为 Long 的成本有多高,以及这是否会淹没在 64 位数量上进行剩余计算的效率。有什么想法吗?

此函数的一个预期用途是帮助优化逆格雷码计算。

更新。我编写了两种方法并运行了基准测试。

  • 如果数字低于 Ulong.MaxValue,则转换为 Ulong 并执行二进制搜索方法的速度是使用 BigInteger.Log 的两倍。
  • 如果这个数字非常大(我高达 10000 位),那么 Log 的速度会快 3.5 倍。

    对 MostSignificantBitUsingLog 的 100 万次调用经过了 96 毫秒 (可转换为长)。

    100 万次调用耗时 42 毫秒 MostSignificantBitUsingBinarySearch(可转换为 Long)。

    一万次调用 MostSignificantBitUsingLog 耗时 74 毫秒 (太大无法转换)。

    一万次调用耗时 267 毫秒 MostSignificantBitUsingBinarySearch(太大而无法转换)。

下面是使用Log的代码:

public static int MostSignificantBitUsingLog(BigInteger i)
{
    int bit;
    if (i == 0)
        bit = -1;
    else
        bit = (int)BigInteger.Log(i, 2.0);

    return bit;
}

这是我的二进制搜索方法。可以改进将二进制除法扩展到 BigInteger 范围。下次我会试试的。

public static int MostSignificantBitUsingBinarySearch(BigInteger i)
{
    int bit;
    if (i.IsZero)
        bit = -1;
    else if (i < ulong.MaxValue)
    {
        ulong y = (ulong)i;
        ulong s;
        bit = 0;
        s = y >> 32;
        if (s != 0)
        {
            bit = 32;
            y = s;
        }
        s = y >> 16;
        if (s != 0)
        {
            bit += 16;
            y = s;
        }
        s = y >> 8;
        if (s != 0)
        {
            bit += 8;
            y = s;
        }
        s = y >> 4;
        if (s != 0)
        {
            bit += 4;
            y = s;
        }
        s = y >> 2;
        if (s != 0)
        {
            bit += 2;
            y = s;
        }
        s = y >> 1;
        if (s != 0)
            bit++;
    }
    else
        return 64 + MostSignificantBitUsingBinarySearch(i >> 64);

    return bit;
}

更新 2:我更改了我的二进制搜索算法以针对 BigIntegers 最多一百万个二进制数字,而不是在 64 位块中递归地调用自身。好多了。现在运行我的测试需要 18 毫秒,比调用 Log 快四倍! (在下面的代码中,MSB 是我的 ulong 函数,它做同样的事情,循环展开。)

public static int MostSignificantBitUsingBinarySearch(BigInteger i)
{
    int bit;
    if (i.IsZero)
        bit = -1;
    else if (i < ulong.MaxValue)
        bit = MSB((ulong)i);
    else
    {
        bit = 0;
        int shift = 1 << 20; // Accommodate up to One million bits.
        BigInteger remainder;     
        while (shift > 0)
        {
            remainder = i >> shift;
            if (remainder != 0)
            {
                bit += shift;
                i = remainder;
            }
            shift >>= 1;
        }
    }
    return bit;
}

【问题讨论】:

  • “BigInteger 将把希尔伯特指数保持在希尔伯特空间填充曲线中,这条曲线蜿蜒穿过分形深度为 4 的 1000 维超立方体。”我的脑袋爆炸了。
  • 恐怕我不明白这个问题。 BigInteger 是一个任意精度的整数类,这意味着不再有“最大位”,而字符串中只有一个固定的最终字符(具有讽刺意味的是,这与 BigInteger 的工作方式没有太大区别)。
  • @Neil:该类型没有“最大位”,但任何给定的 BigInt 都有。
  • 亚当,你应该看看我的头。 N维任何东西都让我头疼。我的孩子们,虽然我很喜欢在一个立方体纸巾盒的侧面贴上带有二进制数字的纸,这样我就可以看到穿过立方体的路径。
  • @HenkHolterman 实际上有一个 BigInteger 没有“最大位”的值:BigInteger.Zero

标签: c# biginteger


【解决方案1】:

您可以计算 log2 代表所需的位数:

var numBits = (int)Math.Ceil(bigInt.Log(2));

【讨论】:

  • 我喜欢这个建议,尽管据我所知,Log 是一个静态方法:BigInteger.Log(num, 2.0)。我会试试看效果如何。
  • 或者我可以像扩展方法一样使用它吗?我对如何识别何时可以使用静态方法作为扩展方法非常模糊。
  • 您可以使用静态方法作为扩展,如果它被声明为这样。没有其他区别。在这种情况下,您需要使用静态语法,因为该方法尚未标记为扩展。
  • 我写了两个实现:一个使用 Log,另一个使用二进制搜索。如果数字在 Ulong 的范围内,则转换为 Ulong 并进行二进制搜索会更快。否则,使用 Log 会更快。我将拆分差异并将 Log 用于大于 Ulong.MAXVALUE 的任何内容。
  • 嗯,现在我想起来了:你为什么不和 ULong.MaxValue 比较呢?
【解决方案2】:

您可以将其视为二进制搜索问题。

您的上限为 4000(可能会增加一些空间)

int m = (lo + hi) / 2;
BigInteger x = BigInteger(1) << m;

if (x > n) ...
else  ...

【讨论】:

  • 一个好建议。我会试试这个,以及 Log 方法。
  • 这种方法对于 Ulong.MAXVALUE 以下的数字更快,但对于以上的数字比 Log 慢。我会用它作为特例。
【解决方案3】:

在 .Net 5 中,这是 now built-in...

int log2 = myBigInt.GetBitLength()

【讨论】:

    【解决方案4】:

    如果您可以使用 Java 而不是 C#,则可以在 http://uzaygezen.googlecode.com 找到一个用于任意精度希尔伯特曲线索引的库。对于格雷码逆的实现,您可能需要仔细查看上述项目中的 LongArrayBitVector.grayCodeInverse 或 BitSetBackedVector.grayCodeInverse。

    【讨论】:

      【解决方案5】:

      晚了 8 年,为了找到 MSB 最高位(又名 Log2),我想出了这个快速方法...

      static int GetTopBit(BigInteger value)
      {
          if (value < 0)
              BigInteger.Negate(value);
          int lowerBytes = value.GetByteCount(true) - 1;
          int t = value.ToByteArray(true)[lowerBytes];
          int top = t > 127 ? 8 : t > 63 ? 7 : t > 31 ? 6 : t > 15 ? 5 : t > 7 ? 4 : t > 3 ? 3 : t > 1 ? 2 : 1;
          int topbit = (top + lowerBytes * 8);
          return topbit;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-06
        • 1970-01-01
        • 2015-03-25
        • 2020-09-07
        • 2019-09-12
        • 2013-02-11
        相关资源
        最近更新 更多