🫠

[Skript] 整数部n桁目を瞬時に取得できる関数を作った

に公開

ぬぽさんです。
TheMining Season-2の鉱石復活の時間(Unixtime)を管理する際に、
value<number>に対する整数部digit<integer>桁目をinteger型で取得する関数が欲しくなったので、作ってみました。

とりあえず関数作る

第一引数としてnumber型のvalue、
第二引数としてinteger型のdigit(桁数)、
返り値としてinteger型を指定しました。

function getDigit(value: number, digit: integer) :: integer:

どうやって取得しよう

まず思いついたのは、
valueを文字列にして文字のn桁目を取得する方法
どうやらSkriptにはcharacter index構文があるらしいです。
(文字列のn桁目を取得する構文)

char[acter][s] at [index] %numbers% (within|in) %strings%

実装してみる

function getDigit(value: number, digit: integer) :: integer:
    set {_string} to "%floor({_value})%"
    set {_atDigit} to char at {_digit} in {_string}
    return {_atDigit} parsed as integer

さっそく問題点

ただし、getDigit関数は毎秒数百〜数千回実行されることが予想されています。
パフォーマンス的な問題がないか確認するために、character index構文のソースコードをチラ見してみました。(ExprSubstring.javaで実装されてた)
https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/expressions/ExprSubstring.java
調べた限りだと、他の方もsubstrや文字列に変換してn桁目を見る方法をとっている方が多数でしたが、
Skriptだとsubstr系が地味に結構計算オーダがかかってるっぽいので、もっと効率的に算出する方法を考えてみました。

最終的な思いつき

さっきの実装は、わざわざ文字列に変換している+文字列のn桁目の取得に計算オーダをかなり消費するという、非効率な実装になっていたため、できればnumber型のまま数式的に算出したいと思います。

123456.789というnumber型の数字を例にすれば、整数部だけ処理するまでは一緒で良さそうです。

floor({_value})

そこで、123456を10で割って小数点を切り捨てたもの(12345)に10を掛けたもの(123450)を元の123456から引けば1桁目になるんじゃない?
という少々強引ではあるが、1桁目を効率的取得できる方法は思いつきました。

1桁目を求める
floor({_value}) - 10 * floor({_value}/10)

あれこの形って...犬は閃きました。
さっき思いついた1桁目を求める方法について、a = {_value}として数式にすると

\lfloor a \rfloor - 10 \times \left\lfloor \frac{a}{10} \right\rfloor = a \bmod 10

当たり前といえば当たり前ですが、1桁目がモジュロ演算で求められることがわかりました。
(なんでmodが出てくるかは少々長くなるので端折ります^^)
良いものが出てきたので、もちろん使わせていただきます()

元の123456の求めたいn桁目を1桁目に持ってきてさっきの数式に入れれば、n桁目もこれで実装できると思い、実装してみました。
n桁目を1桁目に合わせるのは、
123456を10で割ると12345.6となり、2桁目の5が整数部の1桁目に移動しました。

そのため...

  • {_value}を10で割って小数点を切り捨てたものを10で割った余りが2桁目になる
  • {_value}を100で割って小数点を切り捨てたものを10で割った余りが3桁目になる
  • {_value}を1000で割って小数点を切り捨てたものを10で割った余りが4桁目になる

のように考えれば、
{_value}を10のn-1乗で割って小数点を切り捨てたものを10で割った余りがn桁目になる
ということがわかりました。

実装してみた

先ほどのことを踏まえて、Skriptの関数に実装してみました。

function getDigit(value: number, digit: integer) :: integer:
    set {_coefficient} to 10^({_digit}-1)
    return mod(floor(abs({_value}) / {_coefficient}), 10)

見にくかったので、_coefficient変数に10のn-1乗を先に計算して入れておきました。

最後に

私が調べた限りでは、数式的にn桁目を算出できるこの方法を関数化をしている方はあまりいませんでした。(もしかしたら他の言語だと既にデフォルトで実装されてるのかも?)

バグやもっと効率良い方法を思いついた方は、Nekozouneko Group Developerに入れるレベルの技術をお持ちだと思いますので、ぜひ応募してみてください(最後に宣伝^^)

かなり使用頻度は低いと思いますが、少しでも参考になれば幸いです。

Nekozouneko Group

Discussion