「コードの分析」のセクションでコードを分析します。その前に、ボーナス素材の楽しいセクションをいくつか紹介します。
1ライナー 1文字1
say e; # 2.718281828459045
上記のリンクをクリックして、Damian Conwayのe
Raku でのコンピューティングに関する臨時の記事をご覧ください。
記事はとても楽しいです(結局のところ、それはDamianです)。これは、コンピューティングについて非常に理解しやすい議論ですe
。そして、それはラリー・ウォールによって支持されたTIMTOWTDI哲学のラクの重炭酸塩の生まれ変わりへのオマージュです。3
前菜として、ここに記事のほぼ半分からの引用があります:
これらの効率的な方法はすべて、無限の一連の項(の最初のサブセット)を合計することによって同じように機能するため、それを行う関数があるとよいでしょう。そして、正確な回答を生成するために関数がシリーズの最初のサブセットの実際にどれだけ含める必要があるかを関数自体が正確に計算できれば、確かに良いでしょう...それを発見するための複数の試験。
そして、Rakuでよくあることですが、必要なものだけを構築するのは驚くほど簡単です。
sub Σ (Unary $block --> Numeric) {
(0..∞).map($block).produce(&[+]).&converge
}
コードを分析する
シリーズを生成する最初の行は次のとおりです。
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
クロージャ({ code goes here }
)は項を計算します。クロージャーには、暗黙的または明示的なシグニチャーがあり、受け入れるシグニチャーの数を決定します。この場合、明示的な署名はありません。$_
(「トピック」変数)を使用すると、にバインドされている1つの引数を必要とする暗黙の署名が生成され$_
ます。
シーケンス演算子(...
)は、左側のクロージャを繰り返し呼び出し、前の項をクロージャの引数として渡し、一連の項をその右側のエンドポイント(この場合は無限大の省略形)まで遅延して構築します。*
Inf
クロージャの最初の呼び出しのトピックは1
です。したがって、クロージャ1 / (1 * 1)
は、系列の最初の2つの項をとして計算して返します1, 1/1
。
2番目の呼び出しのトピックは、前の呼び出しの値1/1
、つまり、1
再びです。したがって、クロージャはを計算して返し1 / (1 * 2)
、系列をに拡張し1, 1/1, 1/2
ます。それはすべてよさそうだ。
次のクロージャはを計算1 / (1/2 * 3)
します0.666667
。その用語はそうあるべきです1 / (1 * 2 * 3)
。おっとっと。
コードを数式に一致させる
あなたのコードは式に一致するはずです:
この式では、各用語は系列内での位置に基づいて計算されます。系列のk番目の項(最初のはk = 0 1
)は、階乗kの逆数です。
(したがって、前の期間の値とは関係ありません。したがって$_
、前の期間の値を受け取るをクロージャーで使用しないでください。)
階乗後置演算子を作成しましょう:
sub postfix:<!> (\k) { [×] 1 .. k }
(×
は、中置乗算演算子であり、通常のASCII中置の見栄えの良いUnicodeエイリアスです*
。)
それは以下の略記です:
sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
(私は中括弧内の疑似メタ構文記法を使用して、必要なだけ用語を追加または減算するという考えを示しました。
より一般的にop
は、式の先頭で角括弧内に中置演算子を置くと、と同等の複合前置演算子が形成されreduce with => &[op],
ます。詳細については、削減メタオペレーターを参照してください。
これで、新しい階乗後置演算子を使用するようにクロージャーを書き直すことができます。
my @e = 1, { state $a=1; 1 / $a++! } ... *;
ビンゴ。これにより、適切なシリーズが作成されます。
...異なる理由で、それができないまで。次の問題は数値の精度です。しかし、次のセクションでそれを扱いましょう。
コードから派生したワンライナー
多分3行を1つに圧縮します:
say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
.[^10]
によって設定されるトピックに適用されますgiven
。(^10
はの省略形な0..9
ので、上記のコードはシリーズの最初の10項の合計を計算します。)
私は$a
次期の計算から閉鎖から除外しました。孤独$
はと同じ(state $)
で、匿名の状態スカラーです。に初期化$a
することで行ったのと同じ効果を実現するために、後置インクリメントではなく前置インクリメントにしました1
。
以下のコメントで指摘されているように、最後の(大きな!)問題が残ります。
いずれのオペランドもa Num
(浮動小数点数、したがって概数)でない場合、/
演算子は通常100%の正確さRat
(限られた精度の有理数)を返します。ただし、結果の分母が64ビットを超える場合、その結果はaに変換されます。Num
これは、パフォーマンスと正確さのトレードオフですが、トレードオフは行いません。それを考慮する必要があります。
無制限の精度と100%の精度を指定するには、FatRat
s を使用するように操作を強制します。これを正しく行うには、(少なくとも)オペランドの1つをaにしますFatRat
(他のオペランドをaにしないでくださいNum
)。
say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
これを10進数500桁で確認しました。Raku言語またはRakudoコンパイラーの制限を超えてプログラムがクラッシュするまでは、正確であることが期待されます。(それについての議論については、65536ビット幅のbigintをネイティブ整数にアンボックスできないという私の答えを参照してください。)
脚注
1楽に含むに建てられたいくつかの重要な数学定数を、持っているe
、i
とpi
(とそのエイリアスπ
)。したがって、数学の本にあるように、オイラーのアイデンティティをラクで書くことができます。オイラーのアイデンティティのためのRosettaCodeのRakuエントリへのクレジット付き:
# 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ダミアンの記事は必読です。しかし、それはグーグルの 'raku "euler's number"'の 100以上のマッチの中にあるいくつかの見事な扱いの1つにすぎません。
3 pythonのファンによって書かれたTIMTOWTDIのよりバランスの取れたビューの1つについては、TIMTOWTDIとTSBO-APOO-OWTDIを参照してください。しかし、TIMTOWTDIを使いすぎてしまうことには欠点があります。この後者の「危険」、ユーモラスに長い、読めない、と控えめ造語Perlコミュニティ反映するためTIMTOWTDIBSCINABTEを -そこですつ以上の方法するために行うことが、時には「ティム・口のうまい人重炭酸塩」と発音一貫性ではありませんA悪いことどちらか、。不思議なことに、ラリーは重炭酸塩をラクの設計に適用し、ダミアンはそれをe
ラクのコンピューティングに適用しました。