# 総和 (Σ) 関数を書く --- tags: Delphi programming Pascal objectpascal Simula created_at: 2021-12-04 updated_at: 2021-12-05 --- # はじめに 数学は詳しくないのですが、「**総和** というのがあって **Σ** 記号で表して...」というくらいは知っていて、「プログラミング言語で言うところの **for** 文に相当する」というのも理解していました。 - [総和 (Wikipedia)](https://ja.wikipedia.org/wiki/%E7%B7%8F%E5%92%8C) そして調べ物をしていたら、**Simula** で書かれた関数 `Sigma()` というのが Wikipedia に載っていました。 - [Simula (Wikipedia)](https://ja.wikipedia.org/wiki/Simula) ```math z= \sum_{i=1}^{100} \frac{1}{(i+a)^2}  ``` 上記の式は ```pascal Z:= Sigma(i, 1, 100, 1 / (i + a) ** 2) ; ``` と書けるのだそうです。そしてその実装はどうなっているのかというと... ```pascal Real Procedure Sigma (l, m, n, u) ; Name l, u ; Integer l, m, n ; Real u ; Begin Real s ; l:= m ; While l <= n Do Begin s := s + u ; l := l + 1 ; End ; Sigma := s ; End ; ``` あー、簡単そうに見えて簡単に書けないやつだ Σ(゚д゚lll) **See also:** - [名前呼び (Wikipedia)](https://ja.wikipedia.org/wiki/%E8%A9%95%E4%BE%A1%E6%88%A6%E7%95%A5#%E5%90%8D%E5%89%8D%E5%91%BC%E3%81%B3) # 実装 総和の計算を**ルーチンで書くなら**難しくはないです。アルゴリズム解説の書籍では、大抵 **for** 文を使ったルーチンで書かれていると思います。実際、アルゴリズムの解説ならそれで用は足りますので。 ```pascal program pgSigma; {$APPTYPE CONSOLE} uses System.Math; function Sigma: Extended; var i, lFrom, lTo: Integer; a: Extended; begin lFrom := 1; lTo := 100; a := 2; result := 0.0; for i := lFrom to lTo do Result := Result + 1 / Power(i + a, 2); end; var z: Extended; begin z := Sigma; Writeln(z: 1 : 2); end. ``` :::note info Delphi (Pascal) にはべき乗の演算子がないので Power() 関数を使っています。 ::: でも、Simula の `Sigma()` 関数と同等な、 - 式を関数のパラメータとして与えることができる - [副作用](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0))を伴わない 上記を満たす**関数**を書こうとすると、途端に頭を抱える事になります。「そんなの簡単じゃないか?」と思った方は次のような実装を考えたのだと思います。 ```pascal function Sigma(var i: Integer; const aFrom, aTo: Integer; aExp: Extended): Extended; begin Result := 0.0; i := aFrom; while i <= aTo do // 言語によっては for にローカル変数しか使えないので while で begin Result := Result + aExp; Inc(i); // i++ end; end; ``` 次のような式の場合、 ```math z=\sum_{i=1}^{5} 2 = 2 + 2 + 2 + 2 + 2 ``` 呼び出しはこんな感じで。 ```pascal var i: Integer; var z := Sigma(i, 1, 5, 2); Writeln(z: 1 : 2); ``` 確かに結果は正しく `10` となりました。 ``` 10.00 ``` でも、次のような式の場合、 ```math z=\sum_{i=1}^{5} i = 1 + 2 + 3 + 4 + 5 ``` ```pascal var i: Integer; var z := Sigma(i, 1, 5, i); Writeln(z: 1 : 2); ``` 結果は正しくなりません。 ``` 0.00 ``` 何故なら上記コードは次のコードと同等だからです (変数 `i` が 0 で初期化される場合)。 ```pascal var i: Integer; var z := Sigma(i, 1, 5, 0); Writeln(z: 1 : 2); ``` では 4 番目のパラメータを変数パラメータ (参照渡し) にすると、 ```pascal function Sigma(var i: Integer; const aFrom, aTo: Integer; var e: Integer): Extended; begin Result := 0.0; i := aFrom; while i <= aTo do begin Result := Result + e; Inc(i); end; end; ``` 結果は正しくなりましたが... ``` 15.00 ``` 当然、式が使えなくなります。 ```math z=\sum_{i=1}^{5} (i+1) = 2 + 3 + 4 + 5 + 6 ``` ```pascal var i: Integer; var z := Sigma(i, 1, 5, i + 1); // <- 式が使えない Writeln(z: 1 : 2); ``` これが実装が簡単でない理由です。 :::note info 僕が用意した解答を見ずに、自分でも考えてみたいという方は、この辺でブラウザのスクロールを止めてください。 ::: **See also:** - [11.1.1. パラメータリスト (パラメータ並び / Parameter list) (Qiita)](./b93ac03bfee002f17137.md#1111-%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%AA%E3%82%B9%E3%83%88-%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E4%B8%A6%E3%81%B3--parameter-list) - [11.2.2. 副作用 (Side effect) (Qiita)](./b93ac03bfee002f17137.md#1122-%E5%89%AF%E4%BD%9C%E7%94%A8-side-effect) - [12.3. 手続き Write と Writeln](./391d6c1a53c7aaa3416f.md#123-%E6%89%8B%E7%B6%9A%E3%81%8D-write-%E3%81%A8-writeln) ## ・Delphi での実装 こんな感じのユニットを作ってみました。 ```pascal:uSigma.pas unit uSigma; interface type TSigmaExp = reference to function (i: Integer): Extended; function Sigma(var i: Integer; const aFrom, aTo: Integer; aExp: TSigmaExp): Extended; implementation function Sigma(var i: Integer; const aFrom, aTo: Integer; aExp: TSigmaExp): Extended; begin Result := 0.0; i := aFrom; while i <= aTo do begin Result := Result + aExp(i); Inc(i); end; end; end. ``` メソッド参照型を使って、計算式はそこに書くようにしました。こうしておけば `Sigma()` に無名メソッドが渡せます。 ### 例1: Z:= Sigma(i, 1, 100, 1 / (i + a) ** 2) ; こうかな? ```math z= \sum_{i=1}^{100} \frac{1}{(i+a)^2}  ``` ```pascal program pgSigma; {$APPTYPE CONSOLE} uses System.Math, uSigma; begin var i: Integer; var a := 2; // 任意の数 var z := Sigma(i, 1, 100, function (i: Integer): Extended begin result := 1 / Power(i + a, 2); end); Writeln(z: 1 : 2); end. ``` `a=2` の時の結果は次のようになりました。 ``` 0.39 ``` ### 例2: Z:= Sigma(i, 1, 5, i + 1) ; ```math z=\sum_{i=1}^{5} (i+1)  ``` ```pascal:pgSigma.dpr program pgSigma; {$APPTYPE CONSOLE} uses uSigma; begin var i: Integer; var z := Sigma(i, 1, 5, function (i: Integer): Extended begin result := i + 1; end); Writeln(z: 1 : 2); end. ``` こちらは検算が簡単ですね (w ``` 20.00 ``` ちゃんと `15.00` が表示されました。 ## ・標準 Pascal での実装 つまりは名前渡しができなくとも、関数のパラメータとして関数が渡せれば総和関数が書けます (コールバック関数など)。パラメータとして無名関数が渡せればもっとスマートになります。 標準 Pascal も**関数パラメータ**に対応しているため、総和関数が書けます。 ```pgSigma.pas program pgSigma(Output); var i: Integer; z, a: Real; function Sigma(var i: Integer; aFrom, aTo: Integer; function f(i: Integer): Real): Real; var v: Real; begin v := 0.0; i := aFrom; while i <= aTo do begin v := v + f(i); i := i + 1; end; Sigma := v; end; { Sigma } function SigmaExp1(i: Integer): Real; begin SigmaExp1 := 1 / Exp(Ln(i + a) * 2); end; { SigmaExp1 } function SigmaExp2(i: Integer): Real; begin SigmaExp2 := i + 1; end; { SigmaExp2 } begin { Sigma #1 } a := 2; z := Sigma(i, 1, 100, SigmaExp1); Writeln(z : 1 : 2); { Sigma #2 } z := Sigma(i, 1, 5, SigmaExp2); Writeln(z : 1 : 2); end. ``` :::note info 標準 Pascal には Power() 関数もないため、べき乗相当を Exp() と Ln() を使って計算しています。ただし、Xn の時、X > 0 である必要があります。古い Delphi の Standard 版も Math ユニットが含まれないため、Power() 関数が使えませんでした。 ::: 式を分離して関数にしなくてはならないのなら、総和関数を 2 度書いても大して手間は変わらない気がするので、個人的にはイマイチに思えます。 **See also:** - [11.2.1. 関数パラメータ (Functional parameters)](./b93ac03bfee002f17137.md#1121-%E9%96%A2%E6%95%B0%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF-functional-parameters) - [System.Math (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Math) - [Pascal (J&W) - Wirth 先生の邦訳本を読んでみる (Qiita)](./3086b7cff08928eca7a9.md#pascal-pascal) [^1] - [#12 コンピュータアルゴリズム事典 - 技術評論社の Software Technology シリーズから Pascal 関連の書籍を読んでみる (Qiita)](./06946a72b51f577523bb.md#12-%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E4%BA%8B%E5%85%B8) [^2] ### 追記 後で『J&W (第4版)』の「11.2.1. 関数パラメータ」を確認したら、そこの例が `「プログラム 11.9 級数の連続和の表を書く」` で、そのコードの中に Sigma() 関数がありました!当然ですが、内容もほぼ同じでした。 ```pascal function Sigma(function F(X: Real): Real; Lower, Upper: Integer): Real; var Index: Integer; Sum: Real; begin Sum := 0.0; for Index := Lower to Upper do Sum := Sum + F(Index); Sigma := Sum; end; { Sigma } ``` /(^o^)\ナンテコッタイ 「11.2.1. 関数パラメータ (原著では「11.B.1. Functional parameters」)」のサンプルコード (プログラム 11.9) は『J&W (初版・第2版)』と『J&W (第3版・第4版)』で内容が異なっているのですが、第2版の方しか頭に入ってなかったみたいです。 # おわりに 今回記述した総和関数ですべての Σ 記号のある式を計算できる訳ではありませんが、Simula の `Sigma()` 関数と同等のものは書けたように思います (何か勘違いしてなきゃいいけど...)。もっとスマートに書ける方法をご存じな方がいらっしゃいましたら、ぜひ教えてください m(_ _)m あと、他の言語では総和関数をどう書けるのか知らないので (標準で持っていたり?) 、他の言語の状況もコメ欄で教えて頂けると幸いです。 **See also:** - [Delphi 用に FizzBuzz コレクションを作る (Qiita)](./a2016e9e0243e2cf1af6.md) - [ホクホクのイモを Delphi で書いてみる (Qiita)](./adef65fcb10c0144b3b0.md) - [Delphi で湯婆婆を実装してみる (Qiita)](./6826c8f1c05030d09146.md) - [Delphi で前前前世 (Qiita)](./19f7bc80ec24e2019650.md) [^1]: 標準 Pascal で書いた Power() 関数が載っています (プログラム 11.8)。 [^2]: 標準 Pascal で書いた Power() 関数が載っています。(累乗 <1>・<2>・<3>)