最強の数学演算アップデート到来
/compute コマンドがヤバすぎる!
Minecraft 26.3 秋のアップデートがやってきたのですが、色鮮やかな表向きのアップデート内容の中に、滅茶苦茶強力なアップデート要素が裏に潜んでいます。
その名も /compute コマンド!
/compute コマンドは「数値プロバイダ」という機能をダイレクトに実行するコマンドです。
数値プロバイダは、ルートテーブルなどのJsonで記述される数値項目…その数値をより柔軟に表現するための機能です。
私の記憶が正しければ、1.14で定数or乱数or二項分布が選べる程度の実装しかなかったはずです。ルートテーブルなどでランダムな数を扱える程度のお話。確かに1.20.2にならないと/randomコマンドは実装されないわけで、乱数が使えるってのはかなり羨ましいことではあったのですが、今となっては「別に…」っていうリアクションになりますね。
ちょっと昔話
/randomのない時代に乱数ってどうやって取得していたのか?
エンティティのUUIDを使っていました。
軽量なエンティティを召喚して、ほぼ重複することがないと期待できるUUIDの数値を乱数代わりに使っていました。UUIDはint*4の配列でNBTに保存されているので、それのどれかをスコアに代入して乱数としたわけです。もちろん、エンティティ召喚の高いコストは払いますし、乱数取得したら即破壊します。
どのintを取るかでちょっとした議論があったようですが、まだマシなレベルですね。
さらに昔(1.16未満)だとUUIDはlong*2なので、そのままじゃスコアに入りきらないという地獄みたいな問題がありました。
数値ではなくランダムにアイテムを手に入れるだけなら、エンティティのドロップを管理するルートテーブルで直接出すことができるので(死亡時のルートテーブルはデータタグで個別に差し替え可能)、ガチャをすることは昔からできていました。
主にエンダーマイトが犠牲になっていましたね。小さく。鳴き声もなく、万が一破壊し損ねても時間による寿命があるので被害は少なめだったからです。
バージョンアップに伴い数値プロバイダがスコア(1.17)やストレージ(1.20.5)を参照できるようになりましたが、根本的な計算能力なんてなかったんですよ。
なので、私も全く注目していませんでした。
スコアで計算した結果をルートテーブルに持ち込めるなどの利点はありますが、本質的にコマンド側でできる演算が限界でしたし、大きな変化とは思っていなかったのです。
ところが、1.26.1で加算sum(現在のadd)が実装され出した辺りから様子が変わってきます。
1.26.3のスナップショットで突然内容が充実し始めたのです。
私はこの頃、データパック開発が一時的にできていなかったので、この変化に気付いたのは正式版リリース後でした。
当時リアルタイムで見ていた人には「何事!?」って思っていたかもしれませんね。正式リリース時には完全な演算装置になっていたのですから…。
四則演算はもちろん、平方根、累乗、三角関数、ユークリッド距離…基本的な演算なら大体こなせるようになりました。過去の実装にあるスコア・ストレージ参照を入力とし、演算結果を出力する…しかもそれだけ強力な処理を/computeによってコマンド側で扱えるようになった。
もう今の数値プロバイダは数学的処理専用の関数と言っていいでしょう!
ちょっと使っていかない?
そんなこんなで、唐突に数学処理が滅茶苦茶強力になってしまったMinecraftなわけですが、こんな強力な機能…使わないのは勿体ないことです!
というわけで、少し昔話(過去実際にやったことのある実装)も挟みながら、現代的マイクラ数学ライフの一例を示していきます。
まずは文法から
ここまで/computeコマンドの話のみをしていましたが、実は/dataコマンドにもサブコマンドとしてcomputeが実装されており、前者は整数で受け取りたい場合、後者は実数で受け取りたい場合に利用するのが良いかと思います。
数値プロバイダ(共通項目)
数値プロバイダはcontext_float_providerかcontext_int_providerディレクトリにjsonファイルで記述することができます。再利用できるので、基本はこちらの方が有効です。
floatの加算
{
"type": "minecraft:add",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math",
"path": "y"
}
]
}
これはstorage xxx:math x,storage xxx:math yをfloat値として参照し x+y の演算を行います。
intの乗算
{
"type": "minecraft:mul",
"inputs": [
{
"type": "minecraft:score",
"target": {
"type": "fixed",
"name": "#xxx:math.x"
},
"score": "var"
},
{
"type": "minecraft:score",
"target": {
"type": "fixed",
"name": "#xxx:math.y"
},
"score": "var"
}
]
}
これはscore #xxx:math.x var,score #xxx:math.y varをint値として参照し x*y の演算を行います。
こちらは乗算の結果intの限界値を超える可能性があるので注意してください。越えてしまった場合は内部で例外が発生し、コマンドも失敗に終わります(基本0が返る)。
/compute コマンド
compute default float xxx:add <scale>
compute default int xxx:mul <scale>
defaultは他にblock,entityを指定できますが、純粋な演算を行うだけならdefaultで問題ありません。むしろ、ブロックやエンティティに依存する何かで演算結果が変わると非常に困ります。
floatを指定したらcontext_float_providerの数値プロバイダを参照し、context_int_providerの数値プロバイダを参照します。同じ数値プロバイダでもintとfloatでは使える演算機能が一部異なったり挙動も微妙に異なることがあるのでキッチリ区別されています。
/computeの演算結果は整数での取得となります。これはfloat指定でも変わりません。float指定は数値プロバイダ内での数値の扱い方であり/computeコマンドはそれを整数の戻り値で返します。
<scale>倍して小数点以下を拾うことは可能です。
結果の代入方法
execute store result score #xxx:math/result var run compute ...
整数値での取得なので、基本は上記のようなスコアへの代入形式で利用します。
結果を実数値(float値)のまま取得したい場合は後述する/data modify ... set computeを利用しましょう。
インライン記述
# float: x+y
compute default float \
{\
type:add,\
inputs:[\
{type:storage,storage:"xxx:math",path:x},\
{type:storage,storage:"xxx:math",path:y}\
]
}
# int: x*y
compute default int \
{\
type:mul,\
inputs:[\
{type:storage,storage:"xxx:math",path:x},\
{type:storage,storage:"xxx:math",path:y}\
]
}
短い演算はインラインでの実装をお勧めします。
ファイルをmcfunctionとjsonの2つに分けると、参照するストレージやスコアを別々のファイルで記述ミスなく管理する必要があります。
スコア名などを関数側でのみ変更すると正しく動作しなくなるなどの危険性が出てくるので、疲れた時の凡ミスに繋がるかもしれません。
/data modify ... set compute コマンド
data modify storage xxx:math result set compute default float xxx:add
data modify storage xxx:math result set compute default float xxx:add
基本は/computeコマンドと同じです。<scale>倍の項目がない点を除けば、computeサブコマンドの先は/computeコマンドと同じ記述ができます。インラインも可能です。
大きく違う点は、演算結果をfloatとしても取得できる点ですね。int指定をすれば結果もint値になりますが、float指定をすれば結果はfloat値となり、storage:math resultに保存されます。
int or float ?
computeにはintとfloatの指定がありますが、どちらを選ぶべきかと言われると…迷っているならfloatの方が無難でしょうか。
もちろんint側にしかない機能もありますし、同じ機能でもintとfloatで挙動が違うこともあります。
ただ、その点に問題がないと確認できたなら、大は小を兼ねるではないですが、演算を実数で行ってくれるfloat指定の方がいいと思います。
これは整数値を返す/computeコマンドでも同じです。自前で固定小数点の仕様を決めておけば、適切な<scale>倍で固定小数点として値を取得できます。
int指定だと演算の挙動もint的で、除算が整数の除算になったりと実数的な結果を期待できません。
逆に、演算が整数的だと都合がいい場合もあるかと思います。ナンバープロバイダー - Minecraft Wikiを参考にしてください。
何ができるかな?
値の参照
定数
{
"type": "minecraft:constant",
"value": 1024
}
これは、そのまま即値(上記の例なら1024)をそのまま記述しても問題ないです。
scoreから代入(int専用)
{
"type": "minecraft:score",
"target": {
"type": "fixed",
"name": "#xxx:math/score.x"
},
"score": "var"
}
スコアから整数値を取得します。float値として扱いたい場合は後述するfrom_intで変換する必要があります。
storageから代入
{
"type": "minecraft:storage",
"storage": "xxx:math/storage",
"path": "x"
}
ストレージから整数or実数値を取得します。整数か実数かは文脈に応じて変換されます。数値以外は0となります。
均一分布
{
"type": "minecraft:uniform",
"min": {
"type": "minecraft:storage",
"storage": "xxx:math/uniform",
"path": "min"
},
"max": {
"type": "minecraft:storage",
"storage": "xxx:math/uniform",
"path": "max"
}
}
min~max範囲内の確立に偏りのない一般的な乱数です。
後述するbinomialは値ごとの確率がそれぞれ異なります。
二項分布(int専用)
{
"type": "minecraft:binomial",
"n": {
"type": "minecraft:storage",
"storage": "xxx:math/avg",
"path": "x"
},
"p": {
"type": "minecraft:storage",
"storage": "xxx:math/avg",
"path": "y"
}
}
pの確率で成功する試行をn回繰り返す(二項分布)時、何回成功したかを返します。
端的に言えば、p=0.5(50%)ならコイントスが、今回は何回成功したかを返す乱数です。
n=10なら10回。10回トスして1回も成功しないかもしれないし、5回成功するかもしれない、うまくいけば10回全部成功するかも…。でも、10回成功する確率というのはレアなことであり、5回だけ成功するよりも低い確率になります。単純な乱数ではこの起きうるレアリティの差が表現できません(5回成功の確率が最も高く、0回or10回成功の確率が最も低い)。
なので、上記の条件で乱数を取得すれば、5が出やすくなります。
環境属性
これはディメンションのカスタマイズに食い込む話なので今回は割愛します。
ここでは純粋な数学演算関連に的を絞ります。
エンチャントレベル依存値
これはカスタムエンチャントのレベル依存値というものが関係するので割愛します。
ここでは純粋な数学演算関連に的を絞ります。
値の変換
float ← int(float専用)
{
"type": "minecraft:from_int",
"input": {
"type": "minecraft:score",
"target": {
"type": "fixed",
"name": "#xxx:math/score.x"
},
"score": "var"
}
}
これはほぼスコアをfloat値へ変換する時に使います。
float ← int(int専用)
{
"type": "minecraft:from_int",
"input": {
"type": "minecraft:floor_div",
"left": {
"type": "minecraft:score",
"target": {
"type": "fixed",
"name": "#xxx:math.x"
},
"score": "var"
},
"right": {
"type": "minecraft:score",
"target": {
"type": "fixed",
"name": "#xxx:math.y"
},
"score": "var"
}
}
}
float指定内でint専用の演算を埋め込みたい場合、これで明示的に変換します。
from_floatと違って、なかなか使う機会はないかと思います。
丸め処理
-∞方向に切り捨て
{
"type": "minecraft:floor",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/floor",
"path": "x"
}
}
-∞方向に切り捨てます。正の値では直感的な切り捨てになりますが、負の値では-0.5が-1.0となります。切り捨てる基準が0ではなく-∞だからです。
+∞方向に切り上げ
{
"type": "minecraft:ceil",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/ceil",
"path": "x"
}
}
+∞方向に切り上げます。これも負の値では-0.5が0.0となります。切り捨てる基準が0ではなく+∞だからです。
最近接丸め
{
"type": "minecraft:round",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/round",
"path": "x"
}
}
シンプルに表現するなら実数で行われる四捨五入です。これは負の値でも-0.5が-1.0に-0.4999...が0.0となり直感的な丸め方になります。
0方向丸め
{
"type": "minecraft:truncate",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/truncate",
"path": "x"
}
}
0方向に丸めます。0.5,1.5は0方向に丸めて0.0,1.0、-0.5,-1.5も0方向に丸めて0.0,-1.0となります。直感的な小数点切り捨て方法…というより小数点削除と言うべきでしょうか?
四則演算+α
x+y
{
"type": "minecraft:add",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/add",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/add",
"path": "y"
}
]
}
これは3つ以上の加算も可能です。inputsの配列内に追加でz以降の値を追記してください。
-x
{
"type": "minecraft:negate",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/negate",
"path": "x"
}
}
これはシンプルに-xです。後述するsubはnegateとaddの組み合わせでも実装可能です。
x-y
{
"type": "minecraft:sub",
"left": {
"type": "minecraft:storage",
"storage": "xxx:math/sub",
"path": "x"
},
"right": {
"type": "minecraft:storage",
"storage": "xxx:math/sub",
"path": "y"
}
}
これは2項演算のみです。1つの値に対して2つ以上の値を減算したい場合は入れ子かnegateとaddを組み合わせると良いでしょう。例:(x-y)-z orx+(-y)+(-z)
x*y
{
"type": "minecraft:add",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/mul",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/mul",
"path": "y"
}
]
}
これも加算と同じく3項演算が可能です。
x/y(0方向)
{
"type": "minecraft:div",
"left": {
"type": "minecraft:storage",
"storage": "xxx:math/div",
"path": "x"
},
"right": {
"type": "minecraft:storage",
"storage": "xxx:math/div",
"path": "y"
}
}
x/y(-∞方向/int限定)
{
"type": "minecraft:floor_div",
"left": {
"type": "minecraft:storage",
"storage": "xxx:math/floor_div",
"path": "x"
},
"right": {
"type": "minecraft:storage",
"storage": "xxx:math/floor_div",
"path": "y"
}
}
除算も2項演算限定なので、3項以上は入れ子で対応しましょう。例:(x/y)/z
floor_divは負の結果の扱いが変わってきます。
一般的に除算の結果が1.5,0.5,-0.5,-1.5となった場合、整数に丸めるなら1,0,0,-1と考えられます。
しかしfloor_divは1,0,-1,-2となります。
これは0を基準に考えるか、-∞を基準に考えるかの差です。後者はどんな負の結果でもより小さい方に丸める考え方です。
x%y(0方向)
{
"type": "minecraft:mod",
"left": {
"type": "minecraft:storage",
"storage": "xxx:math/mod",
"path": "x"
},
"right": {
"type": "minecraft:storage",
"storage": "xxx:math/mod",
"path": "y"
}
}
x%y(-∞方向/int限定)
{
"type": "minecraft:floor_mod",
"left": {
"type": "minecraft:storage",
"storage": "xxx:math/floor_mod",
"path": "x"
},
"right": {
"type": "minecraft:storage",
"storage": "xxx:math/floor_mod",
"path": "y"
}
}
剰余算、x/yの余りを算出します。剰余算も除算同様に丸め方が2種類ある点に注意してください。
基本数学関数
最大
{
"type": "minecraft:max",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/max",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/max",
"path": "y"
}
]
}
これは3つ以上の値から最大値を算出することもできます。その場合はinputsに追加の値を追記してください。
最小
{
"type": "minecraft:max",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/min",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/min",
"path": "y"
}
]
}
こちらも同様に3つ以上の値から最小値を算出できます。
範囲内への丸め
{
"type": "minecraft:max",
"inputs": [
{
"type": "minecraft:min",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/clamp",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/clamp",
"path": "max"
}
]
},
{
"type": "minecraft:storage",
"storage": "xxx:math/clamp",
"path": "min"
}
]
}
最大と最小を使って範囲内にxを丸めるclampを実装することができます。
プロセスは値に対して「最大値と最小比較」して「最小値と最大比較」する流れです。
x^y
{
"type": "minecraft:pow",
"base": {
"type": "minecraft:storage",
"storage": "xxx:math/pow",
"path": "x"
},
"exponent": {
"type": "minecraft:storage",
"storage": "xxx:math/pow",
"path": "y"
}
}
累乗を計算します。int値では桁あふれに注意が必要なのと、負の指数(y<0)も対象外です。
そして0^0は色々と論争を巻き起こしている問題であり、MinecraftではNaN(こんなもん数値じゃない!)という結果となりint値では失敗、float値でも困ったことになると思いますので控えましょう。
√x(float限定)
{
"type": "minecraft:sqrt",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/sqrt",
"path": "x"
}
}
一応、√4=2のように整数の結果となる平方根もあるにはあるのですが、数値プロバイダ上ではfloat値でのみ処理可能です。
|x|
{
"type": "minecraft:abs",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/abs",
"path": "x"
}
}
絶対値を取得します。後述する条件分岐を使えば自前実装もできますが、ちゃんと数値プロバイダは実装してくれています。
平均値
{
"type": "minecraft:avg",
"operands": [
{
"type": "minecraft:storage",
"storage": "xxx:math/avg",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/avg",
"path": "y"
}
]
}
平均値を取得します。3つ以上の値を指定可能です。inputsに値を追記してください。
幾何学的数学関数
ユークリッド距離
{
"type": "minecraft:length",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/length",
"path": "x"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/length",
"path": "y"
},
{
"type": "minecraft:storage",
"storage": "xxx:math/length",
"path": "z"
}
]
}
ユークリッド距離と聞くと馴染みがないかと思いますが、要は(x,y)だけなら2D平面距離、(x,y,z)なら3D立体距離となります。何次元にでも対応できているだけで、結局はユークリッド幾何学におけるただの距離です。
これをスコアだけで算出するのにどれだけ苦労したことか…。
√x^2+y^2+z^2を計算しようとすると、x^2が桁あふれしないよう怯えて、平方根に至っては「ニュートン法」による近似ができるのですが、条件がうまくかみ合わないと値が収束しないという恐ろしい性質があるため、平方根の筆算技術である「開平法」でちまちまと…。
固定小数点環境でこんなんしてたら精度が出るわけないだろ!!
そもそもコマンド数が大変なことになって処理は落ちるしコマンド数限界にも到達するし…。
三角関数
弧度法 ← 度数法
{
"type": "minecraft:mul",
"inputs": [
{
"type": "minecraft:mod",
"left": {
"type": "minecraft:storage",
"storage": "egg:fixed/rotation_to_radians",
"path": "in-data[1]"
},
"right": 360
},
0.01745329251
]
}
数値プロバイダの三角関数は弧度法を引数に取るので、Rotationなどの度数法の角度は一度変換する必要があります。
この変換で-360~360度にしておけば弧度法の角度を固定小数点のスコアに格納しやすくなります。逆に0~360度のように完全な最適化を考えると、取得時の演算がむしろ複雑になるので、これくらいが落としどころですね。
度数法 ← 弧度法
{
"type": "minecraft:mul",
"inputs": [
{
"type": "minecraft:storage",
"storage": "xxx:math/degrees",
"path": "radians"
},
57.29577951308232
]
}
現在逆三角関数がデフォルトで実装されていないので、弧度法から度数法への変換を使う場合は、後日投稿予定の逆三角関数実装をする必要があります。
sin
{
"type": "minecraft:sin",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/sin",
"path": "x"
}
}
sinを取得します。弧度法の角度を渡してください。
cos
{
"type": "minecraft:cos",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/cos",
"path": "x"
}
}
cosを取得します。弧度法の角度を渡してください。
tan
{
"type": "minecraft:div",
"left": {
"type": "minecraft:sin",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/tan",
"path": "x"
}
},
"right": {
"type": "minecraft:cos",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/tan",
"path": "x"
}
}
}
tan=sin/cosという関係性はあまりにも有名。デフォルトでtanは実装されていないですが簡単に算出できます。ただし、cos=0の時は0除算となり失敗します。90度と270度でtanは無限遠方にぶっ飛んでいきますので…。
まあ、これをスコアボードと固定小数点だけでごり押し実装したことがありまして…。
実装方法は四則演算ができればなんとかなるテイラー級数でした。とはいえ、固定小数点は何かと癖のある実数表現です。
精度も大したことなく、それ以上に使用するコマンド数が尋常じゃないことになりまして、人生初めてコマンド実行数限界を体験しました。
つい最近だとexecute幾何学を利用してある程度の精度と処理速度を確保していました。
原点にエンティティを召喚して、y軸に対して特定の角度を向かせ、そのエンティティをteleport ^ ^ ^1で1m先に移動させることで、x座標とz座標が三角関数の結果になります。
エンティティの召喚などのコストはあるものの、流石にスコアで級数積み上げるよりはるかに精度が良かったです。
条件分岐
if ... else ... 分岐
{
"type": "minecraft:conditional",
"condition": {
"type": "minecraft:float_value_check",
"value": {
"type": "minecraft:storage",
"storage": "xxx:math/sgn",
"path": "x"
},
"test": 0
},
"on_true": 0,
"on_false": {
"type": "minecraft:div",
"left": {
"type": "minecraft:storage",
"storage": "xxx:math/sgn",
"path": "x"
},
"right": {
"type": "minecraft:abs",
"input": {
"type": "minecraft:storage",
"storage": "xxx:math/sgn",
"path": "x"
}
}
}
}
minecraft:conditionalはif構文です。
上記の例はconditionalで実装した符号取得のsgn xです。
if condition then on_true else on_falseみたいなイメージです。on_true,on_falseは条件の審議によって手に入る値で、これまでの演算を全て使えます。
conditionはpredicateに分類され、上記ではインラインで表記していますが、predicateディレクトリ内に別ファイルで記述も可能です。まあ、一意な固有のストレージ(スコア)を参照している場合、再利用性もないのでインラインの方が読みやすいかもしれません。
上記の例だとストレージに3回もアクセスすることになりそうで、処理効率が心配ですね。何度もアクセスするならスコアに一度保存した方がいいかもしれません。
{
"type": "conditional",
"condition": {
"type": "minecraft:float_value_check",
"value": {
"type": "minecraft:storage",
"storage": "xxx:math/sgn",
"path": "x"
},
"test": 0
}
}
基本的に、純粋な数学処理を行うならconditionで使う項目はint_value_checkかfloat_value_checkくらいですね。
value部分は数値プロバイダと同じ記述ができます。なので、チェック用に変換したり、演算したりも可能です。
testは値指定か範囲指定が可能です。
0
{
"test": 0
}
0以上
{
"test": {"min": 0}
}
0以下
{
"test": {"max": 0}
}
-1から1まで
{
"test": {"min": -1, "max": 1}
}
if ... else if ... else 構文
{
"type": "minecraft:number_dispatcher",
"cases": [
{
"condition": {
"type": "minecraft:float_value_check",
"value": {
"type": "minecraft:storage",
"storage": "xxx:math/sgn",
"path": "x"
},
"test": 0
},
"value": 0
},
{
"condition": {
"type": "minecraft:float_value_check",
"value": {
"type": "minecraft:storage",
"storage": "xxx:math/sgn",
"path": "x"
},
"test": {"min": 0}
},
"value": 1
}
],
"default": -1
}
number_dispatcherはswitchライクに見えますが、条件をそれぞれに詳細に指定できるのでどちらかというとif ... else if ... else if ... else ...というif構文の連続に近いです。
上記の例はsgn xのnumber_dispatcher版です。
casesの配列にconditionとvalue(on_true) のセットを判定する順番に記述していきます。度の条件にも当てはまらない else ... 部分はdefaultに記述します(valueと同じ文法)。
手前から順番に判定するので、上記の例では、
- 0の時 (x=0)
- 0以上の時 (実質 x<0)
- それ以外 (実質 x>0)
という風に場合分けしています。
結局ツール使うんですけどね。
凄い便利な数値プロバイダですが、ぶっちゃげ気合で記述するとえらいこっちゃになります。後日投稿予定の atan2 の実装では1000行超えてきたのでツールにがっつり頼りました。
# 加算
x+y
# 減算
x-y
# 乗算
x*y
# 除算
x/y
# 剰余算
mod(x,y)
# 累乗
pow(x,y)
# 平方根
sqrt(x)
# ユークリッド距離
length(x)
# 三角関数
sin(x)
cos(x)
tan(x)
# 条件分岐 (<on_true>, <on_false> 部分も上記の記述が可能)
# <condition>のpredicateは
# ・インライン記述
# ・リソース指定(xxx:yyyy)
# ・Misode Predicate GeneratorのURL (http://misode.github.io/predicate/?share=...)
# Misode Predicate Generatorで作って、シェアで得た共有用URLを代わりに記述できます
# 略記として ... 部分の共有IDだけでも指定可能です
if(<condition>?<on_true>;<on_false>)
# 複数条件分岐 (<value>, <default> 部分も上記の記述が可能)
# <condition>は if と同様の手法が扱えます
nd[(<condition>?<value>),(<condition>?<value>);<default>]
Misode Predicate Generatorでpredicateを記述して、共有IDで<condition>を記述しておけば出力時にはpredicateをインライン展開してくれます。
リソース指定の場合は独自で別のpredicateファイルを用意する必要があります。
変数は1文字でx,yと指定すれば自動で詳細設定項目が表示され、スコアやストレージの詳細指定が可能です。'yaw','pitch'のように2文字以上にする場合はクォートで囲む必要があります。
predicate周りはちょっと記述が大変かもしれませんが、ほぼ直感的にプログラムの演算感覚レベルで記述できます。お勧めします。
Function Library の項目には簡単な関数を定義できます。
atan
{1}/(1+pow({1},2)/(3+4*pow({1},2)/(5+1.28571428571*pow({1},2))))
ガウスの連分数(4項)によるatanのシンプル実装です。このままでは大きなxで精度が落ちるのでもうちょっと工夫が必要ですが…。
{1} が1番目の引数を意味します(Add parameterボタンを押さないと引数が定義されない点に注意)。
こういうこともできるので、複雑な実装もかなりストレスなくできると思います。
参考程度に
実際に三角関数などを実装して参考程度に精度計測したもの
演算の処理速度も参考程度に調べてみた
こんな感じです。
精度はぶっちぎり、処理速度もおそらく上々…。
皆さんもぜひ数値プロバイダをお試しください!