Menghitung root yang bersarang di C

9

Saya diminta untuk menghitung ekspresi root bersarang berikut ini hanya menggunakan rekursi .

masukkan deskripsi gambar di sini

Saya menulis kode di bawah ini yang berfungsi, tetapi mereka memungkinkan kami untuk menggunakan hanya satu fungsi dan 1 input nuntuk tujuan dan bukan 2 seperti yang saya gunakan. Dapatkah seseorang membantu saya mengubah kode ini menjadi satu fungsi yang akan menghitung ekspresi? tidak dapat menggunakan perpustakaan apa pun kecuali fungsi dari <math.h>.

output untuk 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);
}
Ronen Dvorkin
sumber
Dapatkah seseorang membantu saya mengubah kode ini menjadi satu fungsi yang akan menghitung ekspresi? apa? Bantu Anda menyingkirkan itu helper?
4386427
Jika argumennya salah, saya akan menelepon abort()(dari <stdlib.h>), tidak diam-diam mengembalikan 0.
Kaz
1
@chqrlieforyellowblockquotes Twisied. @ Pastaleg Bagaimana dengan rekursi yang tidak berguna? 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: ya, penyalahgunaan aturan yang lebih sederhana.
chqrlie
2
@ Openpen: Jika tujuan penugasan adalah untuk mendanai ekspresi fungsi non-rekursif, mungkin tidak akan meminta masalah diselesaikan dengan menggunakan "rekursi saja." Tentu saja perulangan sederhana akan menghitung hasilnya dengan mudah. Meskipun saya umumnya curiga ketika pertanyaan-pertanyaan ini diposting ke Stack Overflow tanpa teks tugas yang sebenarnya.
Eric Postpischil

Jawaban:

7

Gunakan bit atas nsebagai penghitung:

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

Tentu, bahwa malfungsi ketika awal nadalah Ratau lebih besar. Berikut adalah versi yang lebih rumit yang berfungsi untuk nilai positif apa pun dari n. Berhasil:

  • Ketika nnegatif, ini berfungsi seperti versi di atas, menggunakan bit atas untuk menghitung.
  • Ketika npositif, jika kurang dari Ritu, ia memanggil dirinya sendiri -nuntuk mengevaluasi fungsi seperti di atas. Kalau tidak, itu menyebut dirinya dengan R-1negasi. Ini mengevaluasi fungsi seolah-olah dipanggil dengan R-1. Ini menghasilkan hasil yang benar karena seri berhenti berubah dalam format floating-point setelah hanya beberapa lusin iterasi - akar kuadrat dari angka yang lebih dalam menjadi sangat terdilusi sehingga tidak memiliki efek. Jadi fungsinya memiliki nilai yang sama untuk seluruh nambang batas yang kecil.
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;
}
Eric Postpischil
sumber
Ide bagus, tetapi mengasumsikan int 32-bit :)
chqrlie
1
@ chqrlieforyellowblockquotes: Nah, itu sebabnya Rterpisah, jadi bisa disetel. Sebelum nmencapai 32, nilai kembali berhenti berubah untuk IEEE-754 binary64, dan sebelum mencapai 256, nilai kembali berhenti berubah untuk format yang masuk akal untuk double. Jadi saya sedang mempertimbangkan versi alternatif yang mengubah input klem di atas R, tetapi perlu menggunakan bit tanda, dan saya masih mengerjakannya.
Eric Postpischil
Ada fungsi pasangan lain yang dapat Anda gunakan, tetapi tidak ada yang sesederhana milik Anda. Keuntungan utama mereka adalah bahwa mereka bekerja dengan presisi sewenang-wenang, tetapi OP tidak pernah menyebut itu sebagai persyaratan.
Ruud Helderman
@chqrlieforyellowblockquotes: Selesai. Sekarang menghasilkan jawaban yang benar untuk setiap positif nterlepas dari lebar int.
Eric Postpischil
5

Tanpa mengubah rumus secara matematis (saya tidak tahu apakah itu mungkin), Anda tidak dapat benar-benar menggunakan hanya satu parameter, karena untuk setiap elemen Anda memerlukan dua informasi: langkah saat ini dan yang asli n. Namun Anda bisa menipu . Salah satu caranya adalah menyandikan dua angka dalam intparameter (seperti yang ditunjukkan oleh Eric ).

Cara lain adalah dengan menyimpan yang asli ndalam variabel lokal statis. Pada panggilan pertama kita simpan ndalam variabel statis ini, kita memulai rekursi dan pada langkah terakhir kita meresetnya ke nilai sentinel:

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

Rupanya static int n = sentinelbukan standar C karena sentinelbukan waktu kompilasi konstan dalam C (itu aneh karena baik gcc dan dentang tidak mengeluh, bahkan dengan-pedantic )

Anda bisa melakukan ini sebagai gantinya:

enum Special { Sentinel = -1 };
static int n = Sentinel;
bolov
sumber
Pendekatan yang menarik, tapi saya khawatir penginisialisasi static int n = sentinel;tidak sepenuhnya sesuai dalam C karena sentineltidak ekspresi konstan sesuai Standar C. Ia bekerja di C ++, dan dikompilasi dengan versi gcc dan clang saat ini dalam mode C tetapi tidak pada MSVC 2017, tetapi Anda mungkin harus menulis static int n = -1;lihat godbolt.org/z/8pEMnz
chqrlie
1
@chqrlieforyellowblockquotes ish. Terima kasih telah menunjukkan ini. Perilaku kompiler yang menarik. Saya telah menanyakan hal ini dalam pertanyaan ini: stackoverflow.com/q/61037093/2805305
bolov
5

Masalah ini memohon solusi berkerut.

Berikut ini adalah yang menggunakan fungsi tunggal yang mengambil satu atau dua intargumen:

  • jika argumen pertama positif, itu menghitung ekspresi untuk nilai itu
  • jika argumen pertama negatif, itu harus diikuti oleh argumen kedua dan melakukan satu langkah dalam perhitungan, berulang untuk langkah sebelumnya.
  • menggunakan <stdarg.h>yang mungkin atau mungkin tidak diizinkan.

Ini kodenya:

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

Berikut adalah solusi lain dengan fungsi tunggal, hanya menggunakan <math.h>, tetapi menyalahgunakan aturan dengan cara yang berbeda: menggunakan makro.

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

Namun satu lagi, secara tegas rekursif , tetapi dengan tingkat rekursi tunggal dan tidak ada trik lain. Seperti yang dikomentari Eric , itu menggunakan forloop yang mungkin tidak valid di bawah batasan OP:

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;
    }
}
chqrlie
sumber
ya itu berhasil kurasa. terima kasih banyak atas semua bantuannya
Ronen Dvorkin
Terakhir double rec_sqrt_series(int n), IMO, memenuhi tujuan OP dengan menggunakan tanda sebagai bendera rekursi. (Saya akan menjatuhkan yang elsetidak diperlukan seperti yang returnada di if.)
chux - Reinstate Monica
1
@ chux-ReinstateMonica: menjatuhkan elsemungkin mungkin tapi saya agak suka simetri dari kedua cabang dari ifmengembalikan hasil, semacam gaya pemrograman fungsional.
chqrlie
@ chux-ReinstateMonica: Saya berharap persyaratan penugasan "hanya rekursi" menghalangi iterasi.
Eric Postpischil
@EricPostpischil: ya, saya pikir sama, tetapi tidak mendapat umpan balik dari OP.
chqrlie
0

Ini pendekatan lain.

Itu bergantung pada int32 bit. Idenya adalah untuk menggunakan bit 32 atas 64 bit intuntuk

1) Lihat apakah panggilan itu panggilan rekursif (atau panggilan dari "luar")

2) Simpan nilai target dalam 32 bit atas selama rekursi

// 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
}
4386427
sumber