C# 平方根的快速逼近?
我正在写一个程序,里面有一些计算。虽然我的问题看起来很模糊,也许是可以避免的,但事实并非如此,即使是这样,我现在也不想回避它 问题是我有一个巨大的数字(数千位,在某个点上可能是数百万位),我需要为它找到一个近似的SQRT。我使用的是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
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;
}