Menghitung angka e menggunakan Raku

9

Saya mencoba menghitung konstanta e ( AKA Euler's Number ) dengan menghitung rumus e

Untuk menghitung faktorial dan pembagian dalam satu kesempatan, saya menulis ini:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
say reduce  * + * , @e[^10];

Tapi itu tidak berhasil. Bagaimana cara melakukannya dengan benar?

Lars Malmsteen
sumber
Dengan cara apa itu: "itu tidak berhasil"?
SherylHohman
1
Bagian penyebut dalam kode sampel tidak berhasil karena menggunakan variabel nilai sebelumnya $_ , dalam upaya untuk membangun faktorial. Itu jelas berlebihan. Dalam solusi yang benar di bawah ini, $_dijatuhkan dan bekerja dengan sempurna.
Lars Malmsteen
Terima kasih. Saya kira, saya lebih mencari apa yang sebenarnya dimaksud oleh pernyataan itu. Seperti ada kesalahan, bagaimana tidak konsisten dengan apa yang Anda harapkan, hal semacam itu. Saya kira, perhitungan Anda tidak cocok dengan jawaban yang diketahui untuk perhitungan itu. Senang Anda berhasil !! Juga, uraian post-jawaban yang bagus untuk apa masalah sebenarnya adalah :-)
SherylHohman

Jawaban:

11

Saya menganalisis kode Anda di bagian Menganalisis kode Anda . Sebelum itu saya menyajikan beberapa bagian menyenangkan dari materi bonus.

Satu liner Satu huruf 1

say e; # 2.718281828459045

"Sebuah risalah tentang berbagai cara" 2

Klik tautan di atas untuk melihat artikel luar biasa Damian Conway tentang komputasi edi Raku.

Artikel ini sangat menyenangkan (lagipula Damian). Ini adalah diskusi komputasi yang sangat dimengerti e. Dan itu adalah penghormatan terhadap reinkarnasi bikarbonat Raku terhadap filosofi TIMTOWTDI yang didukung oleh Larry Wall. 3

Sebagai hidangan pembuka, inilah kutipan dari sekitar setengah artikel:

Mengingat bahwa metode yang efisien ini semuanya bekerja dengan cara yang sama — dengan menjumlahkan (bagian awal dari) serangkaian istilah yang tak terbatas — mungkin akan lebih baik jika kita memiliki fungsi untuk melakukan itu untuk kita. Dan tentu saja akan lebih baik jika fungsi tersebut dapat bekerja dengan sendirinya persis berapa banyak dari subset awal dari seri yang sebenarnya perlu dimasukkan untuk menghasilkan jawaban yang akurat ... daripada mengharuskan kita untuk secara manual menyisir hasil dari beberapa percobaan untuk menemukan itu.

Dan, seperti yang sering terjadi di Raku, sangat mudah untuk membangun apa yang kita butuhkan:

sub Σ (Unary $block --> Numeric) {
  (0..∞).map($block).produce(&[+]).&converge
}

Menganalisis kode Anda

Inilah baris pertama, menghasilkan seri:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;

Penutupan ( { code goes here }) menghitung suatu istilah. Penutupan memiliki tanda tangan, baik implisit atau eksplisit, yang menentukan berapa banyak argumen yang akan diterima. Dalam hal ini tidak ada tanda tangan eksplisit. Penggunaan $_( variabel "topik" ) menghasilkan tanda tangan implisit yang membutuhkan satu argumen yang terikat $_.

Operator urutan ( ...) berulang kali memanggil penutupan di sebelah kirinya, melewati istilah sebelumnya sebagai argumen penutupan, untuk dengan malas membangun serangkaian istilah sampai titik akhir di sebelah kanannya, yang dalam hal ini adalah *, singkatan untuk Infalias tak terhingga.

Topik dalam panggilan pertama ke penutupan adalah 1. Jadi penutupan menghitung dan mengembalikan 1 / (1 * 1)menghasilkan dua istilah pertama dalam seri sebagai 1, 1/1.

Topik dalam panggilan kedua adalah nilai dari panggilan sebelumnya 1/1, yaitu 1sekali lagi. Jadi penutupan menghitung dan mengembalikan 1 / (1 * 2), memperluas seri ke 1, 1/1, 1/2. Semuanya terlihat bagus.

Penutupan selanjutnya menghitung 1 / (1/2 * 3)yaitu 0.666667. Istilah itu seharusnya 1 / (1 * 2 * 3). Ups.

Membuat kode Anda sesuai dengan formula

Kode Anda seharusnya cocok dengan rumus:
e

Dalam rumus ini, setiap istilah dihitung berdasarkan posisinya dalam rangkaian. Istilah k th dalam seri (di mana k = 0 untuk yang pertama 1) hanyalah faktorial k timbal balik.

(Jadi itu tidak ada hubungannya dengan nilai dari istilah sebelumnya. Dengan demikian $_, yang menerima nilai dari istilah sebelumnya, tidak boleh digunakan dalam penutupan.)

Mari kita buat operator postfix faktorial:

sub postfix:<!> (\k) { [×] 1 .. k }

( ×Adalah operator multiplikasi infiks, alias Unicode yang tampak lebih bagus dari infiks ASCII biasa *.)

Itu singkatan untuk:

sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }

(Saya telah menggunakan notasi metasyntactic pseudo di dalam kurung kurawal untuk menyatakan gagasan menambah atau mengurangi sebanyak istilah yang diperlukan.

Lebih umum, menempatkan operator infiks opdalam tanda kurung siku pada awal ekspresi membentuk operator awalan komposit yang setara dengan reduce with => &[op],. Lihat Pengurangan metaoperator untuk info lebih lanjut.

Sekarang kita dapat menulis ulang penutupan untuk menggunakan operator postfix faktorial baru:

my @e = 1, { state $a=1; 1 / $a++! } ... *;

Bingo. Ini menghasilkan seri yang tepat.

... sampai tidak, untuk alasan yang berbeda. Masalah selanjutnya adalah akurasi numerik. Tapi mari kita hadapi itu di bagian selanjutnya.

Satu liner berasal dari kode Anda

Mungkin kompres ketiga garis menjadi satu:

say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf

.[^10]berlaku untuk topik, yang diatur oleh given. ( ^10adalah singkatan 0..9, jadi kode di atas menghitung jumlah dari sepuluh istilah pertama dalam seri.)

Saya telah menghilangkan $adari penutupan komputasi istilah berikutnya. Satu-satunya $adalah sama dengan (state $), skalar keadaan anonynous. Saya membuat pra-increment bukan pasca-kenaikan untuk mencapai efek yang sama seperti yang Anda lakukan dengan menginisialisasi $auntuk 1.

Kami sekarang memiliki masalah (besar!) Final, yang ditunjukkan oleh Anda dalam komentar di bawah.

Asalkan operandnya tidak merupakan Num(float, dan dengan demikian perkiraan), /operator biasanya mengembalikan 100% akurat Rat(rasional presisi terbatas). Tetapi jika penyebut dari hasil melebihi 64 bit maka hasil itu dikonversi ke a Num- yang memperdagangkan kinerja untuk akurasi, tradeoff yang tidak ingin kita buat. Kita perlu memperhitungkannya.

Untuk menentukan presisi tak terbatas serta akurasi 100%, cukup paksakan operasi untuk menggunakan FatRats. Untuk melakukan ini dengan benar, buat saja (setidaknya) salah satu operan menjadi FatRat(dan tidak ada yang lain menjadi Num):

say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf

Saya telah memverifikasi ini hingga 500 digit desimal. Saya berharap itu tetap akurat sampai program crash karena melebihi batas bahasa Raku atau kompilator Rakudo. (Lihat jawaban saya untuk Tidak dapat membuka kotak bigint 65536 bit ke dalam integer asli untuk beberapa diskusi tentang itu.)

Catatan kaki

1 Raku memiliki beberapa konstanta matematika penting dibangun, termasuk e, idan pi(dan alias-nya π). Dengan demikian orang dapat menulis Identitas Euler dalam Raku seperti yang terlihat di buku matematika. Dengan kredit ke entri Raku RosettaCode untuk Identitas Euler :

# There's an invisible character between <> and i⁢π character pairs!
sub infix:<⁢> (\left, \right) is tighter(&infix:<**>) { left * right };

# Raku doesn't have built in symbolic math so use approximate equal 
say e**i⁢π + 1 ≅ 0; # True

2 Artikel Damian harus dibaca. Tapi itu hanya salah satu dari beberapa perawatan mengagumkan yang ada di antara 100+ kecocokan untuk google untuk 'raku "nomor euler"' .

3 Lihat TIMTOWTDI vs TSBO-APOO-OWTDI untuk salah satu pandangan TIMTOWTDI yang lebih seimbang yang ditulis oleh penggemar python. Tapi ada yang downsides untuk mengambil TIMTOWTDI terlalu jauh. Untuk merefleksikan "bahaya" yang terakhir ini, komunitas Perl menciptakan TIMTOWTDIBSCINABTE yang panjang, penuh humor, dan tidak dimengerti - Ada Lebih Dari Satu Cara Untuk Melakukannya Tetapi Terkadang Konsistensi Bukanlah Hal yang Buruk, diucapkan "Tim Toady Bicarbonate". Anehnya , Larry menerapkan bikarbonat pada desain Raku dan Damian menerapkannya pada komputasi edi Raku.

raiph
sumber
Terima kasih atas jawabannya. Bagian berjudul Cara saya berdasarkan cara Anda menyelesaikannya dengan cukup baik. Saya perlu melihat seluk-beluknya. Saya tidak tahu dataran $adalah singkatan state $, itu cukup berguna.
Lars Malmsteen
Apakah ada cara untuk menentukan jumlah digit euntuk solusi ke-3 (berjudul Cara saya berdasarkan cara Anda )? Saya sudah mencoba menambahkan FatRat (500) di sebelah 1 in: ... given 1.FatRat(500), ...untuk membuat angka 500-digit presisi, tetapi tidak berhasil.
Lars Malmsteen
@LarsMalmsteen Saya telah menjawab FatRatpertanyaan Anda yang sangat penting di bagian terakhir. Saya juga mengasah seluruh jawaban, meskipun satu-satunya perubahan besar adalah FatRathal - hal itu. (Btw, saya menyadari bahwa sebagian besar jawaban saya benar-benar bersinggungan dengan pertanyaan awal Anda; saya percaya Anda tidak keberatan saya menulis semua bulu ekstra untuk menghibur diri sendiri dan mungkin menarik bagi pembaca kemudian.)
raiph
Terima kasih atas upaya ekstra. Jadi .FatRatekstensi harus diletakkan di dalam pembuat kode. Sekarang saya mencobanya dengan FatRatmenambahkan cara ini dan menghitung e ke 1000 digit presisi. Bulu ekstra yang ditambahkan sementara waktu. Sebagai contoh saya tidak tahu bahwa sayitu memotong array panjang / urutan. Informasi seperti itu baik untuk diketahui.
Lars Malmsteen
@ LarsMalmsteen :) "Jadi .FatRatekstensi harus dimasukkan ke dalam pembuat kode." Iya. Lebih umum, jika ekspresi yang melibatkan divisi telah dievaluasi, sudah terlambat untuk membatalkan kerusakan yang disebabkan jika meluap Ratpresisi. Jika sudah, ia akan mengevaluasi ke Num(float) dan pada gilirannya mencemari setiap perhitungan lebih lanjut yang melibatkannya, membuatnya juga Num . Satu-satunya cara untuk memastikan hal tinggal FatRatadalah untuk memulai mereka FatRatdan menghindari Nums. Ints dan Rats OK, asalkan setidaknya ada satu FatRatuntuk membiarkan Raku tahu untuk tetap pada FatRats.
raiph
9

Ada pecahan di $_. Jadi, Anda perlu1 / (1/$_ * $a++) atau lebih tepatnya $_ /$a++.

Dengan Raku Anda bisa melakukan perhitungan ini langkah demi langkah

1.FatRat,1,2,3 ... *   #1 1 2 3 4 5 6 7 8 9 ...
andthen .produce: &[*] #1 1 2 6 24 120 720 5040 40320 362880
andthen .map: 1/*      #1 1 1/2 1/6 1/24 1/120 1/720 1/5040 1/40320 1/362880 ...
andthen .produce: &[+] #1 2 2.5 2.666667 2.708333 2.716667 2.718056 2.718254 2.718279 2.718282 ...
andthen .[50].say      #2.71828182845904523536028747135266249775724709369995957496696762772
wamba
sumber
Bagus. Saya tidak tahu andthen.
Holli