Open10

固定少数と浮動小数の疑問。あるいは2進数と10進数 (整理中メモ)

kodukikoduki

まあ、コンピュータの性能が上がっても浮動小数使ってるしね、というお話。
https://togetter.com/li/1289806

ただ、なんでそもそも我々は浮動小数なんていまだに第一級市民として使ってるんだ? COBOLみたく固定十進演算が増えても良いじゃないかという疑問がむくりと。コンピュータ性能上がったんだし。

関連ツイート
https://twitter.com/koduki/status/1380049924419506179

kodukikoduki

固定少数は精度を指定して扱う方式。人間にやさしい。1.3は1.3であり 1.3 - 0.3 == 1.0 が常に成り立つ。
浮動小数は IEEE 754で規定。(仮数部)×(基数)^(指数部)で表す。たとえば、0.5を浮動小数点数で表すと、基数が10の場合は5.0×10^−1(5.0e-1)、基数が2の場合は1.0×2^−1となる。

浮動小数はコンピュータで一般的に使われる少数表現(float/double)だが誤差がある。そのため以下のような結果になる。

> 1.0 - 0.9 == 0.1
=> false

これは浮動小数によるためと通常は説明される。なので計算機イプシロンを使って誤差を吸収する
https://ja.wikipedia.org/wiki/計算機イプシロン

(f - g).abs <= Float::EPSILON * [f.abs, g.abs].max
# f == g もしくは f - g == 0.0 としてはならない

あるいはBigDecimalを使え、となる。

https://twitter.com/RIORAO/status/923599110262874112

kodukikoduki

そもそもFloat/Dobuleの誤差は浮動小数由来というよりは2進数由来だよね、という話。
自分もごっちゃになっていたが、COBOLの一般的な数値フォーマットである10進固定少数とFloatやDoubleといった2進浮動小数を比較するから話がおかしくなる。

0.2を2進にすると0.0011001100110011…となり無理数となる。ゆえに10進数とは違うところで丸目誤差が発生するため最終的に10進数にして出力したときに予期せぬ値になる。
これはどっちかというと、浮動小数というより2進数の問題の気がするので2進固定少数を作っても発生するんじゃないだろうか?

つまり10進浮動小数点を使えば2進数に比べて非効率でも割と良いとこどりが出来そうな予感

kodukikoduki

そもそもなんで固定少数ではなく浮動小数にしたいかというと、効率的に桁数の多い少数を表現したいから。

0.001だと、整数が1桁で少数が3桁で合計4桁。使うわけだけど、その場合の取り合える範囲が9.000 - 9.999になってしまう。つまり10.999はもちろん10.99も表現できない。5桁のデータとして確保すればいいのだけど言うまでもなく鼬ごっこ。これは不便。
特に、円周率を例に出すまでもなく普通に演算してたら無理数は多く出てくるため、妥当な丸目どころを事前に定義するのは結構難しい。
その点、浮動小数なら取りえる最大の桁数で取れるので精度を保ちつつ(=3とか3.14とか固定の誤差の大きい所でうちきらず)データを効率的に扱える。

多くの科学計算とかにはこちらが便利。あと、現代ではFPUがいっぱい載ってるから速い。Decimal系よりずっと速い。

つまり、固定少数ではなく浮動小数を使うのは少数で柔軟性の高い桁数を効率的に確保するため。

kodukikoduki

科学計算は大きな値を取り扱うことが多いから2進数の誤差が問題にならないことが多いので好まれる。
ただし、金融をはじめとした現実の社会的な数値を扱う事務計算だと10進数演算が必要。
ネイティブというかプリミティブというか第一級市民な言語はCOBOLを古めのものでいくつかしかない。たいていの言語にC#やRubyやPython、JavaやScalaにもBigDecimalはあるのだけど普通のリテラルで表現される少数はあくまでも浮動小数。

現代はマシンパワーが余ってそうだし、第一級市民の少数を別に2進浮動小数にこだわらなくても? が疑問の発端。必要に応じてDobuleとかfloatを選ぶ形でも良い。COBOLみたく。

kodukikoduki

よく考えたら精度は浮動小数の問題じゃなくて2進数の少数の問題の気がするので、10進浮動小数があれば最強では? と思いいたる。
というわけで探してみたらふつうにあった。
https://ja.wikipedia.org/wiki/IEEE_754#基本形式

decimal32, decimal64, decimal128.

このあたりでJavaとかのBigDecimalも実は10進固定小数じゃなくて10進浮動小数なのか? とちょっと思い始める。そんな気がするけど要確認。

10進浮動小数メモ

https://news.mynavi.jp/article/architecture-104/
https://twitter.com/ohtsuka/status/1380059453924339714
https://qiita.com/ma2shita/items/d8aeec9e2921dc3536f4
https://teratail.com/questions/172535

kodukikoduki

今のところ、10進浮動小数をメインで使う言語が少ない理由に明確な答えは出せてないけど、単純に遅いからはあると思う。
FPUの支援無いし。

固定少数と浮動小数という比較だと固定少数のが計算が簡単だから速いはずだけど、2進数と10進数の比較なら普通に考えて2進数のが速そうだし、なにより長い歴史の中でハードウェアアクセラレータとしてFPUがもりもり入ってるので圧倒的にBigDecimalのライブラリよりDoubleとかのが速い(たしかそのせいでJavaも桁数が少なければ内部でDoubleを使ってるはず)。

シリコンが余ってきたと言ってもたいていはAI支援とかカメラ支援に使われるから10進演算支援はPowerくらいしかない気がする。
https://news.mynavi.jp/article/architecture-105/

ちなみにBCDじゃなくてDPDというより効率的なデータ表現で10進を表してるらしい。比較的素朴なHW実装ながらJavaの実アプリで7倍性能が上がった模様。すごい!

kodukikoduki

10進浮動小数の時にも精度問題があるのかは謎。要確認。
もちろん1/3とかしたら無理数だから丸めるのだけど、どこで切り捨てなりをするのか。四捨五入だと1.000...になるしな。。。 10進浮動小数で0.1 == 0.1 や 1.3 - 0.3 == 0 が成り立つのか否か。