Реализовать двойной sqrt (двойной x) в С++

Реализовать double sqrt(double x) на C++ без использования стандартной библиотеки.

Это вопрос интервью в Facebook, который я видел здесь. http://www.glassdoor.com/Interview/Implement-double-sqrt-double-x-in-C-QTN_87210.htm Любая другая хорошая идея по этому поводу?...

!!!Отредактировано.!!!(без использования стандартной библиотеки.)


person Josh Morrison    schedule 15.02.2011    source источник
comment
#include <cmath> [новая строка] double sqrt(double x) { return std::sqrt(x); }   -  person James McNellis    schedule 15.02.2011
comment
@James: я думал #include <cmath> [новая строка] double sqrt(double x) { return std::pow(x, 0.5); }   -  person Fred Larson    schedule 15.02.2011
comment
en.wikipedia.org/wiki/Newton%27s_method   -  person Anycorn    schedule 15.02.2011
comment
en.wikipedia.org/wiki/Methods_of_computing_square_roots   -  person Guy Sirton    schedule 15.02.2011
comment
Наверное, это один из самых глупых вопросов на собеседовании. Им нужен кто-то, способный на C++, или кто-то, знающий алгоритмы, которые можно легко найти?   -  person sstn    schedule 15.02.2011
comment
Это не вопрос по программированию, это вопрос по теории математики   -  person thecoshman    schedule 05.10.2012


Ответы (4)


Посмотрите здесь. В этой статье CodeProject сравниваются 14 различных методов вычисления квадратного корня.

person Lior Kogan    schedule 15.02.2011

Два очевидных ответа: деление пополам (полумедленное) и итерация Ньютона-Рафсона/Лейбница (обычно быстрее). Чтобы никому не портить удовольствие, я проведу реинтерпретацию по этому вопросу - вот реализация целочисленного квадратного корня на языке ассемблера 8086 с использованием техники Ньютона-Рафсона:

isqrt proc uses di, number:word
;
; uses bx, cx, dx
;
    mov di,number
    mov ax,255
start_loop:
    mov bx,ax
    xor dx,dx
    mov ax,di
    div bx
    add ax,bx
    shr ax,1
    mov cx,ax
    sub cx,bx
    cmp cx,2
    ja  start_loop
    ret
isqrt endp

Это открыто для некоторых улучшений - он использует x/2 в качестве исходного предположения о sqrt(x). С более чем 386 инструкциями вы можете использовать bsr, чтобы найти старший бит, установленный для получения грубого приближения log2x, и разделить его на 2, чтобы получить начальное приближение.

OTOH, это действительно имело смысл только на древних процессорах. Для всего, начиная с 486 (или около того), которое имеет встроенное оборудование с плавающей запятой, почти наверняка инструкция FSQRT превзойдет это (или почти все, что вы можете написать).

person Jerry Coffin    schedule 15.02.2011
comment
насколько мне известно, использование подхода Reciprocal en.wikipedia.org/wiki/ является быстрее, вероятно, потому, что он заменяет зацикливание умными хаками. Хотя я не уверен в его точности. - person Matthieu M.; 15.02.2011
comment
@Matthieu M.: Да, если вы измените проблему, вы можете немного повысить скорость. Большинство из них также делают очень грубую аппроксимацию, которая хороша только для 10 или 12 бит (что по-прежнему составляет ~ 1 пиксель при типичных разрешениях). Однако для реального квадратного корня действительно трудно превзойти выделенное оборудование. - person Jerry Coffin; 15.02.2011
comment
да, очевидно, но я имел в виду функцию сборки, которую вы написали :) Я использовал подход Reciprocal с идеальной точностью для целых чисел, и я думаю, что добавление некоторых итераций Ньютона несколько улучшит точность. Что интересно, так это получить хорошее первое приближение в любом случае, вы можете уточнить точность в зависимости от ваших потребностей. - person Matthieu M.; 15.02.2011

Вот одна из самых гениальных реализаций sqrt, которую можно найти в википедии. Он не самый точный, но чрезвычайно быстрый.

float fast_sqrt(float number) {
   long i;
   float x2, y;
   const float threehalfs = 1.5F;

   x2 = number * 0.5F;
   y  = number;
   i  = * ( long * ) &y;                     // floating point bit level hacking [sic]
   i  = 0x5f3759df - ( i >> 1 );             // Newton's approximation
   y  = * ( float * ) &i;
   y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
   y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration
   y  = y * ( threehalfs - ( x2 * y * y ) ); // 3rd iteration

   return 1/y;
}
person regality    schedule 15.02.2011
comment
И вы хотите сказать нам, что, по вашему мнению, это должен был быть ответ на собеседовании? - person Tony Delroy; 15.02.2011
comment
Он хотел эту работу? Вы должны попытаться произвести на них впечатление во время интервью. - person regality; 15.02.2011
comment
@Tony: я бы использовал методологию быстрой функции обратного квадратного корня из Quake - эта формула потрясающая - знаете ли вы это? наверное хватило бы. (хотя технически это делает 3 прохода приближения Ньютона-Рафсона вместо 1... так что это не совсем одно и то же) - person James; 15.02.2011
comment
о да, я добавил их для большей точности и забыл убрать. в принципе то же самое. - person regality; 15.02.2011
comment
@regality: я использовал этот подход для целых чисел, и для 32 бит одной итерации было достаточно для 100% точности (выбраны случайные числа и включая ребра), проверяли ли вы дополнительные итерации для float ? - person Matthieu M.; 15.02.2011
comment
@James, @regality: выплюнь это из памяти, и это вполне может произвести впечатление. Но чего они могут ожидать? Если это не та работа, где можно ожидать такого уровня математики, или sqrt особенно актуален, тогда просто рабочая реализация практического, основанного на здравом смысле подхода, такого как последовательное приближение. Тот факт, что это постоянная степень 0,5, а не логарифм или другая функция, вероятно, случайный. Ссылка на Quake показалась бы мне любопытной, если бы вы не могли объяснить алгоритм, точно так же, как C++ boost::any дает, что типизация во время выполнения не стоит многого без понимания реализации. - person Tony Delroy; 15.02.2011
comment
Хорошо, я надеюсь, что вы не возражаете против использования BoneOS. Мы указали на его оригинальность. Источник github.com/Bone-Project/BoneOS /blob/amanuel_progress_kbd/libc/ - person amanuel2; 08.11.2016

Если мне разрешено использовать log (ln) и exp, то, конечно, exp(log(x)/2) даст мне квадратный корень.

Предполагая, что нет:

Если наше значение, которое мы находим, sqrt равно x, а начальное значение равно y, тогда мы повторяем y->(y+x/y)/2

Завершающим условием будет либо близость y к его предыдущему значению, либо y*y к x.

С 385 в качестве значения x я получаю эти значения в своих итерациях (Excel)

1
193
97.49740933
50.7231161
29.15667189
21.1805984
19.67880541
19.62150055
19.62141687
19.62141687

Вы можете использовать «приблизительное» 2 ^ (логарифмическое основание 2 (x) / 2) в качестве начальной точки вместо 1. 385 имеет логарифм где-то между 8 и 9, поэтому, если мы скажем 8,5 и, следовательно, начнем с 2 ^ 4,25. Если мы сделаем это линейно между 16 и 32, то мы начнем с 20.

Начиная с 20 я добираюсь туда всего за 4 шага:

20
19.625
19.6214172
19.62141687

но для вычисления приблизительного логарифма и экспоненты требовались предыдущие «итерации».

person CashCow    schedule 15.02.2011