我正在编写一个正在编写的程序中进行一些计算。虽然我的问题可能看起来模糊不清,也许是可以避免的,但事实并非如此,即使是这样,我现在也不想避开它:-D
问题是我有一个巨大的数字(数千位,在某些时候可能有数百万),我需要找到一个近似的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;
}
答案 0 :(得分:4)
使用我的回答here来计算Log 2 (N),我准备了一个测试用例。
虽然您的代码更接近实际平方根,但当输入数字变大时,我的测试显示数百万次加速。
我认为您必须在更精确的结果与数千/数百万次加速之间做出决定,
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));
BigInteger.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);
}
}
测量速度的代码
long Measure(int n,Action action)
{
action();
var sw = Stopwatch.StartNew();
for (int i = 0; i < n; i++)
{
action();
}
return sw.ElapsedMilliseconds;
}
<强>结果:强>
在我的计算机中,两个代码都会在8秒内生成,但我发布的代码运行10*200000
次,比较代码只运行10次。 (是的,很难相信,对于8000位数字,差异是200,000
次)
当与16000位数进行比较时,差异会增加到1,000,000
次......
答案 1 :(得分:3)
这是粗略的近似值,可以让你在平方根的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);
}
不是以n
作为近似值(newValue = n;
)开始,而是先调用此(newValue = RoughSqrt(n);
)。这将以更少的迭代次数为您提供答案。
如果你想取第N个根,你只需要改变粗略近似值以使用1 / N的位并改变牛顿方法以使用x^N
的导数:
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;
}
答案 2 :(得分:3)
首先,以近似形式写下您的号码
a = n.10 2m
n
是一个整数且大到可以使系统sqrt
可用于其大小。 m
也是一个整数。点意味着相乘。
您应该可以从初始整数中的位数和m
中的位数获得n
。
请注意,这永远不会大于给定的数字(因为我们已将许多较低有效数字设置为0):因此满足答案的要求是下限。
平方根
a 1/2 = n 1/2 .10 m
计算速度快:第一部分使用系统sqrt
;第二部分是一个微不足道的乘法;完全在BigInteger
范围内。
此方法的准确性接近浮点精度,其速度与数字的大小无关。
答案 3 :(得分:2)
√ N ≈ ½(N/A + A)
这是牛顿平方根近似 - 发现使用... Google :-P
修改强>
以下功能返回
7.9025671444237E + 89&lt; -approx sqrt 7.9019961626505E + 89&lt; -actual sqrt
代表
624415433545435435343754652464526256453554543543254325247654435839457324987585439578943795847392574398025432658763408563498574398275943729854796875432457385798342759843263456484364
以下是我提出的建议(为自己的逻辑感到骄傲 - 而不是编码:-P)
好的,这是PHP(这很有趣:-D)但应该很容易转换。
看起来很可怕但不是
找到前3/4位数(取决于字符串长度)。
使用最接近的正方形技术找到这些数字的大约sqrt(例如,找到45的大约sqrt,我们知道最接近的整数正方形(正方形 - 遗忘项)是6(36)和7(49) - 和45在36和49之间的大约70% - 所以6.7是很好的近似值)
坚持使用牛顿方形近似公式。
我所写的是一种非常丑陋的方式 - 完成所有步骤 - 但实际上它更容易看到思维模式(我希望)。
function approxSqrRt($number){
$numLen = strlen($number);//get the length of the number - this is used to work out whether to get the first 3 or 4 digits from the number.
$testNum = 0;
$zerosToAdd = 0;
//find out if even or odd number of digits as 400 and 40,000 yield same start 20 -> 200 BUT 4000 yields 63.245
if($numLen % 2 == 0){
$testNum = substr($number, 0, 4);
$zerosToAdd = ($numLen - 4) / 2;
}else{
$testNum = substr($number, 0, 3);
$zerosToAdd = ($numLen - 3) / 2;
}
$square1 = 0;
$x = 0;
$z = false;
//this should be made static array and look up nearest value but for this purpose it will do.
// It is designed to find the nearest 2 squares - probably a much better way but was typing as I thought.
while ($z == false){
$x++;
if(($x*$x) >= $testNum){
$square1 = $x - 1;
$z = true;
}
}
$square2 = $square1 + 1;
$ratio = 0;
//ratio - we are simply qorking out if our square is closer to $square1 or $square2 and how far it is in between them - this will yield a much more accurate approximation.
//
if($testNum != ($square1 * $square1)){
if($testNum != ($square2 * $square2)){
$topOfEquation = $testNum;
$BottomOfEquation = $square2 * $square2;
$ratio = ($topOfEquation / $BottomOfEquation);
}else{
$ratio = 1;
}
}else{
$ratio = 0;
}
//get the final approximation before adding the relevant number of 0's.
$testNumSqrRt = $square1 + $ratio;
//add the 0's on to make the number the correct length.
$testNumberFinal = $testNumSqrRt * pow(10, $zerosToAdd);
//run through Newtons Approximation.
$approxSqrtFinal = (($number / $testNumberFinal) + $testNumberFinal) / 2;
return $approxSqrtFinal;
}
$num = "624415433484364";
echo approxSqrRt($num) . "<br/>";
echo sqrt($num);
答案 4 :(得分:0)
Looking for an efficient integer square root algorithm for ARM Thumb2
无论如何,算法必须使用移位,+ / - 操作代替除法。
此外,如果您使用汇编语言实现例程,则可能会获得一些额外的优化。有关在C#中使用程序集的信息,请参阅:
http://www.codeproject.com/Articles/1392/Using-Unmanaged-code-and-assembler-in-C
答案 5 :(得分:0)
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;
}
参考:http://www.softwareandfinance.com/CSharp/Find_SQRT_Approximation.html