计算BigInteger的平方根(System.Numerics.BigInteger)

时间:2010-08-07 23:13:36

标签: c# biginteger

.NET 4.0为任意大整数提供System.Numerics.BigInteger类型。我需要计算BigInteger的平方根(或合理的近似值 - 例如,整数平方根)。所以我没有必要重新实现这个轮子,有没有人有一个很好的扩展方法呢?

8 个答案:

答案 0 :(得分:18)

Check if BigInteger is not a perfect square具有计算Java BigInteger的整数平方根的代码。在这里它被翻译成C#,作为扩展方法。

    public static BigInteger Sqrt(this BigInteger n)
    {
        if (n == 0) return 0;
        if (n > 0)
        {
            int bitLength = Convert.ToInt32(Math.Ceiling(BigInteger.Log(n, 2)));
            BigInteger root = BigInteger.One << (bitLength / 2);

            while (!isSqrt(n, root))
            {
                root += n / root;
                root /= 2;
            }

            return root;
        }

        throw new ArithmeticException("NaN");
    }

    private static Boolean isSqrt(BigInteger n, BigInteger root)
    {
        BigInteger lowerBound = root*root;
        BigInteger upperBound = (root + 1)*(root + 1);

        return (n >= lowerBound && n < upperBound);
    }

非正式测试表明,对于小整数,这比Math.Sqrt慢约75倍。 VS剖析器指向isSqrt中的乘法作为热点。

答案 1 :(得分:13)

我不确定牛顿方法是否是计算bignum平方根的最佳方法,因为它涉及对bignum来说缓慢的划分。您可以使用CORDIC方法,该方法仅使用加法和移位(此处显示为无符号整数)

static uint isqrt(uint x)
{
    int b=15; // this is the next bit we try 
    uint r=0; // r will contain the result
    uint r2=0; // here we maintain r squared
    while(b>=0) 
    {
        uint sr2=r2;
        uint sr=r;
                    // compute (r+(1<<b))**2, we have r**2 already.
        r2+=(uint)((r<<(1+b))+(1<<(b+b)));      
        r+=(uint)(1<<b);
        if (r2>x) 
        {
            r=sr;
            r2=sr2;
        }
        b--;
    }
    return r;
}

有一种类似的方法,只使用加法和移位,称为'Dijkstras Square Root',例如在这里解释:

答案 2 :(得分:5)

将平方根计算为任意精度的最简单可行方法可能是Newton's method.

答案 3 :(得分:2)

您可以将其转换为您选择的语言和变量类型。这是一个JavaScript中截断的squareroot(对我来说最新鲜),利用1 + 3 + 5 ... + nth odd number = n ^ 2。所有变量都是整数,它只是加减法。

var truncSqrt = function(n) {
  var oddNumber = 1;
  var result = 0;
  while (n >= oddNumber) {
    n -= oddNumber;
    oddNumber += 2;
    result++;
  }
  return result;
};

答案 4 :(得分:2)

好吧,这里先发布一些变体的速度测试。 (我只考虑了能够给出确切结果并且至少适合BigInteger的方法):

+------------------------------+-------+------+------+-------+-------+--------+--------+--------+
| variant - 1000x times        |   2e5 | 2e10 | 2e15 |  2e25 |  2e50 |  2e100 |  2e250 |  2e500 |
+------------------------------+-------+------+------+-------+-------+--------+--------+--------+
| my version                   |  0.03 | 0.04 | 0.04 |  0.76 |  1.44 |   2.23 |   4.84 |  23.05 |
| RedGreenCode (bound opti.)   |  0.56 | 1.20 | 1.80 |  2.21 |  3.71 |   6.10 |  14.53 |  51.48 |
| RedGreenCode (newton method) |  0.80 | 1.21 | 2.12 |  2.79 |  5.23 |   8.09 |  19.90 |  65.36 |
| Nordic Mainframe (CORDIC)    |  2.38 | 5.52 | 9.65 | 19.80 | 46.69 |  90.16 | 262.76 | 637.82 |
| Sunsetquest (without divs)   |  2.37 | 5.48 | 9.11 | 24.50 | 56.83 | 145.52 | 839.08 | 4.62 s |
| Jeremy Kahan (js-port)       | 46.53 | #.## | #.## |  #.## |  #.## |   #.## |   #.## |   #.## |
+------------------------------+-------+------+------+-------+-------+--------+--------+--------+

+------------------------------+--------+--------+--------+---------+---------+--------+--------+
| variant - single             | 2e1000 | 2e2500 | 2e5000 | 2e10000 | 2e25000 |  2e50k | 2e100k |
+------------------------------+--------+--------+--------+---------+---------+--------+--------+
| my version                   |   0.10 |   0.77 |   3.46 |   14.97 |  105.19 | 455.68 | 1,98 s |
| RedGreenCode (bound opti.)   |   0.26 |   1.41 |   6.53 |   25.36 |  182.68 | 777.39 | 3,30 s |
| RedGreenCode (newton method) |   0.33 |   1.73 |   8.08 |   32.07 |  228.50 | 974.40 | 4,15 s |
| Nordic Mainframe (CORDIC)    |   1.83 |   7.73 |  26.86 |   94.55 |  561.03 | 2,25 s | 10.3 s |
| Sunsetquest (without divs)   |  31.84 | 450.80 | 3,48 s |  27.5 s |    #.## |   #.## |   #.## |
| Jeremy Kahan (js-port)       |   #.## |   #.## |   #.## |    #.## |    #.## |   #.## |   #.## |
+------------------------------+--------+--------+--------+---------+---------+--------+--------+

- value example: 2e10 = 20000000000 (result: 141421)
- times in milliseconds or with "s" in seconds
- #.##: need more than 5 minutes (timeout)

说明:

Jeremy Kahan (js-port)

Jeremy的简单算法可以工作,但是由于简单的加/减...:)

Sunsetquest (without divs)

没有划分的方法是好的,但是由于划分和征服的变体,结果收敛相对较慢(尤其是数量较大)

Nordic Mainframe (CORDIC)

尽管不可植入的BigInteger的逐位操作会产生大量开销,但CORDIC算法已经非常强大。

我已经通过这种方式计算了所需的位:int b = Convert.ToInt32(Math.Ceiling(BigInteger.Log(x, 2))) / 2 + 1;

RedGreenCode (newton method)

经过验证的牛顿法表明,旧的东西不一定要慢。尤其是大量数字的快速收敛几乎是无与伦比的。

RedGreenCode (bound opti.)

耶桑·法方(Jesan Fafon)保存乘法的提议在这里带来了很多。

my version

首先:从Math.Sqrt()开始计算小数,一旦double的精度不再足够,则使用牛顿算法。但是,我尝试使用Math.Sqrt()预先计算尽可能多的数字,这使牛顿算法收敛得更快。

来源:

static readonly BigInteger FastSqrtSmallNumber = 4503599761588224UL; // as static readonly = reduce compare overhead

static BigInteger SqrtFast(BigInteger value)
{
  if (value <= FastSqrtSmallNumber) // small enough for Math.Sqrt() or negative?
  {
    if (value.Sign < 0) throw new ArgumentException("Negative argument.");
    return (ulong)Math.Sqrt((ulong)value);
  }

  BigInteger root; // now filled with an approximate value
  int byteLen = value.ToByteArray().Length;
  if (byteLen < 128) // small enough for direct double conversion?
  {
    root = (BigInteger)Math.Sqrt((double)value);
  }
  else // large: reduce with bitshifting, then convert to double (and back)
  {
    root = (BigInteger)Math.Sqrt((double)(value >> (byteLen - 127) * 8)) << (byteLen - 127) * 4;
  }

  for (; ; )
  {
    var root2 = value / root + root >> 1;
    if (root2 == root || root2 == root + 1) return root;
    root = value / root2 + root2 >> 1;
    if (root == root2 || root == root2 + 1) return root2;
  }
}

完整基准测试来源:

using System;
using System.Numerics;

namespace MathTest
{
  class Program
  {
    static readonly BigInteger FastSqrtSmallNumber = 4503599761588224UL; // as static readonly = reduce compare overhead
    static BigInteger SqrtMax(BigInteger value)
    {
      if (value <= FastSqrtSmallNumber) // small enough for Math.Sqrt() or negative?
      {
        if (value.Sign < 0) throw new ArgumentException("Negative argument.");
        return (ulong)Math.Sqrt((ulong)value);
      }

      BigInteger root; // now filled with an approximate value
      int byteLen = value.ToByteArray().Length;
      if (byteLen < 128) // small enough for direct double conversion?
      {
        root = (BigInteger)Math.Sqrt((double)value);
      }
      else // large: reduce with bitshifting, then convert to double (and back)
      {
        root = (BigInteger)Math.Sqrt((double)(value >> (byteLen - 127) * 8)) << (byteLen - 127) * 4;
      }

      for (; ; )
      {
        var root2 = value / root + root >> 1;
        if (root2 == root || root2 == root + 1) return root;
        root = value / root2 + root2 >> 1;
        if (root == root2 || root == root2 + 1) return root2;
      }
    }

    // newton method
    public static BigInteger SqrtRedGreenCode(BigInteger n)
    {
      if (n == 0) return 0;
      if (n > 0)
      {
        int bitLength = Convert.ToInt32(Math.Ceiling(BigInteger.Log(n, 2)));
        BigInteger root = BigInteger.One << (bitLength / 2);

        while (!isSqrtRedGreenCode(n, root))
        {
          root += n / root;
          root /= 2;
        }

        return root;
      }

      throw new ArithmeticException("NaN");
    }
    private static bool isSqrtRedGreenCode(BigInteger n, BigInteger root)
    {
      BigInteger lowerBound = root * root;
      //BigInteger upperBound = (root + 1) * (root + 1);

      return n >= lowerBound && n <= lowerBound + root + root;
      //return (n >= lowerBound && n < upperBound);
    }

    // without divisions
    public static BigInteger SqrtSunsetquest(BigInteger number)
    {
      if (number < 9)
      {
        if (number == 0)
          return 0;
        if (number < 4)
          return 1;
        else
          return 2;
      }

      BigInteger n = 0, p = 0;
      var high = number >> 1;
      var low = BigInteger.Zero;

      while (high > low + 1)
      {
        n = (high + low) >> 1;
        p = n * n;
        if (number < p)
        {
          high = n;
        }
        else if (number > p)
        {
          low = n;
        }
        else
        {
          break;
        }
      }
      return number == p ? n : low;
    }

    // javascript port
    public static BigInteger SqrtJeremyKahan(BigInteger n)
    {
      var oddNumber = BigInteger.One;
      var result = BigInteger.Zero;
      while (n >= oddNumber)
      {
        n -= oddNumber;
        oddNumber += 2;
        result++;
      }
      return result;
    }

    // CORDIC
    public static BigInteger SqrtNordicMainframe(BigInteger x)
    {
      int b = Convert.ToInt32(Math.Ceiling(BigInteger.Log(x, 2))) / 2 + 1;
      BigInteger r = 0; // r will contain the result
      BigInteger r2 = 0; // here we maintain r squared
      while (b >= 0)
      {
        var sr2 = r2;
        var sr = r;
        // compute (r+(1<<b))**2, we have r**2 already.
        r2 += (r << 1 + b) + (BigInteger.One << b + b);
        r += BigInteger.One << b;
        if (r2 > x)
        {
          r = sr;
          r2 = sr2;
        }
        b--;
      }
      return r;
    }

    static void Main(string[] args)
    {
      var t2 = BigInteger.Parse("2" + new string('0', 10000));

      //var q1 = SqrtRedGreenCode(t2);
      var q2 = SqrtSunsetquest(t2);
      //var q3 = SqrtJeremyKahan(t2);
      //var q4 = SqrtNordicMainframe(t2);
      var q5 = SqrtMax(t2);
      //if (q5 != q1) throw new Exception();
      if (q5 != q2) throw new Exception();
      //if (q5 != q3) throw new Exception();
      //if (q5 != q4) throw new Exception();

      for (int r = 0; r < 5; r++)
      {
        var mess = Stopwatch.StartNew();
        //for (int i = 0; i < 1000; i++)
        {
          //var q = SqrtRedGreenCode(t2);
          var q = SqrtSunsetquest(t2);
          //var q = SqrtJeremyKahan(t2);
          //var q = SqrtNordicMainframe(t2);
          //var q = SqrtMax(t2);
        }
        mess.Stop();
        Console.WriteLine((mess.ElapsedTicks * 1000.0 / Stopwatch.Frequency).ToString("N2") + " ms");
      }
    }
  }
}

答案 5 :(得分:1)

简答:(但要注意,请参阅下面的详细信息)

Math.Pow(Math.E, BigInteger.Log(pd) / 2)

其中pd代表您要执行平方根操作的BigInteger

长期答案和解释:

理解这个问题的另一种方法是了解平方根和日志的工作原理。

如果您使用公式5^x = 25,要解决x,我们必须使用日志。在这个例子中,我将使用自然日志(其他基础中的日志也是可能的,但自然日志是简单的方法)。

5^x = 25

重写,我们有:

x(ln 5) = ln 25

为了隔离x,我们有

x = ln 25 / ln 5

我们在x = 2中看到此结果。但是既然我们已经知道x(x = 2,在5 ^ 2中),那么让我们改变我们不知道的东西并编写一个新的方程并求解新的未知数。设x是平方根运算的结果。这给了我们

2 = ln 25 / ln x

重写以隔离x,我们有

ln x = (ln 25) / 2

要删除日志,我们现在使用自然日志的特殊标识和特殊编号 e 。具体来说,e^ln x = x。重写方程现在给我们

e^ln x = e^((ln 25) / 2)

简化左侧,我们有

x = e^((ln 25) / 2)

其中x将是25的平方根。您也可以将此想法扩展到任何根或数字,x的第y个根的通用公式变为e^((ln x) / y)

现在专门针对C#,BigIntegers和这个问题,我们只是实现了这个公式。 警告:虽然数学是正确的,但是有限制。这种方法只会让你进入附近,有一个很大的未知范围(取决于你操作的数字有多大)。也许这就是微软没有实现这种方法的原因。

// A sample generated public key modulus
var pd = BigInteger.Parse("101017638707436133903821306341466727228541580658758890103412581005475252078199915929932968020619524277851873319243238741901729414629681623307196829081607677830881341203504364437688722228526603134919021724454060938836833023076773093013126674662502999661052433082827512395099052335602854935571690613335742455727");
var sqrt = Math.Pow(Math.E, BigInteger.Log(pd) / 2);

Console.WriteLine(sqrt);

注意: BigInteger.Log()方法返回一个double,因此会出现两个问题。 1)数字不精确,2)Log()输入BigInteger可以处理的内容有一个上限。要检查上限,我们可以查看自然日志的正常形式,即ln x = y。换句话说,e^y = x。由于double是[{1}}的返回类型,因此最大的BigInteger.Log() e 提升到BigInteger。在我的电脑上,那将double.MaxValue。不精确是不妥当的。任何人都希望实施e^1.79769313486232E+308并更新BigDecimal

消费者要小心,但它会让你进入邻居,并且平方结果会产生一个类似于原始输入的数字,最多可达到这么多数字,而不像RedGreenCode的答案那么精确。快乐(正方形)生根! ;)

答案 6 :(得分:1)

已经将近10年了,但是希望这会对某人有所帮助。这是我一直在使用的那个。它不使用任何慢速除法。

//source: http://mjs5.com/2016/01/20/c-biginteger-square-root-function/  Michael Steiner, Jan 2016
public static BigInteger Sqrt(BigInteger number)
{
    BigInteger n = 0, p = 0;
    if (number == BigInteger.Zero)
    {
        return BigInteger.Zero;
    }
    var high = number >> 1;
    var low = BigInteger.Zero;

    while (high > low + 1)
    {
        n = (high + low) >> 1;
        p = n* n;
        if (number < p)
        {
            high = n;
        }
        else if (number > p)
        {
            low = n;
        }
        else
        {
            break;
        }
    }
    return number == p? n : low;
}

答案 7 :(得分:0)

以下两种方法使用巴比伦方法计算所提供数字的平方根。 Sqrt 方法返回 BigInteger 类型,因此只会提供最后一个整数的答案(无小数点)。

该方法将使用 15 次迭代,虽然经过几次测试,我发现 12-13 次迭代足以处理 80 多个数字,但我决定将其保持在 15 次以防万一。

由于巴比伦平方根近似方法要求我们选择一个长度为我们想要求平方根的数字长度的一半的数字,因此 RandomBigIntegerOfLength() 方法提供了该数字。

RandomBigIntegerOfLength() 将数字的整数长度作为参数,并提供该长度随机生成的数字。该数字是使用 Random 类中的 Next() 方法生成的,Next() 方法被调用两次以避免数字开头为 0(类似于 041657180501613764193159871),因为它导致 DivideByZeroException。需要指出的是,最初是将数字一一生成,连接起来,然后才将其从字符串转换为 BigInteger 类型。

Sqrt 方法使用 RandomBigIntegerOfLength 方法获取提供的参数“number”一半长度的随机数,然后使用巴比伦方法进行 15 次迭代计算平方根。迭代次数可以根据需要更改为更小或更大。由于巴比伦方法不能提供 0 的平方根,因为它需要除以 0,如果提供 0 作为参数,它将返回 0。

//Copy the two methods
public static BigInteger Sqrt(BigInteger number)
{
    BigInteger _x = RandomBigIntegerOfLength((number.ToString().ToCharArray().Length / 2));
    
    try
    {
        for (int i = 0; i < 15; i++)
        {
            _x = (_x + number / _x) / 2;
        }
        return _x;
    }
    catch (DivideByZeroException)
    {
        return 0;
    }
}

// Copy this method as well
private static BigInteger RandomBigIntegerOfLength(int length)
{
    Random rand = new Random();

    string _randomNumber = "";
    
    _randomNumber = String.Concat(_randomNumber, rand.Next(1, 10));

    for (int i = 0; i < length-1; i++)
    {
        _randomNumber = String.Concat(_randomNumber,rand.Next(10).ToString());
    }
    if (String.IsNullOrEmpty(_randomNumber) == false) return BigInteger.Parse(_randomNumber);
    else return 0;
}