Calcolo di una radice nidificata in C


9

Mi è stato chiesto di calcolare la seguente espressione radice nidificata usando solo la ricorsione .

inserisci qui la descrizione dell'immagine

Ho scritto il codice qui sotto che funziona, ma ci hanno permesso di usare solo una funzione e 1 input nper lo scopo e non 2 come ho usato. Qualcuno può aiutarmi a trasformare questo codice in una funzione che calcolerà l'espressione? non posso usare nessuna libreria tranne le funzioni di <math.h>.

uscita per 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);
}

Qualcuno può aiutarmi a trasformare questo codice in una funzione che calcolerà l'espressione? che cosa? Ti aiuta a sbarazzarti del helper?
4386427

Se gli argomenti sono sbagliati, chiamerei abort()(da <stdlib.h>), non restituendo silenziosamente 0.
Kaz

1
@chqrlieforyellowblockquotes Twisied. @pastaleg Che ne dici di una ricorsione inutile? 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 - Ripristina Monica il

1
@ chux-ReinstateMonica: sì, un abuso più semplice delle regole.
Chqrlie,

2
@Aperto: se l'obiettivo dell'incarico fosse finanziare un'espressione non ricorsiva della funzione, probabilmente non richiederebbe che il problema fosse risolto usando "solo ricorsione". Certamente un semplice ciclo calcolerebbe facilmente il risultato. Anche se sono generalmente sospettoso quando queste domande vengono pubblicate su Stack Overflow senza il testo effettivo del compito.
Eric Postpischil,

Risposte:


7

Utilizzare i bit superiori di ncome contatore:

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;
}

Naturalmente, quei malfunzionamenti quando l'iniziale nè Ro maggiore. Ecco una versione più complicata che funziona per qualsiasi valore positivo di n. Funziona:

  • Quando nè negativo, funziona come la versione precedente, usando i bit superiori per contare.
  • Quando nè positivo, se è inferiore a R, si chiama -nper valutare la funzione come sopra. Altrimenti, si chiama R-1negato. Questo valuta la funzione come se fosse stata chiamata con R-1. Questo produce il risultato corretto perché la serie smette di cambiare nel formato a virgola mobile dopo solo una dozzina di iterazioni: le radici quadrate dei numeri più profondi vengono così diluite da non avere alcun effetto. Quindi la funzione ha lo stesso valore per tutta nuna piccola soglia.
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;
}

Buona idea, ma assume
ints a

1
@chqrlieforyellowblockquotes: No, ecco perché Rè separato, quindi può essere sintonizzato. Prima che nraggiunga 32, il valore restituito smette di cambiare per IEEE-754 binary64 e prima che raggiunga 256, il valore restituito smette di cambiare per formati ragionevoli per double. Quindi sto considerando una versione alternativa che trasforma gli input dei morsetti sopra R, ma deve usare il bit di segno e ci sto ancora lavorando.
Eric Postpischil,

Esistono altre funzioni di associazione che è possibile utilizzare, ma nessuna è semplice come la tua. Il loro vantaggio principale è in genere che funzionano con precisione arbitraria, ma OP non l'ha mai menzionato come requisito.
Ruud Helderman,

@chqrlieforyellowblockquotes: Fatto. Ora produce la risposta corretta per qualsiasi positivo nindipendentemente dalla larghezza di int.
Eric Postpischil,

5

Senza trasformare matematicamente la formula (non so se sia possibile), non puoi davvero usare solo un parametro, poiché per ogni elemento hai bisogno di due informazioni: il passaggio corrente e l'originale n. Comunque puoi imbrogliare . Un modo è codificare i due numeri nel intparametro (come mostrato da Eric ).

Un altro modo è memorizzare l'originale nin una variabile locale statica. Alla prima chiamata salviamo nin questa variabile statica, iniziamo la ricorsione e all'ultimo passaggio lo ripristiniamo al valore sentinella:

// 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));
}

Apparentemente static int n = sentinelnon è C standard perché sentinelnon è una costante di tempo di compilazione in C (è strano perché sia ​​gcc che clang non si lamentano, nemmeno con -pedantic)

Puoi farlo invece:

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

Approccio interessante, ma temo che l'inizializzatore static int n = sentinel;non sia pienamente conforme in C perché sentinelnon è un'espressione costante secondo lo standard C. Funziona in C ++ e si compila con le attuali versioni di gcc e clang in modalità C ma non con MSVC 2017, ma probabilmente dovresti scrivere static int n = -1;vedi godbolt.org/z/8pEMnz
chqrlie

1
@chqrlieforyellowblockquotes ish. Grazie per averlo sottolineato. Interessante comportamento del compilatore. Ho chiesto spiegazioni a questa domanda: stackoverflow.com/q/61037093/2805305
bolov

5

Questo problema richiede soluzioni contorte.

Ecco uno che utilizza una singola funzione prendendo uno o due intargomenti:

  • se il primo argomento è positivo, calcola l'espressione per quel valore
  • se il primo argomento è negativo, deve essere seguito da un secondo argomento ed eseguire un singolo passaggio nel calcolo, ricorrendo per il passaggio precedente.
  • utilizza ciò <stdarg.h>che potrebbe o non potrebbe essere consentito.

Ecco il codice:

#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);
    }
}

Ecco un'altra soluzione con una singola funzione, che utilizza solo <math.h>, ma abusa delle regole in un modo diverso: usare una macro.

#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));
    }
}

Ancora un altro, in senso strettamente ricorsivo , ma con un singolo livello di ricorsione e nessun altro trucco. Come ha commentato Eric , utilizza un forciclo che potrebbe non essere valido sotto i vincoli del PO:

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;
    }
}

si, credo. grazie mille per tutto l'aiuto
Ronen Dvorkin

Infine double rec_sqrt_series(int n), IMO, raggiunge gli obiettivi del PO usando il segno come bandiera ricorsiva. ( elsereturnif
Lascerei

1
@ chux-ReinstateMonica: abbandonare il elsepossibile è ovviamente, ma mi piace un po 'la simmetria di entrambi i rami del ifrisultato di ritorno, una sorta di stile di programmazione funzionale.
Chqrlie,

@ chux-ReinstateMonica: mi aspetto che il requisito dell'incarico di "solo ricorsione" precluda l'iterazione.
Eric Postpischil,

@EricPostpischil: sì, ho pensato lo stesso, ma non ho ricevuto feedback dall'OP.
Chqrlie,

0

Ecco un altro approccio.

Si basa intsull'essere 32 bit. L'idea è di usare i 32 bit superiori di 64 bit intper

1) Verifica se la chiamata è stata una chiamata ricorsiva (o una chiamata dall'esterno)

2) Salvare il valore target nei 32 bit superiori durante la ricorsione

// 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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.