少数を100倍とか10000倍とかして整数で扱う手法です。
浮動小数 3.14 ⇒ 固定少数 314
今のゲーム開発では使わなくなったんじゃないでしょうか。
正確には「固定少数点数」ですが、長いので「固定少数」と呼ぶことにします。
メリット
1.昔のハードウェアでは float や double より int の方が計算が速かった。
2.float や double 特有の誤差が出ない。
デメリット
1.int を float や double に変換するのが面倒。
2.速度の問題なら、今のハードウェアでは float か double で良い。
3.誤差の問題なら、誤差が出ない実数型を使えば良い。
限られた状況では、選択肢のひとつになります。
例)
1.float や double の計算が遅いハードウェア向けの環境で開発している。
2.そもそも、float や double が使えない環境。
3.ハードウェア性能は十分だが、誤差が出ない実数型の計算が遅いため、処理速度が重要な状況で使えない。
こういった状況は今ではかなり特殊なので、これから(もう既に?)ロストテクノロジーになると思っています。
boost::multiprecision の多倍長浮動小数点数を使う。
decimal 型を使う。
float や double は「浮動」小数点数と呼びます。
値によって小数点の位置が変わる(小数点以下第何位までを扱うかが変わる)ためです。
これに対して固定少数点数は、小数点の位置をあらかじめ決めておき、位置は変えません。
変えても良いですが、それはもう「固定」ではなく、float や double と実装方法が違う浮動小数点数になります。
C++
#include <cstddef> #include <cstdint> #include <iostream> class Fraction { // 小数点以下第何位までを扱うか? 100 なら2位まで。「ゲタを履かせる」という。 static const std::size_t DENOMINATOR{ 10000 }; // ゲタを履かせた値 std::int_fast64_t value{ 0 }; public: explicit Fraction(const float source_value) { value = static_cast<std::int_fast64_t>(source_value * DENOMINATOR); } /* デフォルトコンストラクタなどは割愛 */ // float に変換して返す。 float ToFloat() const { return static_cast<float>(value) / static_cast<float>(DENOMINATOR); } explicit operator float() const { return ToFloat(); } // += 演算子のオーバーライド Fraction& operator+=(const Fraction& x) { value += x.value; return *this; } /* 以下略… */ }; using namespace std; int main() { Fraction f1(3.14f); Fraction f2(0.01f); f1 += f2; cout << static_cast<float>(f1) << endl; } // 実行結果 3.15
使い勝手を良くするには、全てのオペレーターをオーバーライドする必要が出て来るので、かなり面倒です。
コードを書くよりも、テストに時間がかかります。
boost::multiprecision や decimal を使うよりも処理が速くなるなら、作る意味はあるかも知れません。
必要になる状況はかなり限定的で、期待できる効果も微々たるものです。
高速化が必要なら、もっと影響が大きい部分を優先して高速化するべきで、他に選択肢がないときに「仕方なく自作する」程度のものです。
固定少数のままなら誤差は出ませんが、float や double にキャストするときに誤差が出ます。
必ず出るわけではないですが、float や double は、誤差が出る前提で扱うべきものです。
文字列に変換するのが目的なら、float や double への変換を行わず、固定少数のまま文字列に変換するべきです。
↑のサンプルコードの Fraction クラスに ToChar() とか、ToString() を実装すると良いです。
↑のサンプルコードでは、100や10000など、10の指数を使って「ゲタを履かせて」います。
人間にとって計算しやすいからです。
float が遅かったころは、10の指数の乗算・除算も遅く、コンパイラの最適化もまだまだ微妙だったので、ビットシフト(シフト演算)を使っていました。
ビットシフトなので、2の指数(2 4 8 16 32…)を使って「ゲタを履かせる」ことになります。
コンピューターにとって計算は速いですが、人間にとっては慣れないと難しいです(2や4など1桁ならともかく…)。
なので、慣れないとテストやデバッグに時間がかかります。
今は余程のことがなければ、乗算・除算をビットシフトで代用することはないと思います(コンパイラがいい感じに最適化してくれるはず)。
float と double は誤差が出るので、誤差を許さない数値(ガチャ確率や UI に表示する装備のパラメータなど)で使うのは間違った実装です。
間違った実装をしているシステムをいろんなところで見ています。
何故そうなるのかは謎です。