izhoudiniTweetこの記事ではHScriptのround()関数を少し詳しく掘り下げてみようと思います。「与えられた小数に対して、その小数に一番近い整数に丸め込んだ値を返す」関数となります。このようにみると、一見、round()関数は四捨五入を行うような関数にも見えるかと思いますが、実際のところどうなのでしょうか。引数に負の数を入れてみます。上の結果は恐らく、正しいのでしょう。下の結果、この結果は四捨五入として考えると、正しいでしょうか。-1.5であれば、-2になるべきでは?と考える方もいらっしゃるかと思います。これが「HoudiniのRound()関数は厳密には四捨五入ではない」の真実のひとつです。そもそも、四捨五入というのは実はあいまいな概念で、例えば、マイナスになると、その解釈は様々です。インディゾーンでも調べてみましたが、「四捨五入」という日本語には、正確な定義を見つけることはできませんでした。正確に定義されていないものは、コンピュータの世界で一般に採用されることはまずないでしょう。その代わりにコンピュータでは「似たような厳密な定義」として「丸め」というものがあります。これは英語ではRoundingと呼ばれるもので、日本ではround()関数が丸め≒四捨五入の関数として認知されております。(実際、四捨五入は英語に訳すとRoundとなり、四捨五入は丸めの中の一つの手法概念という位置づけになります。)しかし、実はこの丸めも定義が結構バラバラとなっています。基本的には丸めでは、指定した桁以下の数字を、指定した桁の数字の中で近いものに更新します。その結果として、最後の桁数が5になり、両方の数字からの差が均等になる、いわゆる中間点の扱いが浮いてしまうのです。日本の場合、丸めの定義はJIS8401で定義されていますが、それによると、0.5などの中間点の扱いは以下のようになっています。丸めた数値として偶数倍のほうを選ぶ。丸めた数値として大きい整数倍のほうを選ぶ。この時点で定義が2つあります。規則Bは一般に言う四捨五入に近いものになりますが、直観的に考えると、規則Aはおかしい、と思われるかもしれません。コンピュータで計算を行う上ではこちらの方が都合がよいこともあります。コンピュータと人間では様々な違いがあるのです。※規則Aには、丸めによって生じる統計的な誤差の偏り、累積を是正するような効果があり、統計分野などで広く使われます。因みに、HoudiniではJIS8401における規則Bに則る形でHScriptのround()関数が実装されております。これは別にJIS8401がHoudiniにおいて採用されている訳ではなく、この一致はただの結果論になります。JISは日本国内の規格ですので、海外製品には関係のないものであり、海外には全く違った丸めの定義があります。このあたりが、この後に記載されている各プログラミング言語による仕様の違いに話がつながっていきます。この話は少しHoudiniから離れていってしまいますので、先にもう少しだけHScriptのround()関数を掘り下げてみましょう。次は「誤差」の話になります。こちらを見てみましょう。この数字は1.5未満ですので当然、この結果は「1」が返されております。次はこちらの画像を見てみましょう。この数字も同様に1.5未満のはずですが、この結果はなぜか「2」になってしまいます。この2つの数字はいったい何が違うのでしょう。今度は縦に並べてみます。ご覧の通り、下の方が1桁多いことがわかります。理論上、この2行はいずれも1.5未満であることから、両方とも「1」が出力されないといけないのですが、なぜかそうはなりません。これが「HoudiniのRound()関数は厳密には四捨五入ではない」の真実のもうひとつです。実はこれは小数表現の誤差が原因で、浮動小数をコンピュータで扱うと、どうしても誤差が発生してしまうのです。いうのも、無限に値が小さく成りうる小数を、リソースが有限のコンピュータを使って完璧に表現するのは不可能なのです。そこで、コンピュータにおいて小数を表現する手段として、固定小数点数と浮動小数点数の2種類のデータ形式が考え出されました。右に桁が増えるにつれて1/2、1/4、1/8倍と定義する手法です。=4.75各桁がそのままビット数に対応していることから、厳密に小数を表現できるのですが、表現できる数が少なく、あまり採用されません。3部に分けて表現する手法です。端的に説明するならば、浮動小数点数は小数の類似計算手法になります。厳密な小数ではなく、近似値が出てくるもの、生成/計算の過程で誤差が発生することを前提としたデータ形式なのです。Houdiniにおいてもこちらの浮動小数点数を採用している為、このような誤差が発生してしまうのです。この誤差の原因は0.01のような、2進数で表現すると無限級数になってしまう数字を、途中で打ち切ったり丸めたりしていることです。1.4999999999999999を入力してみます。ここで、round()関数関係なく、そもそも入力した通りの値がパラメータに表示されていないことがわかります。上の数字は右から2つ目(小数点以下14桁目)の9が8になっており、下の数字は1.5に丸められてしまっています。下ではHoudini内部で自動的に丸められた数を、更にround()関数で丸めてしまったので、上記のような問題が発生しました。これを2重丸めといい、意図しない丸めが発生しうる状況下では、round()関数による丸めを行ってしまうと、このように意図せず誤差が膨れ上がってしまうことがあります。普段Houdiniを使う分には、この誤差はほぼ問題にしない数値のはずです。実際、上の数字はたまたま、数が小さくなる方に誤差が出たので、10fmの誤差は問題になりませんでした。下の数字は誤差が大きくなる方に出てしまいましたので、全ての9が繰り上がり、このような状態となってしまっています。一部の処理を固定小数で行うことで、絶対に誤差の出ない計算を行うことはありますが、HScriptやVEXにはその機能はありません。Pythonには固定小数を扱うライブラリはありますが、Houdini側が浮動小数にしか対応していないので、大きな意味はないでしょう。以上のような理由によって、HScriptのround()関数は完璧に四捨五入ができるわけではない、という話がこの記事の答えです。これはHScript、Houdiniには限らない問題である、ということもご紹介させていただきました。浮動小数点数の世界標準規格である、IEEE754のものと異なっていることが確認できました。このことからHoudiniパラメータ上ではIEEE754が採用されていない可能性が高く、以下のような問題が発生する可能性があります。・Pythonでエクスプレッションを記述した際に、コンソールでの出力結果と実際にパラメータに入力される数値が異なってしまう。・浮動小数の誤差発生の予測ができず、同様に検証も非常に難しい。精度の必要な小数計算を行う際には、このような可能性について留意する必要があります。定数倍乗算することで、整数にしてから計算を行い、計算後に先ほどの数で割って元に戻すという誤差発生回避策がよく使われます。他のプログラミング言語と同様の形式で定義されている関数が多くあります。これは一見まったく同じような挙動をとるようにも見えますが、細かく見てみると、小数の扱いや関数の記述による言語仕様の関係で、HScriptと他のプログラミング言語で、微妙に出力結果が異なったりします。先ほどの、round()関数における中間点処理を例にとってみます。同じround()関数でも、プログラミング言語によって小数部が0.5の時の扱いの違いには、以下のような様々なパターンがあります。・大きい数値に丸める(HScript、Java、JavaScriptなど)・一番近い偶数に丸める(R、MATLAB、Python3など)・0に遠い方に丸める(C99、Python2、Haskellなど)・扱いを明示的に設定する(PHP、Ruby、Swiftなど)※他にも様々な手法があります。これもHScriptだけに限らず、全てのプログラミング言語で発生しうる問題で、今回みたいなクリティカルな値を入れることで、round()関数のように、同名の関数でも言語によってどのような違いがあるのかを知ることが、重要になってくる場面があります。因みに、Houdiniにおいて言語ごとの仕様違いを一番身近に感じられるのは、三角関数の仕様でしょうか。HScriptではsin()関数の引数の単位は度[°]ですが、VEXでは全く同じ記述形式の、sin()関数の引数の単位はラジアン[rad]です。この仕様の違いを知らなければ、正しい値を出すことはできません。Houdiniではアプリケーション内で複数の言語が様々な領域で入り乱れるので、この「仕様違い」の概念と、その可能性を頭の片隅に入れておくことが、非常に重要になってきます。想定される用途に対して都合の良いものを採用していたり・・・と理由は様々です。このような関数の局所的な戻値の仕様や言語ごとの仕様の違いに目を向けると、更に汎用性の高いネットワークの作成に貢献できます。Inc.の商標または登録商標です。他のすべてのブランド名、製品名、および商標はそれぞれの所有者に帰属します。当サイトは株式会社インディゾーンが運営しています。