MaxScriptを書こう ~その13

2021年9月3日

四捨五入する

ここまで私たちのadjustPosツールを通じてスクリプトのいろいろなことを学んできましたが、肝心の処理そのものについては目を瞑ってきました。今回はその処理を見直してみましょう。

第二回のときに、元の座標が3.99999という値でも、実行すると3.0になってしまう、という点を問題にしましたが、そのまま素通りしてきました。やはりこれは少々問題がありそうです。問題は数値を丸める際に切り捨てを使っているところにあります。これを四捨五入にしましょう。

そう考えてMaxScriptのリファレンスを探すと、なんと、MaxScriptには四捨五入の関数は用意されていないようです。切り捨て(floor)と切り上げ(ceil)はありますが、四捨五入(一般にroundという関数で用意されていることが多い)は存在しません。

そこで、四捨五入を行う関数roundを定義しましょう。

せっかくなので、これも前回作成したcommonLib.msに書いてしまいます。既にcommonLib.msに記述済みの関数adjustPosでも新たに作るroundという関数を使うことになるので、書く位置としてはadjustPosよりも前(上)になるようにしてください。

※四捨五入関数はどのようにしたら作れるか、先を読む前に考えてみることをオススメします。

function round num = (
    return floor (num + 0.5)
)

私はこんな感じにしました。四捨五入したい数値をnumという変数で引数として受け取り、その値に0.5を足してからfloorで切り捨て、出来上がった値をreturnで返しています。

これでfloorやceilと同じように動作する四捨五入関数ができましたので、adjustPosの中でfloorを使っているところをこのroundに書き換えましょう。

function round num = (
    return floor (num + 0.5)
)

function adjustPos obj = (
    obj.pos.x = round obj.pos.x
    obj.pos.y = round obj.pos.y
    obj.pos.z = round obj.pos.z
)

関数にはどんな処理を行う関数なのか、コメントを書いておくとあとで再利用するときに便利です。

四捨五入する桁を指定できるようにする

少し改善するとさらに改善したくなるのが世の常(?)というもの。ビルトイン関数のfloorやceil、さらにそのfloorを使って作ったroundという関数はすべて、小数第一位を処理して整数に丸める、という処理になっていますが、これを小数第一位まで求める、小数第二位まで求める、など指定できるようにしたいですよね。さすがに小数点以下が5桁もあるのはやりすぎにしても、じゃぁ整数で、というのではあまりに乱暴です。適度に必要に応じて処理できるようにならないものでしょうか。

そこで次はこの桁指定をできるようにしてみましょう。

まず、関数の設計を考えます。桁指定をするということは、その指定を関数に知らせる必要があるので、引数をもう一つ渡す必要が生じます。もちろん指定する桁数を渡せば良さそうです。問題はそれを受け取ってどういう処理をすれば目的の結果を得られるか、という点です。

function round num digits = (
    --受け取ったdigits(桁数)に基づいて処理をしたい
)

これを元に中身を考えましょう。

例えば桁数として1を渡したとき、小数第二位を四捨五入して小数第一位までの答えを返す、という仕様にしましょう。つまり2番目の引数では、最終的に得られる数の小数点以下の桁数を指定する、ということになります。0を渡した場合は整数を返す、という風になります。

ここで、中で使うことになるfloor関数が整数に丸める操作しかできないことに注目し、この処理で求める結果を得られるように工夫しましょう。

function round num digits = (
    local target_num = num * (pow 10 digits) + 0.5
    local ret_val = (floor target_num) / (pow 10 digits)
    return ret_val
)

こんな風にしてみました。

まず、受け取った桁数(最終的に欲しい桁数)分だけ小数点の位置を移動させます。10進数で小数点の位置を移動させるということは、10のべき乗を行えば良い、ということになります。

べき乗計算はビルトイン関数のpowで行うことができます。pow a bと2つの数値を渡すことで、aのb乗を得ることができます。

必要な分だけ小数点を移動させた上で0.5を足し、その数をfloorにかけることで四捨五入した結果を得ます。これは小数点を移動した上で整数を得た状態になっているので、元の位置まで小数点を戻す(10のべき乗で割る)という操作を行い、得られた数をreturnしています。

引数にデフォルト値を与える

round関数に新機能を足したのは良いのですが、このままだとadjustPos()関数がエラーを吐くことになります。なぜなら、round()関数が引数を2つ要するようになったのに、adjustPos側はこの関数の呼び出しで1つしか値を渡していないからです。引数が不足している、というエラーになってしまうのですね。

もちろんadjustPosからの呼び出しを書き直せば良いのですが、ここではround関数の2番目の引数を渡さなかった場合は0が渡されたものとする、という処理にしてみましょう。

この変更は実は非常に簡単です。

function round num digits:0 = (
    local target_num = num * (pow 10 digits) + 0.5
    local ret_val = (floor target_num) / (pow 10 digits)
    return ret_val
)

このように、デフォルト値を設定したい引数に:(コロン)を付け、その後ろにデフォルト値を書くだけです。

※引数が複数ある場合、デフォルト値を設定する引数は設定しない引数より後に書く必要があります。今回の例の場合、デフォルト値を設定するdigitsを設定しないnumより前(左)に書くことはできません。

これで呼び出し側を変更しなくてもエラーは出なくなりました。

桁数がマイナスの場合の挙動

2番目の引数に負の数(マイナスの数値)を渡した際はどうなるでしょうか。二番目の引数は小数点を右に移動させる(数値を10倍ずつ大きくする)のに使っているので、これがマイナスだと逆に左へ移動することになります。つまり結果として「10の位まで求める」「100の位まで求める」といったような処理になるわけです。

試してみましょう。

ここで、デフォルト引数を指定した引数は、呼び出し時にその引数名を用いて「引数名:値」という書き方で渡す必要があります。この点に注意しましょう。

今回は2番目の引数が負数でも問題なく整合性のある結果が得られるのでこのままで構いませんが、渡す値によっておかしな結果になってしまう場合は、受け取った引数の内容に応じて処理を分けるような実装にする必要があるかもしれません。

次回はこの桁指定をツール上から設定できるようにしましょう。

今回のまとめ

  • 既存の関数をカスタムしてより使いやすい関数を作ることができる
  • 引数にデフォルト値を設定することができる
  • デフォルト値を設定した引数は呼び出し時に「引数名:値」という形で渡す

前:MaxScriptを書こう ~その12

次:MaxScriptを書こう ~その14