【Unity】無限とは一体…うごごご! UnityではInfinityがどう扱われるのか
つい最近RangeAttributeをいじくりまわす実験をしたのですが、その中でRangeの最大値を[Infinity]として範囲指定してみました。
その中では、無事範囲を指定することができたのですが、そもそもUnityでInfinity(無限大)はどのように扱われているのか気になったので、計算や比較を行って動作を確認します。
プログラミングって突き詰めると「あれ? 俺今数学やってるんだっけ?」って考えに行き着くなんて話もありますし、数学的なネタもたまにはいいですよね? ね ?
環境
macOS 10.13 High Sierra
Unity2018.1.0f2
無限大とは何か
あまり深く追求すると数学科出身者にフルボッコにされそうなのでざっくりと説明すると以下のようになります。
ある正の数nについて、どのような正の数xに対してもn > x が成り立つとき、nを正の無限大と呼びます。
逆に負の数mについて考えるときは、どのような負の数yに対してもm < yが成り立てば、mは負の無限大となります。
もっとざっくり言えば、かぞえきれないほどおおきいかず。
無限大の記号は∞で、厨二病経験者にはおなじみのウロボロスを表したものとも言われています。
無限大はあくまで概念であり、数では表せません。哲学的ね。
数を数える単位に億や兆などがありますが、日本で使われている最上位の単位は無量大数。元々の言葉の意味は「数えきれないほど大きな数」ですが、数字に直すと10の68乗です。無限にはまだまだ程遠い。
これより上の単位として、1googol(グーゴル)というものがあります。Google先生の元ネタね。こちらは10の100乗で無量大数より大きいものの、数えることができちゃうから無限とは言えません。
googolは組み合わせることができて、10の1googol乗なんて書き方もされます。10の10の100乗乗。1googolplex(グーゴルプレックス)と呼ばれています。
宇宙の大きさとして、レオナルド・サスキンドが得た解である10の10の10の122乗乗乗という数字がありますが、これでも無限には届きません。単位はなんであっても無視できるレベル。
このように、人間の感覚では気の遠くなるような大きな数であっても、無限までは届かないんです。無限ってすごい。
無限大の数値計算上の扱い
とはいえ、プログラミングの世界ではなんらかの方法で無限大を表さないといけません。
例えば、UnityではカメラからのRayを使ってオブジェクトに何かするなんてことが可能ですが、カメラから無限大の距離までの間にあるオブジェクトを検出できます。
また、RigidbodyではDrag(抵抗力)の値を無限大に設定して、物理的にはほとんど動かないようにすることもできます。
こうした時に無限大が表現されていますが、どのように扱っているんでしょうかね。表現ができるということは、なんらかの定義がUnity内にあるはず。
と思ってスクリプトリファレンスをのぞいたらあっさりと見つかりました。
無限大を表現しているのは、みんな大好きMathfの変数であるInfinityです。Mathf.Infinityでは正の無限大を、Mathf.NegativeInfinityでは負の無限大を表現しています。
このMathf.Infinityはfloat型なのですが、Unity Answersのポストを見るとその値はfloat.PositiveInfinityから持ってきているみたい。
C#で使っている『float』というのは実は別名で、本当の名前は.NET Frameworkの中にいるSystem.Singleです。このSystem.SingleのフィールドをMicrosoftのドキュメントであるMSDNで見ると、確かにSystem.Single.PositiveInfinityで正の無限大が定義されています。
この備考欄を見ると、正の数をゼロで割った結果とありました。数学的には0で割ることは意味を持ちませんが、コンピュータの計算では、なんらかの値を持たせないといけません。
ここで登場するのがIEEE(アイトリプルイー)の754。この中では浮動小数点数の例外処理として、0で割ったら無限大を返すと定義されています。
System.Single型はこのIEEE 754に準拠しているので、同じようにゼロで割った結果を無限大にしています。
……おや? ということは自分でもInfinityが作れるかも?
無限大を算出する
無限大のレッスン初級編。floatを使って、無限大を作ってみよー。
ちょっと長くなりましたが、無限大の確認用スクリプトです。
myInfは正の無限大、myMinusInfは負の無限大になるように、それぞれ正の数、負の数を0で割っています。
正負関係なく無限大になっているかを確認するため、floatのIsInfinityメソッドを使っています。正の無限大の確認はIsPositiveInfinity、負の無限大の確認はIsNegativeInfinityで行っています。
それぞれ無限大であることを確認できたら、floatの定数フィールドとの比較です。等価演算子(==)による比較とMathfのApproximatelyメソッドによる比較の両方で確認を行います。
floatの比較なのでApproximatelyを使っていますが、無限大は数値とは違うビット列なので比較はできないつもりで入れています。
無限大だと符号が0か1、指数部が255、仮数部が0と決められているので、ビット列の一致の方が比較できそう。
結果
上のスクリプトをゲームオブジェクトにアタッチしてからゲームを実行すると、以下のログが出力されました。
自力で正の無限大、負の無限大を作ることができました。
値をそのまま表示すれば「Infinity」および「-Infinity」になっていますし、floatのフィールドで定義されている無限大とも一致しています。
MathfのInfinityと比べる
一応確認しておきましょ。
MathfにはInfinityとNegativeInfinityの両方があるので、それぞれfloatの定数フィールドと比較します。
Start()の中でMathfとの比較メソッドを呼び出すようにし、その中で比較を行います。
比較は等価演算子を使ってダイレクトに。Mathfの方は正の無限大にPositiveがついていないことに注意。
結果
スクリプトを保存してゲームを実行すると、以下のデバッグ文が出力されました。
正負どちらもfloatの無限大の定数フィールドと一致しました。MathfはUnityEngineの中にあるので中身を確認することができませんが、floatの無限大の定数フィールドと同じ値となっていることは分かりました。
floatの最大値に1を足したら無限大になるか
無限大の定義として、上の方で「どんな値よりも大きい数」と言いました。
概念的にはそうであっても、数値計算的には有限の値で最大値が定義されているはず。コンピュータの世界では、そこが有限と無限の境目なんです。
float(System.Single)にはMaxValueとMinValueが定数フィールドとして用意されているので、それを使って計算してみます。と言っても実はMSDNにMaxValueを超えた時の処理がサンプルとして載ってたり。
簡単に最大値に1を足し、最小値から1を引くようにスクリプトを編集しました。最大値を超えたら、floatではそれ以上の数値を表現できないので無限大になるはず。
結果
実行してみると……なってないじゃないですかーやだー(棒)
うっかり忘れていました。floatの有効桁数は7桁なので、10の38乗から数えて7桁目、10の32乗のオーダーで計算しないと反映されません。
なので、スクリプトを以下のように書き換えました。
多分10の32乗のオーダーから計算結果に反映されるので、その前後も含めて確認するようにしました。
その実行結果は以下の通り。
どちらも10の32乗のオーダーから計算結果に反映され、最大値を超えたら正の無限大に、最小値を下回ったら負の無限大になりました。
10の32乗、10の33乗のどちらの値を足してもInfinityになっています。どれくらい超えたかは関係ないさそうですね。
floatのinfをdoubleのinfと比べる
さて、これまではfloatの場合を確認しましたが、C#ではdouble型も使うことができます。
このdouble型もエイリアスで、本体は.NET FrameworkのSystem.Doubleです。
型が違うので直接の比較はできませんが、float型の値をdouble型にキャストして確認してみようと思います。
- floatのInfinityをdoubleにキャスト
- MathfのInfinityをdoubleにキャスト
- floatのMaxValueに値を足した結果をdoubleにキャスト
- doubleのInfinityをfloatにキャスト
- floatの最大値を超えるdoubleの値をfloatにキャスト
などの方法が考えられるので、スクリプトを使って確認してみましょ。
このCompareInfinity()をStart()の中で呼びます。それぞれのキャストの結果をコンソールに出力しています。
doubleの値はサフィックスがいらないので楽でいいですね。普段あまり使わないので新鮮。
結果
出力した結果はこちら。
上から順番に確認しましょ。
- floatのInfinityをdoubleにキャスト
float型で無限大を表す定数フィールドをdouble型に突っ込んでみてもInfinityのままでした。無限大は無限大。
- MathfのInfinityをdoubleにキャスト
Mathf.Infinityはfloat.PositiveInfinityを見ているようなので、上と同様の結果になります。
- floatのMaxValueに値を足した結果をdoubleにキャスト
こちらは数値がdouble型に格納されました。右辺の計算結果が一度float型に格納されていたらInfinityになってしまうはずですが、そうではないようです。入れ物がdoubleならそちらに合わせて数値も大きくなります。
- doubleのInfinityをfloatにキャスト
これも予想通り。doubleのInfinityはfloatでもInfinityです。
- floatの最大値を超えるdoubleの値をfloatにキャスト
doubleでは10の308乗のオーダーまで値を持つことができます。floatでは10の38乗までなのでだいぶ大きいです。この値をfloatにキャストすると、floatのMaxValueを超えてしまうので、予想通りInfinityになりました。
無限大と四則演算
無限大に四則演算を適用すること自体が意味のないことかもしれませんが、数値計算の上ではなんらかの結果(または例外)を返してくれるはず。
なので四則演算を使ってどんな値が返ってくるか実験してみます。これまでの結果から、floatの無限大を表す定数フィールドとMathfのInfinityおよびNegativeInfinityは同じようなので、Mathfを対象として計算します。
どの計算方法も、無限大と有限の数値で計算を行います。乗算と除算については0を使っての計算も含めています。
これをStart()の中で呼び、コンソールに出力される値を確認します。
結果
まずは足し算の結果から。
floatのMaxValueと同じオーダーの数字を無限大に足すとInfinityとなりました。無限大には何を足しても無限大のようで、無限大を足した場合でも結果はInfinityでした。
続いて引き算の結果です。
無限大からfloatのMaxValueと同じオーダーの数字を引いてみましたが、結果はInfinity。floatの範囲内では大きな数字でも、無限大からすると微々たるものです。
興味深いのは無限大から無限大を引いた結果ですね。NaNとは、非数のこと。数値じゃないよーという表現です。Excelでよく見る気がします。
無限大はどちらが大きいか定義できないので、無限大から無限大を引いても値が分からないのです。というか実数じゃないし。なので結果は数値として表せないよーとNaNが返ってきています。
次は掛け算です。
無限大と数値、無限大同士の掛け算では無限大になります。
0を掛けた場合にはNaNになっていますね。どんな数値でも0を掛けたら0になるはずですが、残念ながら無限大は数値ではなく概念なので0理論は適用されません。
最後に割り算の結果です。
無限大を数値で割っても無限大、というのは予想通り。
無限大を無限大で割った結果はNaN。どちらが大きいか分からないから数値にしようがないですね。これは引き算と同じ感じ。
数学的には0除算は定義できませんが、この結果を見ると0で割ったときにInfinityになっています。これは上でも扱ったように言語仕様だからですね。1だろうと無限大だろうと、C#でfloatやdoubleの正の値を0で割ったら無限大になります。
まとめ
Unityで無限大ってどう扱われているの? なんて疑問を出発点に、無限大を使った計算を行ってみました。
重箱の隅をレーザーでつつくスタイルで進めていたら、だんだんUnityそんなに関係なくなって、C#の言語仕様の話だったり、数学の話だったりに……。無限大とはいったい……うごごご!
あと何回も無限大、無限大と繰り返していたせいか、この記事を書いている間はずっとデジモンのButter-flyが頭の中で流れていました。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】RangeAttributeの最小値・最大値をConstで指定する 2018.06.14
-
次の記事
【Unity】Capsule Colliderのパラメータについて。向きも変えられるよ 2018.06.16
コメントを書く