double _sqrt(double n)
{
double sqrt = 0, c = 0, prec = 1;
for (;; sqrt += prec) //increments sqrt per precision
{
c = sqrt * sqrt;
if (c == n)
{
return sqrt;
}
if (c > n) // if square is greater then..
{
sqrt -= prec; // decrement squareroot by previous precision
prec *= 0.1; // increase precision eg. 1, 0.1, 0.01, 0.001 .....INF
}
}
}
Это функция квадратного корня, которую я сделал. Он работает для некоторых чисел, но для других он просто пуст, ничего не возвращает. Где я ошибаюсь?
Ваш компилятор должен был предупредить, что не все пути управления возвращают значение.
Сравнение двойников на точное равенство редко работает. Вам нужно сравнить абсолютную разницу с допуском, чтобы определить «достаточно близко».
if (c == n)
проблематична в математике с плавающей запятой. Здесь вы можете использовать некоторое значение эпсилон.
Не помечайте C для вопросов C++.
Проголосовали за открытие. Вопрос вполне ясен, и проблема явно не в результате опечатки. Исправление было предложено в двух комментариях, за оба проголосовали.
@πάνταῥεῖ -- не вижу; единственный обратный путь — через оператор return sqrt;
. Проблема, конечно, в потенциальном бесконечном цикле, но это не еще один обратный путь.
Числа с плавающей запятой не точны, они являются лишь приближением к реальным значениям (только столько битов для хранения информации). Это означает, что при выполнении расчетов всегда присутствуют небольшие ошибки либо в представлении числа, либо в самом расчете. Также см.: не работает математика с плавающей запятой. И именно из этого исходит ответ @RetiredNinja.
Если бы double
имел неограниченную точность, это заняло бы бесконечное количество времени для всех иррациональных корней, и вам все равно пришлось бы довольствоваться «достаточно близким» результатом.
1) Возможно, у вас есть бесконечный цикл; как сказали ниндзя на пенсии и drescherjm, ==
не рекомендуется для парного разряда. 2) Ньютон-Рафсон — лучший алгоритм и вполне может быть быстрее.
Спасибо всем за ответ. Итак, @rossum существуют ли другие алгоритмы для таких сравнений, которые мне следует искать, чтобы решить эту проблему. Я проверю Ньютон-Рафсон, спасибо!
См. комментарий отставного ниндзя. Вам нужно выбрать небольшой допуск, дельту, и сравнить с ним: if (abs(c - n) < delta) then ...
В вашем коде есть несколько проблем, первая из которых заключается в том, что ваш код может бесконечно зацикливаться, когда вы пытаетесь получить бесконечную точность для (возможно) иррационального числа. Хотя двойные числа не имеют бесконечной точности, я, конечно, не рекомендовал бы пытаться оценить эту функцию с такой высокой степенью точности. Решение этой проблемы состоит в том, чтобы добавить какое-то значение «точность» или «эпсилон» (как указано в комментариях ниже). Или даже лучше выйти из цикла, когда increment
станет слишком маленьким (как предложил @Pete Becker):
double _sqrt(const double& n)
{
const double precision = 0.000001;
double increment = 1.0, sqrt = 0.0;
while (true)
{
if (increment <= precision)
break;
else if (sqrt * sqrt > n)
{
sqrt -= increment;
increment *= 0.1;
}
else
sqrt += increment;
}
return sqrt;
}
Вы, конечно, можете сделать это со стандартной библиотекой, используя std::sqrt, но я предполагаю, что вы делаете это для развлечения.
Если вас интересуют другие алгоритмы извлечения квадратного корня, в Википедии есть несколько страшных на вид здесь.
Лично мне нравится Метод Ньютона , но он для нахождения корней функций вообще. Применение этого для функции квадратного корня, которое я скопировал и изменил из здесь:
double _sqrt(const double& n)
{
const double precision = 0.00001;
double sqrt, x = n;
while (true)
{
sqrt = 0.5 * (x + (n / x));
if (std::abs(sqrt - x) < precision)
break;
x = sqrt;
}
return sqrt;
}
Другая возможность в первой версии — выйти из цикла, когда increment
станет меньше некоторого предела.
Я полагаю, что ваше предложение на самом деле намного лучше, поскольку происходит определенно меньше. Я обновлю свой ответ, чтобы включить его.
Хороший и точный ответ! Большое спасибо.
То, что вы пытаетесь сделать, похоже на бинарный поиск. Моя главная забота связана с prec = 1;
и sqrt += prec
, потому что, как только sqrt
действительно большое число, выражение sqrt += prec
никак не изменит sqrt
из-за округления, и ваш цикл for застрянет...
поэтому вам нужно понять начальное значение prec
и во сколько раз точность должна увеличиться на prec *= 0.1;
для чисел |x|>1
теперь у величины sqrt(x)
есть половина целочисленных битов, которые необходимы для int(x)
, поэтому мы можем получить prec
из этого
y = log2(|x|)
prec = y*0.1
log2
не обязательно должно быть реальным log2
вы можете просто взять показатель степени и разделить его на 2 и построить y как:
y = 2^((exponent_extracted_from_x>>1)+1)
для (|x|<=1)
начните, например, с: prec = 0.5
Теперь конечным условием, которое вам не хватает, является остановка, как только sqrt
перестанет меняться, например:
...
for (sqrt0=sqrt-prec; sqrt!=sqrt0; sqrt0=sqrt,sqrt+=prec)
{
... your original code ...
}
return sqrt;
}
Также я не вижу никакой обработки знаков (знаете на всякий случай x<0
)
для получения дополнительной информации см.:
Действительно полезно. Большое спасибо. Я мог бы заставить вас ответить, если бы у меня было две квоты ответов.
Не все ваши пути в этой функции возвращают значение, поэтому у вас неопределенное поведение. Все может случиться.