Обчислення вкладеного кореня в C


9

Мене попросили обчислити наступне вкладене вираз кореня, використовуючи лише рекурсію .

введіть тут опис зображення

Я написав код, який працює нижче, але вони дозволили нам використовувати лише одну функцію та 1 вхід nдля цієї мети, а не 2, як я використовував. Чи може хтось допомогти мені перетворити цей код на одну функцію, яка обчислить вираз? не можу використовувати будь-яку бібліотеку, крім функцій від <math.h>.

вихід для n = 10: 1.757932

double rec_sqrt_series(int n, int m) {
    if (n <= 0)
        return 0;
    if (m > n)
        return 0;
    return sqrt(m + rec_sqrt_series(n, m + 1));
}

double helper(int n) {
    return rec_sqrt_series(n, 1);
}

Чи може хтось допомогти мені перетворити цей код на одну функцію, яка обчислить вираз? що? Допоможе вам позбутися від helper?
4386427

Якщо аргументи невірні, я б зателефонував abort()(від <stdlib.h>), а не мовчки повернувся 0.
Каз

1
@chqrlieforyellowblockquotes Twisied. @pastaleg Як щодо марної рекурсії? double nested_root(unsigned n) { double x = 0.0; if (n > 0) { x = nested_root(0); for (unsigned i = n; i > 0; i--) { x = sqrt(i + x); } } return x; }
chux

1
@ chux-ReinstateMonica: так, простіше зловживання правилами.
chqrlie

2
@ Відкрити: Якщо метою завдання було фінансувати нерекурсивне вираження функції, воно, ймовірно, не вимагало б вирішити проблему за допомогою "лише рекурсії". Звичайно, простий цикл легко обчислить результат. Хоча я взагалі підозрілий, коли ці запитання розміщуються в Stack Overflow без фактичного тексту завдання.
Eric Eric Postpischil

Відповіді:


7

Використовуйте верхні біти nлічильника:

double rec_sqrt_series(int n)
{
    static const int R = 0x10000;
    return n/R < n%R ? sqrt(n/R+1 + rec_sqrt_series(n+R)) : 0;
}

Природно, що несправності , коли початкова nє Rабо більше. Ось більш складна версія, яка працює для будь-якого позитивного значення n. Це працює:

  • Коли nце від’ємник, він працює як у наведеній версії, використовуючи верхні біти для підрахунку.
  • Коли nє позитивним, якщо він менше R, він називає себе, -nщоб оцінити функцію, як описано вище. Інакше він називає себе R-1заперечним. Це оцінює функцію так, ніби вона була викликана R-1. Це дає правильний результат, оскільки серія перестає змінюватися у форматі з плаваючою комою після декількох десятків ітерацій - квадратні корені більш глибоких чисел настільки розбавлені, що вони не впливають. Тож функція має однакове значення для всіх nза малим порогом.
double rec_sqrt_series(int n)
{
    static const int R = 0x100;
    return
        0 < n ? n < R ? rec_sqrt_series(-n) : rec_sqrt_series(1-R)
              : n/R > n%R ? sqrt(-n/R+1 + rec_sqrt_series(n-R)) : 0;
}

Хороша ідея, але передбачається 32-розрядні інти :)
chqrlie

1
@chqrlieforyellowblockquotes: Ні, тому Rце окремо, тому його можна настроїти. Перед nдосягненням 32, повернене значення перестає змінюватися для IEEE-754 бінарного64, а до досягнення 256, повернене значення припиняє змінюватися для розумних форматів для double. Тож я розглядаю альтернативну версію, яка перетворює вхідні затискачі вище R, але для цього потрібно використовувати біт знаків, і я все ще працюю над цим.
Eric Postpischil

Є й інші функції створення пари, які ви можете використовувати, але жодна не така проста, як ваша. Їх головна перевага, як правило, полягає в тому, що вони працюють з довільною точністю, але ОП ніколи не згадував про це як вимогу.
Рууд Гельдерман

@chqrlieforyellowblockquotes: Готово. Тепер виробляє правильну відповідь на будь-який позитивний nнезалежно від ширини int.
Eric Eric Postpischil

5

Без математичної трансформації формули (я не знаю, чи це можливо), ви не можете по-справжньому використовувати лише один параметр, оскільки для кожного елемента потрібні дві інформації: поточний крок та оригінал n. Однак ви можете обдурити . Один із способів - це кодування двох чисел у intпараметрі (як показано Еріком ).

Інший спосіб - збереження оригіналу nв статичній локальній змінній. Під час першого дзвінка ми зберігаємо nв цій статичній змінній, ми починаємо рекурсію і на останньому кроці повертаємо її до значення дозорного:

// fn(i) = sqrt(n + 1 - i + fn(i - 1))
// fn(1) = sqrt(n)
//
// note: has global state
double f(int i)
{
    static const int sentinel = -1;
    static int n = sentinel;

    // outside call
    if (n == sentinel)
    {
        n = i;
        return f(n);
    }

    // last step
    if (i == 1)
    {
        double r = sqrt(n);
        n = sentinel;
        return r;
    }

    return sqrt(n + 1 - i + f(i - 1));
}

Мабуть, static int n = sentinelце не стандартний C, оскільки sentinelце не константа часу компіляції в C (це дивно, оскільки і gcc, і clang не скаржаться навіть з -pedantic)

Ви можете зробити це замість цього:

enum Special { Sentinel = -1 };
static int n = Sentinel;

Цікавий підхід, але я боюсь, що ініціалізатор static int n = sentinel;не повністю відповідає C, оскільки sentinelце не є постійним виразом відповідно до стандарту C. Він працює в C ++, і він компілюється з поточними версіями gcc і clang в режимі C, але не MSVC 2017, але, ймовірно, слід написати static int n = -1;див. Godbolt.org/z/8pEMnz
chqrlie

1
@chqrlieforyellowblockquotes ish. Дякую, що вказали на це. Цікава поведінка компілятора. Я запитав про це в цьому питанні: stackoverflow.com/q/61037093/2805305
боли

5

Ця проблема вимагає спотворених рішень.

Ось одна, яка використовує одну функцію, приймаючи один або два intаргументи:

  • якщо перший аргумент позитивний, він обчислює вираз для цього значення
  • якщо перший аргумент є негативним, за ним повинен слідувати другий аргумент і виконувати один крок у обчисленні, повторюючи попередній крок.
  • він використовує, <stdarg.h>що може бути або не може бути дозволено.

Ось код:

#include <math.h>
#include <stdarg.h>

double rec_sqrt_series(int n, ...) {
    if (n < 0) {
        va_arg ap;
        va_start(ap, n);
        int m = va_arg(ap, int);
        va_end(ap);
        if (m > -n) {
            return 0.0;
        } else {
            return sqrt(m + rec_sqrt_series(n, m + 1));
        }
    } else {
        return rec_sqrt_series(-n, 1);
    }
}

Ось ще одне рішення з однією функцією, використовуючи лише <math.h>, але зловживаючи правилами по-іншому: за допомогою макросу.

#include <math.h>

#define rec_sqrt_series(n)  (rec_sqrt_series)(n, 1)
double (rec_sqrt_series)(int n, int m) {
    if (m > n) {
        return 0.0;
    } else {
        return sqrt(m + (rec_sqrt_series)(n, m + 1));
    }
}

Ще один, строго кажучи, рекурсивний , але з одиничним рівнем рекурсії та інших хитрощів. Як зауважив Ерік , він використовує forцикл, який може бути недійсним згідно обмежень ОП:

double rec_sqrt_series(int n) {
    if (n > 0) {
        return rec_sqrt_series(-n);
    } else {
        double x = 0.0;
        for (int i = -n; i > 0; i--) {
            x = sqrt(i + x);
        }
        return x;
    }
}

так, це працює, я думаю. дуже дякую за всю допомогу
Ронен Дворкін

Нарешті double rec_sqrt_series(int n), ІМО відповідає цілям ОП, використовуючи знак як прапор рекурсії. (Я б кинув elseне потрібне, як returnє if.)
chux - Відновіть Моніку

1
@ chux-ReinstateMonica: відміняти elseможливе, звичайно, але мені якось подобається симетрія обох гілок ifповертаючого результату, свого роду функціональний стиль програмування.
chqrlie

@ chux-ReinstateMonica: Я очікую, що вимога завдання "лише для рекурсії" виключає ітерацію.
Ерік Postpischil

@EricPostpischil: так, я думав те саме, але не отримав відгуків від ОП.
chqrlie

0

Ось ще один підхід.

Він покладається на int32 біти. Ідея полягає у використанні верхнього 32-бітного 64-бітного intдля

1) Перевірте, чи був виклик рекурсивного дзвінка (або дзвінка "ззовні")

2) Збережіть цільове значення у верхніх 32 бітах під час рекурсії

// Calling convention:
// when calling this function 'n' must be a positive 32 bit integer value
// If 'n' is zero or less than zero the function have undefined behavior
double rec_sqrt_series(uint64_t n)
{
  if ((n >> 32) == 0)
  {
    // Not called by a recursive call
    // so start the recursion
    return rec_sqrt_series((n << 32) + 1);
  }

  // Called by a recursive call

  uint64_t rn = n & 0xffffffffU;

  if (rn == (n >> 32)) return sqrt(rn);      // Done - target reached

  return sqrt (rn + rec_sqrt_series(n+1));   // Do the recursive call
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.