Я знаю, это звучит как домашнее задание, но это не так. В последнее время меня интересуют алгоритмы, используемые для выполнения некоторых математических операций, таких как синус, квадратный корень и т. д. В данный момент я пытаюсь написать вавилонский метод вычисления квадратных корней в C#.
Пока что у меня это:
public static double SquareRoot(double x) {
if (x == 0) return 0;
double r = x / 2; // this is inefficient, but I can't find a better way
// to get a close estimate for the starting value of r
double last = 0;
int maxIters = 100;
for (int i = 0; i < maxIters; i++) {
r = (r + x / r) / 2;
if (r == last)
break;
last = r;
}
return r;
}
Он отлично работает и каждый раз дает точно такой же ответ, что и метод Math.Sqrt() .NET Framework. Однако, как вы, наверное, догадались, это медленнее, чем нативный метод (примерно на 800 тиков). Я знаю, что этот конкретный метод никогда не будет быстрее, чем собственный метод, но мне просто интересно, есть ли какие-либо оптимизации, которые я могу сделать.
Единственная оптимизация, которую я увидел сразу, заключалась в том, что расчет выполнялся 100 раз, даже после того, как ответ уже был определен (в этот момент r всегда будет одним и тем же значением). Итак, я добавил быструю проверку, чтобы увидеть, совпадает ли вновь вычисленное значение с ранее рассчитанным значением, и выйти из цикла. К сожалению, это не сильно повлияло на скорость, но просто казалось правильным поступить.
И прежде чем вы скажете: "Почему бы просто не использовать вместо этого Math.Sqrt()?"... Я делаю это в качестве учебного упражнения и не собираюсь фактически использовать этот метод в каком-либо производственном коде.
Ответ 6 · рейтинг 2 · Eric Bainville
С вашим методом каждая итерация удваивает количество правильных битов.
Используя таблицу для получения начальных 4 битов (например), у вас будет 8 бит после 1-й итерации, затем 16 бит после второй и все биты, которые вам нужны, после четвертой итерации (поскольку double хранит 52+1 бит мантиссы).
Для поиска в таблице вы можете извлечь мантисса в [0,5,1[ и показатель степени из входных данных (используя функцию, например frexp), а затем нормализовать мантисса в [64,256[, используя умножение на подходящую степень 2.
mantissa *= 2^K
exponent -= K
После этого ваш входной номер по-прежнему будет mantissa*2^exponent. K должен быть равен 7 или 8, чтобы получить четный показатель степени. Вы можете получить начальное значение для итераций из таблицы, содержащей все квадратные корни целой части мантиссы. Выполните 4 итерации, чтобы получить квадратный корень r из мантиссы. Результатом является r*2^(exponent/2), построенный с использованием функции, подобной ldexp.
РЕДАКТИРОВАТЬ. Ниже я привожу код C++, чтобы проиллюстрировать это. Функция OP sr1 с улучшенным тестом занимает 2,78 с для вычисления 2 ^ 24 квадратных корней; моя функция sr2 занимает 1,42 с, а аппаратный sqrt - 0,12 с.
#include <math.h>
#include <stdio.h>
double sr1(double x)
{
double last = 0;
double r = x * 0.5;
int maxIters = 100;
for (int i = 0; i < maxIters; i++) {
r = (r + x / r) / 2;
if ( fabs(r - last) < 1.0e-10 )
break;
last = r;
}
return r;
}
double sr2(double x)
{
// Square roots of values in 0..256 (rounded to nearest integer)
static const int ROOTS256[] = {
0,1,1,2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,
9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,
12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 };
// Normalize input
int exponent;
double mantissa = frexp(x,&exponent); // MANTISSA in [0.5,1[ unless X is 0
if (mantissa == 0) return 0; // X is 0
if (exponent & 1) { mantissa *= 128; exponent -= 7; } // odd exponent
else { mantissa *= 256; exponent -= 8; } // even exponent
// Here MANTISSA is in [64,256[
// Initial value on 4 bits
double root = ROOTS256[(int)floor(mantissa)];
// Iterate
for (int it=0;it<4;it++)
{
root = 0.5 * (root + mantissa / root);
}
// Restore exponent in result
return ldexp(root,exponent>>1);
}
int main()
{
// Used to generate the table
// for (int i=0;i<=256;i++) printf(",%.0f",sqrt(i));
double s = 0;
int mx = 1<<24;
// for (int i=0;i<mx;i++) s += sqrt(i); // 0.120s
// for (int i=0;i<mx;i++) s += sr1(i); // 2.780s
for (int i=0;i<mx;i++) s += sr2(i); // 1.420s
}