見出し画像

float : can't be too careful

量産Firmから離れて数年、気楽に浮動小数点 使ってprogram書くことが増えましたが、久々に はまったので、改めて研究しました。

浮動小数点の基本 (32bit)

まず、浮動小数点の基本を説明します。

> イメージ

浮動小数点のイメージは以下の通りです。

画像1

実際は、-10.25 = -0.1025 x 10^2でも良い訳で、表現が一意に決まりませんが、以下できちんと説明して行きます。

> 符号部

これは、簡単ですね。
正:0
負:1
です。

今回は、1になります。

> 仮数部

仮数部を説明する前に、おさらいですが、
小数部の表現は、以下のようになります。

画像2

よって、当然、全ての小数を表現しきれず、誤差を生じ得ます(たまたま、ぴったり表現できる時もありますが)。
ちなみに、0.25は、以下のように、たまたまぴったりと表現できました。

画像3

仮数部は、この小数部と、整数部を合わせて表現します(下図)。
小数部について、残り(右側)は0で埋めます。

画像4

> 指数部

指数部では、 仮数部でshiftした桁数を戻します。今回は、仮数部の処理で左に3 shiftしたので、右に3 shiftして戻します。
よって、0000 0011 としたい所ですが、指数は、右 or 左の方向があり、これを正負で表すため、8bit(0 - 255)の真ん中である127をcenter(zero)として表現します。よって、offset Bias = 127を足して、指数部は、以下のようになります。

画像5

以上で、-10.25を浮動小数点で表現することができました。
ちなみに、仮数部の説明で、「1.xxxになるよう」と言う部分がありますが、例外があります。
そうです。0(zero)ですね。0の時は、全てのbitが0になります。

さらにちなみに、1の時は、 

0 | 0111 1111 | 0000 0000 0000 0000 0000 000

です。

floatのbit列をprintするprogram

gitHubにsourceをupしました。

floatに関する注意点

1. 小数点以下で誤差が生じる
2. 整数部が大きくなるに連れて、小数部の精度が悪化していく

上記で、特に2. は注意が必要です。これは、整数部と小数部をまとめて仮数部に格納するためで、整数部の桁が上がると、小数部の桁が1つ落ちてしまう ために生じる問題です。
Art系のprogrammingでは、時間をfloatで扱い、各種Animationなどにこれを適用することも多いと思いますが、時間と共にAnimationのキレがなくなっていく(精度が落ちていくので)、と言うわかりにくいバグの温床となります。

installationなどでは、長時間の展示(丸1日から長い時で1ヶ月走らせっぱなしとか)になることが多いので、注意が必要です。

結論

> openframeworks : 今後

基本的に全て整数型で対応する。
時間に関しては、ofGetElapsedTimef()でなく、ofGetElapsedTimeMillis() or ofGetElapsedTimeMicros()を使用する。これらは、uint64_t(unsigned long long)を返すが、long longは、64bit。signedとしても max = 9223372036854775807であり、micro secの時でも292471年まで計測できます。しかも整数型なので精度劣化はありません。

> openframework : 既存

例えば、私の音楽連動System =Gusha= の場合、経過時間を、光のAnimationに適用しています。ですので、1msくらいの精度があれば、問題ありません。

以下の検討結果から、

画像6

- 4[h]以内での運用
key inputにより、ofResetElapsedTimeCounter()を明示的にcallできる仕組み
で対応しようと思います。
club Event程度なら4[h]で十分だし、ofResetElapsedTimeCounter()で一瞬動きが乱れるものの、ほとんど気づかれることなくResetできる仕組みがあれば、問題ないでしょう。

- unity : 今後
以下のいずれか

* Time.timeSinceLevelLoad を使い、4[h]を目処にScene を切り替える (1 msの精度が必要な場合)

* Time.deltaTime(floatだが小さい数なので高精度) を使い、自分で総合時間(整数型で定義して)を積算していく

> unity : 既存

4[h]以内での運用。
club Event程度であれば、これで問題ないのでokとする。

きっかけとなった今回の失敗

音声系のProgramで、44.1kHz sampleの時、1sampleは、1/44.1kHz = 2.267e-5 [sec] (= 22.6 us)となります。
openframeworksのaudioOut()内で

static float t = 0.0;
float dt = float(1.0)/ AUDIO_SAMPLERATE;
・・・
t += dt;
・・・

と言う処理を書いてしまいました。
すると、t = 512となった所で、 

512 + 2.267e-5 = 512 (加算されない)

と進まなくなってしまい、その結果、「振動がなくなって音がでない」と言う現象に遭遇しました。
下図のように、511から512に上がる所で、整数の桁が1bit上がり、小数の精度が落ちます。

画像7

その結果、512 + 2.267e-5は、"512 + 6.10352e-5"と"512 + 0"で、より近い方の"512 + 0"に近似されてしまい、以降、時間が進まなくなったのでした。ちなみに、255から256に上がる所では、時間こそ進むものの、やはり精度は落ちています。

自主制作中に、気付くことができて、良かった。。。

編集後記

量産Firmでは、処理速度、計算誤差、の観点から浮動小数点を使わずにprogramを書いていましたが、浮動小数点は、多くの場合、感覚に合っていることから、特にArt系のprogramでは、ついつい使いがちです。

しかし、やはり、programは真面目に(できるだけ整数型で)書くべきだな、と反省しました。

「浮動小数点の誤差を生かしたArt program」と言うのも ある訳ですが、今一度、基本に立ち返って真面目なprogramを書いていこう、と思いました。

参考URL

ビットで表す数字の世界~浮動小数点編~
浮動小数点数の内部表現を取得してみよう


もしよろしければ、サポートをお願いします! 頂いたサポートは、Creatorとしての活動費に充てさせて頂きます。