C# 平方根的快速逼近?

C# 平方根的快速逼近?,c#,biginteger,approximation,square-root,C#,Biginteger,Approximation,Square Root,我正在写一个程序,里面有一些计算。虽然我的问题看起来很模糊,也许是可以避免的,但事实并非如此,即使是这样,我现在也不想回避它 问题是我有一个巨大的数字(数千位,在某个点上可能是数百万位),我需要为它找到一个近似的SQRT。我使用的是System.Numerics.BigInteger,我需要近似值小于或等于实际SQRT。例如,如果提供的数字是100,我会很高兴有7、8、9或10,而不是11 当前代码: public static BigInteger IntegerSqrt(BigInteger

我正在写一个程序,里面有一些计算。虽然我的问题看起来很模糊,也许是可以避免的,但事实并非如此,即使是这样,我现在也不想回避它

问题是我有一个巨大的数字(数千位,在某个点上可能是数百万位),我需要为它找到一个近似的SQRT。我使用的是
System.Numerics.BigInteger
,我需要近似值小于或等于实际SQRT。例如,如果提供的数字是100,我会很高兴有7、8、9或10,而不是11

当前代码:

public static BigInteger IntegerSqrt(BigInteger n)
{
    var oldValue = new BigInteger(0);
    BigInteger newValue = n;

    while (BigInteger.Abs(newValue - oldValue) >= 1)
    {
        oldValue = newValue;
        newValue = (oldValue + (n / oldValue)) / 2;
    }

    return newValue;
}
虽然这给了我正确的答案(据我所知),但它的速度非常慢,因为它试图得到一个精确的答案

如果得到立方根或者其他类似的东西会更快,我会很高兴的。另外,是的,我知道快速找到平方根可以让你赚很多钱,但这并不是我想做的,我只是想要一个快速的近似值

你能给我的任何帮助都太好了


编辑-Gabe

这是我正在使用的更新后的IntegerSqrt函数,它似乎不能更快地工作

public static BigInteger IntegerSqrt(BigInteger n)
{
    var oldValue = n.RoughSqrt();
    BigInteger newValue = n;
    while (BigInteger.Abs(newValue - oldValue) >= 1)
    {
        oldValue = newValue;
        newValue = (oldValue + (n / oldValue)) / 2;
    }

    return newValue;
}

编辑2 你是这么想的吗我在一个大样本(50k位)上运行了30分钟,循环了100次,但没有完成。我觉得我好像错过了什么

public static BigInteger IntegerSqrt(BigInteger n)
{
    var oldValue = new BigInteger(0);
    BigInteger newValue = n.RoughSqrt();
    int i = 0;
    while (BigInteger.Abs(newValue - oldValue) >= 1)
    {
        oldValue = newValue;
        newValue = (oldValue + (n / oldValue)) / 2;
        i++;
    }
    return newValue;
}
这是牛顿平方根近似-使用…Google:-p找到

编辑

下面的函数返回

7.9025671444237E+89

无论如何,算法必须使用移位、+/-运算,而不是除法

此外,如果用汇编语言实现该例程,可能会得到一些额外的优化。有关在C#中使用汇编,请参见:


这里是一个粗略的近似值,它将使您的平方根的系数在2以内:

System.Numerics.BigInteger RoughSqrt(System.Numerics.BigInteger x)
{
    var bytes = x.ToByteArray();    // get binary representation
    var bits = (bytes.Length - 1) * 8;  // get # bits in all but msb
    // add # bits in msb
    for (var msb = bytes[bytes.Length - 1]; msb != 0; msb >>= 1)
        bits++;
    var sqrtBits = bits / 2;    // # bits in the sqrt
    var sqrtBytes = sqrtBits / 8 + 1;   // # bytes in the sqrt
    // avoid making a negative number by adding an extra 0-byte if the high bit is set
    var sqrtArray = new byte[sqrtBytes + (sqrtBits % 8 == 7 ? 1 : 0)];
    // set the msb
    sqrtArray[sqrtBytes - 1] = (byte)(1 << (sqrtBits % 8));
    // make new BigInteger from array of bytes
    return new System.Numerics.BigInteger(sqrtArray);
}

首先,用近似的形式写下你的号码

a=n.102米

其中,
n
是一个整数,它的大小取决于系统
sqrt
m
也是一个整数。这个点表示乘法

您应该能够从初始整数中的位数和
n
中的位数中获得
m

请注意,这永远不会大于给定的数字(因为我们已将许多低有效数字设置为0):因此满足答案为下限的要求

平方根是

a1/2=n1/2.10m

计算速度很快:第一部分使用系统
sqrt
;第二部分是一个平凡的乘法;在
biginger
的范围内

这种方法的精度接近浮点精度,其速度与数字大小无关。

使用我的答案计算Log2(N),我准备了一个测试用例

虽然您的代码更接近真正的平方根,但我的测试显示,当输入数字越来越大时,速度会提高数百万倍

我认为您必须在更精确的结果与数千/数百万倍的加速之间做出决定

biginger.Sqrt

public static class BigIntegerExtensions
{
    static int[] PreCalc = new int[] { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
    static int Log2(this BigInteger bi)
    {
        byte[] buf = bi.ToByteArray();
        int len = buf.Length;
        return len * 8 - PreCalc[buf[len - 1]] - 1;
    }

    public static BigInteger Sqrt(this BigInteger bi)
    {
        return new BigInteger(1) << (Log2(bi) / 2);
    }
}
公共静态类BigIntegerExtensions
{
静态int[]PreCalc=新int[]{ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
静态int Log2(这个BigInteger bi)
{
字节[]buf=bi.ToByteArray();
int len=基本长度;
返回len*8-预信用证[buf[len-1]]-1;
}
公共静态BigInteger Sqrt(此BigInteger bi)
{
返回新的BigInteger(1)
静态公共双SQRTByGuess(双数值)
{
//假设该数字小于1000000,则SQRT的最大值为1000。
//假设结果为1000。如果需要,可以增加此限制
双最小值=0,最大值=1000;
双答案=0;
双重检验=0;
if(num<0)
{
Console.WriteLine(“不允许使用负数”);
返回-1;
}
else if(num==0)
返回0;
while(true)
{
测试=(最小+最大)/2;
答案=测试*测试;
如果(数值>答案)
{
//min需要移动
最小值=试验;
}
else if(num(回答-0.0001)&&
数值<(答案+0.0001))
打破
}
回归试验;
}

参考资料:

但这需要我猜测实际的平方,对吗?我不能这么做…@Vijay:使用粗略近似(基于几何平均值)在我发布的维基百科链接中,我得到了粗略的猜测。然后是牛顿·拉弗森的一次迭代。通过一点技巧,你可以进行调整,这样你就不会超出值。如果它循环而未完成,你可能使用了负数作为测试用例。确保你首先对测试数字进行Abs或其他操作。你的近似函数是c失去了我所需要的,但由于数字长度的巨大变化,我怎么知道要截断到什么?还有,这是否涉及到保持SQRT结果小于或等于真实SQRT?谢谢,伊芙
System.Numerics.BigInteger RoughSqrt(System.Numerics.BigInteger x)
{
    var bytes = x.ToByteArray();    // get binary representation
    var bits = (bytes.Length - 1) * 8;  // get # bits in all but msb
    // add # bits in msb
    for (var msb = bytes[bytes.Length - 1]; msb != 0; msb >>= 1)
        bits++;
    var sqrtBits = bits / 2;    // # bits in the sqrt
    var sqrtBytes = sqrtBits / 8 + 1;   // # bytes in the sqrt
    // avoid making a negative number by adding an extra 0-byte if the high bit is set
    var sqrtArray = new byte[sqrtBytes + (sqrtBits % 8 == 7 ? 1 : 0)];
    // set the msb
    sqrtArray[sqrtBytes - 1] = (byte)(1 << (sqrtBits % 8));
    // make new BigInteger from array of bytes
    return new System.Numerics.BigInteger(sqrtArray);
}
static BigInteger RoughRoot(BigInteger x, int root)
{
    var bytes = x.ToByteArray();    // get binary representation
    var bits = (bytes.Length - 1) * 8;  // get # bits in all but msb
    // add # bits in msb
    for (var msb = bytes[bytes.Length - 1]; msb != 0; msb >>= 1)
        bits++;
    var rtBits = bits / root + 1;   // # bits in the root
    var rtBytes = rtBits / 8 + 1;   // # bytes in the root
    // avoid making a negative number by adding an extra 0-byte if the high bit is set
    var rtArray = new byte[rtBytes + (rtBits % 8 == 7 ? 1 : 0)];
    // set the msb
    rtArray[rtBytes - 1] = (byte)(1 << (rtBits % 8));
    // make new BigInteger from array of bytes
    return new BigInteger(rtArray);
}

public static BigInteger IntegerRoot(BigInteger n, int root)
{
    var oldValue = new BigInteger(0);
    var newValue = RoughRoot(n, root);
    int i = 0;
    // I limited iterations to 100, but you may want way less
    while (BigInteger.Abs(newValue - oldValue) >= 1 && i < 100)
    {
        oldValue = newValue;
        newValue = ((root - 1) * oldValue 
                    + (n / BigInteger.Pow(oldValue, root - 1))) / root;
        i++;
    }
    return newValue;
}
Random rnd = new Random();
var bi = new BigInteger(Enumerable.Range(0, 1000).Select(x => (byte)rnd.Next(16))
                                                 .ToArray());


var sqrt1 = bi.Sqrt().ToString();
var sqrt2 = IntegerSqrt(bi).ToString();

var t1 = Measure(10 * 200000, () => bi.Sqrt());
var t2 = Measure(10, () => IntegerSqrt(bi));
public static class BigIntegerExtensions
{
    static int[] PreCalc = new int[] { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
    static int Log2(this BigInteger bi)
    {
        byte[] buf = bi.ToByteArray();
        int len = buf.Length;
        return len * 8 - PreCalc[buf[len - 1]] - 1;
    }

    public static BigInteger Sqrt(this BigInteger bi)
    {
        return new BigInteger(1) << (Log2(bi) / 2);
    }
}
long Measure(int n,Action action)
{
    action(); 
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < n; i++)
    {
        action();
    }
    return sw.ElapsedMilliseconds;
}
        static public double SQRTByGuess(double num)
        {
            // Assume that the number is less than 1,000,000. so that the maximum of SQRT would be 1000.
            // Lets assume the result is 1000. If you want you can increase this limit
            double min = 0, max = 1000;
            double answer = 0;
            double test = 0;
            if (num < 0)
            {
                Console.WriteLine("Negative numbers are not allowed");
                return -1;
            }
            else if (num == 0)
                return 0;

            while (true)
            {
                test = (min + max) / 2;
                answer = test * test;
                if (num > answer)
                {
                    // min needs be moved
                    min = test;
                }
                else if (num < answer)
                {
                    // max needs be moved
                    max = test;
                }
                if (num == answer)
                    break;
                if (num > (answer - 0.0001) &&
                    num < (answer + 0.0001))
                    break;
            }
            return test;
        }