✍️

C++ MODULE : Fixedクラスの実装 [CPP02-42]

2024/04/21に公開

この問題の意図は、単に固定小数点数の値を0で初期化する基本的な機能から、より実用的で汎用性のある固定小数点数クラスの構築へと進むことです。具体的には、固定小数点数を扱うクラスを拡張し、整数や浮動小数点数から固定小数点数への変換、またその逆の変換を可能にすることが目的です。これにより、クラスは実際の数値計算においてより有用なものとなります。

固定小数点数の利点と用途

固定小数点数は、浮動小数点数に比べていくつかの利点があります。主な利点は、パフォーマンスと予測可能性です:

パフォーマンス: 固定小数点演算は、多くのプラットフォームで浮動小数点演算よりも高速です。特に、浮動小数点ユニットを搭載していないマイクロコントローラーや埋め込みシステムでは、固定小数点演算が効率的です。
予測可能性: 固定小数点数は、その表現が固定されているため、計算の結果が常に同じになるという予測可能性があります。これは金融計算やリアルタイムシステムで非常に重要です。
用途については、固定小数点数は次のような場面でよく使用されます:

オーディオ処理: オーディオデータはしばしば固定小数点形式で処理されます。
ビデオゲーム開発: リソースが限られている環境やリアルタイムの要求が厳しいビデオゲームコンソールでよく使用されます。
埋め込みシステム: リアルタイム制御や信号処理など、計算時間とリソースが限られている環境で使用されます。

オーバーロードされた演算子の重要性

クラスにオーバーロードされた演算子を定義することで、そのクラスのオブジェクトを標準のデータ型のように扱うことができるようになります。これにより、コードの可読性と保守性が向上します。たとえば、固定小数点数クラスで算術演算子をオーバーロードすると、以下のような直感的な演算が可能になります:

Fixed a(1.5f);
Fixed b(2.5f);
Fixed c = a + b;  // オーバーロードされた+演算子を使用
std::cout << "c = " << c << std::endl;  // オーバーロードされた<<演算子を使用

このように、演算子のオーバーロードを適切に使用することで、特定のデータ型に対して自然な操作を定義し、プログラム全体の統一感を保つことができます。

Fixedクラスのデザイン

Fixedクラスは、そのインターフェース(メソッドや演算子)を通じて、固定小数点数の操作を抽象化しています。この抽象化により、クラスの使用者は内部的な詳細(例えば、数値がどのように格納されているか)を意識することなく、固定小数点数を扱うことができます。また、minやmaxのような静的メンバ関数は、固定小数点数間での比較を簡単に行えるようにしており、これらはクラスのユーティリティ関数としての役割を果たします。

コンストラクタの追加

整数から固定小数点への変換コンストラクタ:
このコンストラクタは整数を受け取り、それを内部的な固定小数点数表現に変換します。この機能は、プログラム内で整数値を固定小数点数として扱いたい場面で重要です。特に、整数から始まる計算が後に小数を含むようになる場合などに有用です。
浮動小数点数から固定小数点への変換コンストラクタ:
浮動小数点数を固定小数点数表現に変換することで、より精密な計算が求められる科学技術計算や、リソースが限られた環境でのアプリケーションにおいて有効に機能します。これにより、浮動小数点数からの直接変換が可能となり、柔軟性が向上します。

メンバ関数の追加

toFloat:
固定小数点数を浮動小数点数に変換します。この関数は、内部的に保持されている数値を、より一般的に使われる浮動小数点形式で取り扱いたい場合に必要です。例えば、グラフィック表示や精密な数値分析に用いる場合などが考えられます。
toInt:
固定小数点数を整数に変換します。これは、固定小数点数を整数として扱う必要がある場合(例えば、ユーザーへの表示や、整数のみを受け付ける外部システムへの入力として)に役立ちます。

演算子のオーバーロード

出力ストリーム演算子(<<)のオーバーロード:
固定小数点数を浮動小数点数として出力ストリームに挿入することで、デバッグやユーザーへの情報提供が容易になります。これにより、クラスのインスタンスを直接 std::cout などで出力できるようになり、クラスの使用がより直感的になります。

クラス定義とメンバ

class Fixed は固定小数点数を扱うクラスです。
_value はプライベート変数で、固定小数点数の内部表現(整数として)を保持します。
_frac は小数部のビット数を表す静的な定数です。この値は固定小数点の精度を決定します。

コンストラクタとデストラクタ

Fixed() はデフォルトコンストラクタで、固定小数点数をデフォルト値(通常はゼロ)で初期化します。
Fixed(const int value) と Fixed(const float value) は、整数や浮動小数点数から固定小数点数を初期化するためのコンストラクタです。
~Fixed() はデストラクタで、オブジェクトのライフサイクルが終了する際に呼ばれます。
Fixed(Fixed const &copy) はコピーコンストラクタで、既存のオブジェクトをコピーして新しいオブジェクトを作成します。

演算子のオーバーロード

算術演算子 (+, -, *, /) と比較演算子 (==, !=, <=, >=, <, >) をオーバーロードしています。これにより、Fixed オブジェクト間でこれらの演算が直感的に行えるようになります。
インクリメント (++) とデクリメント (--) 演算子もオーバーロードされており、固定小数点数の値を増減させることができます。

最小値と最大値の関数

min と max 関数は、二つの Fixed オブジェクトを比較し、最小値または最大値を返します。これらは静的メンバ関数として定義されているため、クラスのインスタンスが存在しなくても使用できます。

ゲッターとセッター

getRawBits() と setRawBits(int const raw) は、固定小数点数の内部整数値を取得・設定するための関数です。

型変換関数

toFloat() と toInt() は、固定小数点数をそれぞれ浮動小数点数と整数に変換します。

出力ストリームへの挿入

operator<< は、Fixed オブジェクトを標準出力やその他の出力ストリームに簡単に出力できるようにするためにオーバーロードされています。

fixed と bool の違いについて

Fixed クラスは数値を表現し、特定の精度で算術演算を行うためのクラスです。
bool はブール型で、真 (true) または偽 (false) のいずれかの値を取ります。bool 型は条件判定や制御フローの管理に使用されます。

Q. 固定小数点の表現と計算により若干の誤差が生じるのはなぜか?

A. 固定小数点数で計算時に若干の誤差が生じる主要な理由は、数値が保持できる精度が限られているためです。固定小数点数は、通常、数値を整数部と小数部に分けて表現しますが、小数部のビット数が固定されているため、表現できる小数の精度には上限があります。これを理解するために、以下の点を詳しく見ていきましょう。

固定小数点の表現

固定小数点数は、全体のビット数のうち特定の数のビットを小数点以下の値の表現に割り当てます。例えば、32ビットの固定小数点数で8ビットを小数部に使う場合、残りの24ビットは整数部の表現に使用されます。この割り当て方が、数値の範囲と精度を決定します。

精度の制限

小数部のビット数が固定されているため、特定の精度以上の値を表現することができません。これは、小数点以下の値が、使用可能なビット数によって「ステップ」または「刻み」のサイズに限定されるためです。例えば、小数部に8ビットを使用する場合、表現可能な最小の変化は
1/2^8
=0.00390625
​です。この「刻み」より細かい値は表現できません。

計算時の丸め誤差

固定小数点数を使った計算では、加算、減算、乗算、除算の各操作がこの精度制限の影響を受けます。特に乗算と除算は、結果の小数部がオーバーフローすることがあり、正確な値を保持するために丸め処理が必要になる場合があります。この丸め処理によって、さらに誤差が発生する可能性があります。

プログラミングの実装

固定小数点の計算を実装する際には、これらの制約を考慮する必要があります。具体的には、計算の各ステップで適切にスケーリングと丸めを行うことで、誤差の影響を最小限に抑える工夫が求められます。

まとめ

固定小数点数の表現と計算における誤差は、その表現方式の本質的な特性です。固定小数点数を使用する利点は、計算の高速化とリソースの節約にありますが、精度の制限と計算誤差はトレードオフの関係にあります。精度がより重要なアプリケーションでは、浮動小数点数を使用するか、より多くのビットを小数部に割り当てることで対応する必要があります。

このコードは、固定小数点数を扱うFixedクラスの完全な実装を示しています。固定小数点数は、特定の小数精度で数値を扱う際に有用で、計算の予測可能性と効率を高めるために使用されます。ここではクラスの主な機能とその目的を詳細に説明します。

ft_pow 関数
cpp
Copy code
static float ft_pow(float base, int exp)
{
float result;
if (!exp)
return (1);
if (exp < 0)
{
base = 1 / base;
exp *= -1;
}
result = base;
while (--exp)
result *= base;
return (result);
}
この関数は、基数baseを指数expで累乗するカスタム実装です。指数が負の場合、基数をその逆数にして指数を正にします。この関数は、固定小数点数の内部表現を計算するために使用され、特にビットシフトの代わりに使われます。

Discussion