たのしい関数:Current関数

2018年12月13日のLibreOffice Advent CalendarはnogajunさんのLibreOfficeを使ってるならTelegram messengerの「LibreOffice-JA」グループに入ろうでした。(この辺の紹介はEnokiさんのマネ)

この記事はLibreOffice Advent Calendar 2018の14番目の記事です。
Advent Calendar初参戦です。

本題が出てくるまでが非常に長いので注意。

関数に置き換える

こんな感じで関数が定義されていたとする。

Function Add(arg1 As Double, arg2 As Double) As Double
    Add = arg1 + arg2
End Function

Function Subtract(arg1 As Double, arg2 As Double) As Double
    Subtract = arg1 - arg2
End Function 

Function Multiply(arg1 As Double, arg2 As Double) As Double
    Multiply = arg1 * arg2
End Function 

Function Divide(arg1 As Double, arg2 As Double) As Double
    Divide = arg1 / arg2
End Function

いろんな式を演算子を使わずに、上記ユーザー定義の物を含めて関数だけを使って書いてみる。
答えになる式は、関数の引数のための括弧は使っていいけど、演算の優先順位変更のための括弧は使ってはいけない。

例題

1問目
= 3*5+4*1

= Add (Multiply(3,5),Multiply(4,1))

となる。

2問目

RSA暗号から。

= Mod((2^5)^29,7*5) 

= Mod(Power(Power(2,5),29),35)

となる。なお本来の答えは2だが、浮動小数点数は10進数15桁くらいしか保存しないせいか、Calcはこの答えを0とする、と記事を書きながら検証していてわかった。

3問目

いつぞやに話題になったExcel列名変換を自分で試しにCalcの数式で解いてみていた。多分標準関数がもうすでにあると思う。

= (26^Len("XFD")-26)/(26-1) + Decimal(Base(Decimal("XFD",36) - (36^Len("XFD") - 1)/35 * 10,36),26) + 1

= Add(Add(Divide(Subtract(Power(26,Len("XFD")),26),Subtract(26,1)),Decimal(Base(Subtract(Decimal("XFD",36), Multiply(Divide(Subtract(Power(36,Len("XFD")),1),35),10)),36),26)),1)

となります。発狂しそうだった。

逆ポーランド記法

さて、これら答えの式の関数名をその引数の括弧の後ろに持っていくようにする。もうCalcの数式としては使えないのでわかりやすくするため先頭の=は外しておく。

1問目
= Add (Multiply(3,5),Multiply(4,1))

((3,5)Multiply,(4,1)Multiply)Add

括弧やカンマを半角スペースに置き換える。2つ以上連続するスペースは1つにする。

3 5 Multiply 4 1 Multiply Add

となる。この書き方を逆ポーランド記法とか後置記法という。

2問目
= Mod(Power(Power(2,5),29),35)

(((2,5)Power,29)Power,35)Mod

2 5 Power 29 Power 35 Mod
3問目

自分で出しておいてなんだけど、3問目はもうやりたくない→やった(2018-12-15)

(((((26,("XFD")Len)Power,26)Subtract,(26,1)Subtract)Divide,(((("XFD",36)Decimal, ((((36,("XFD")Len)Power,1)Subtract,35)Divide,10)Multiply)Subtract,36)Base,26)Decimal)Add,1)Add

26 "XFD" Len Power 26 Subtract 26 1 Subtract Divide "XFD" 36 Decimal 36 "XFD" Len Power 1 Subtract 35 Divide 10 Multiply Subtract 36 Base 26 Decimal Add 1 Add
オマケ:引数が2個じゃないとき

ちなみに、引数が2つじゃないときもやり方は同じで

= Weekday(Date(Year(Today()), Mod(Month(Today()), 12)+1, 1),1)

なら

(((()Today)Year, (((()Today)Month, 12)Mod,1)Add, 1)Date,1)Weekday

こうなって

Today Year Today Month 12 Mod 1 Add 1 Date 1 Weekday

こう!

こうやって出来た謎の文字列を、以下の方法によって処理する。
空のリストを準備し、文字列を左から順に最後まで読んでいく。それぞれの内容に対して以下の操作を行う。
1. 関数名以外が来たらリストの先頭に挿入する。結果それまでに挿入されてきたものは後ろに1個ずれる
2. 関数名が来たときは、その関数名に対応した処理を行う。多くの関数名は「必要な個数の引数を先頭から取り出して処理の結果を先頭に追加する。」となっている。取り出すときは先頭のものから取り出すとその内容はリストから消え、後ろにあったものが前にずれて出てくる。

例題の場合

1問目
3 5 Multiply 4 1 Multiply Add

リストは最初の3で

3

次の5で

5 3

次のMultiplyで

15

次の4で

4 15

次の1で

1 4 15

次のMultiplyで

4 15

次のAddで

19

よって答えは19となる。

2問目

最初の2で

2

次の5で

5 2

次のPowerで

32

次の29で

29 32

次のPowerで32の29乗をWolfram Alphaに聞いたら

44601490397061246283071436545296723011960832

で35で

35 44601490397061246283071436545296723011960832

最後のModで余りを求める。

2

答えは本来は2となるわけだ。

おまけの場合

以下同様に

Today Year Today Month 12 Mod 1 Add 1 Date 0 Weekday


最初のTodayで2018-12-14にあたる

43448

次のYearで

2018

またTodayがあるから

43448 2018

Monthで

12 2018

12が来て

12 12 2018

Modが来て

0 2018

1が来て

1 0 2018

Addが来て

1 2018

1が来て

1 1 2018

Dateが来て

43101

1が来て

1 43101

Weekdayが来る。

2

ということで2018年1月1日は月曜日。2019年ではありません。来月求めようとしたけど、今年なのか来年なのかの場合分けが複雑化するので断念。

ここから本題

Current関数は引数を取らない関数で、逆ポーランド記法を処理するときに登場すると、そのときにリストの先頭にあったものを取り出して(pop)2回先頭に突っ込む(push)。つまり先頭が複製される。

=1+3*Current()

1 3 Current Multiply Add

だから

1 

こうして

3 1 

こうなって、そしてCurrentで以下のようになって

3 3 1 

Multiplyだから

9 1 

Addで

10 

ですが、

=1+Current()*3

1 Current 3 Multiply Add

だから

1
1 1
3 1 1
3 1
4

となる。Currentが登場する位置によって答えが異なる。乗算の可換性?何それ食えるの?

=1+3*Current()*Current()

1 3 Current Multiply Current Multiply Add

なので

1
3 1
3 3 1
9 1
9 9 1
81 1
82

前のCurrentと後ろのCurrentで値が違う、という話。

短絡評価

通常は全ての引数が順番に評価されるから

=Mod(4+Current(),Current())

の場合
4 Current Add Current Mod
というように引数間もその影響を受ける。答えはもちろん0
ただ、例えばIf関数(関数ということにしておく)の第一引数がtrueであったときは第三引数、falseとなる値だったときは第ニ引数の式は評価されない。だから

=If(0,4 + Current(), Current()) 

の後ろのCurrent()は8ではなく0になる。VB.NETなんかではIf演算子とIIf関数とかでよく違いが語られている話だ。
ちなみに、Ifs()関数とかが短絡評価しないんだが?というバグレポがある。

補遺

四則演算の演算子(+-*/)を関数に置き換えましたが、^とか他にも演算子はいっぱいある。>演算子とか。左側の式が大きかったときに1を右側の式が大きかったときは0を返すわけだ。
Style関数は、適用されるスタイルを変更する。
今までの議論を踏まえてCurrent関数のマニュアルのサンプルを読み、どういう結果になっているのか思いを巡らせてみると面白いと思う。

動機というかなんというか

理解されなかったり誤解されやすい関数でいつか説明したいとは思ってた。どのタイミングでどうやって切り出すか迷ってたけどいい機会なので。英辞郎によるto dateの意味

明日のLibreOffice Advent Calendar 2018はMucky999さんの「LibreOffice Conference 2018 Tirana の話」です。期待しています。