CPP02 1.0
|
EX00では、オブジェクトの基本的な形( Orthodx Canonical Form )でクラス設計(Fixed)の基礎を学びます。
EX01では、Fixedクラスに整数や浮動小数点の値を扱うコンストラクタを追加していきます。さらに、 固定小数点 として扱えるようにして、人間が理解しやすい形に出力できる様にします。(演算子のオーバーロード)
EX02では、Fixedオブジェクト同士の演算を実装します。具体的には演算子のオーバーロード( 比較 :>
, <
, >=
, <=
, ==
, !=
, 四則 :+
, -
, *
, /
, インクリメント・デクリメント :++
, --
)の設計。
EX03では、 BSP (Binary Space Partitioning)という幾何学的アルゴリズム(点が三角形の内側にあるかどうか判定する関数bsp
)を実装します。判定するためにベクトル演算(クロス積??)の概念を利用する。
まとめると、Fixedクラスを基本的なオブジェクトの構造から始まり、具体的な数値の扱い、他のオブジェクトの関係性、実用的な問題解決への応用に繋がっていきます。Fixedクラスが進化していく過程を体験します。
Doxygenで作成されたソースコードドキュメントです。
クラスの連携図やソースコードの説明に関する情報がまとめられています。
浮動小数点の課題(速度、精度、決定性)を克服する手法として、固定小数点の仕組みが利用されることがあります。
ちなみに、Cub3d
の課題でもレンダリングの処理速度を改善するために固定小数点の仕組みを使うこともあります。(実際に実装しました)
また、C++の標準ライブラリには組み込み用固定小数点の型がないため実装する必要があります。
固定小数点の実装を通じて、固定小数点の利点や欠点を理解し、適切な数値型を選択する判断力を養えるだけでなく、クラス設計の演算子オーバーロードによる直感的な操作ができるオブジェクト設計スキルが得られます。
int型の数値をオブジェクトで扱う。
「標準的な形式」基本的なオブジェクトの形を学びます。
構成
- デフォルトコンストラクタ
- コピーコンストラクタ
- コピー代入演算子
- デストラクタ
オブジェクトのライフサイクルを管理する基盤を築く。
- オブジェクトの誕生→コピー→終了まで
* 固定小数点の概念、表現、計算、2進数、ビットシフト
- コンピューターで固定小数点で値を扱うと、整数・小数・負の値を2進すうで表現できる。
- 数値のビットパターンと、その半分の値(1/2)のビットパターンが同じ。
- 2進数小数点(バイナリポイント)の位置が違うだけ。
より実用的な固定小数点クラスへ改良する
オブジェクトに機能を追加していき、数値表現と操作するためのツールとして進化していく課題。
追加する機能
- 整数や浮動小数点をFixed型として扱う。
- 内部の固定小数点の表現を、人間が理解できる表現に戻す。(整数、浮動小数点など)
- 画面に出力する機能(<< の代入演算子を使って出力)
}int型のコンストラクター :
raw << fractionalBits
例:
raw = 10
の場合(0d)
10 << 8
= (0d)2560
(0b)
1010 << 8
= (0b)1010 0000 0000
Fixed::Fixed(const float raw) : fixedPointValue(static_cast<int>(roundf(raw * (1 << fractionalBits)))) {}float型のコンストラクター :
static_cast<int>(roundf(raw * (1 << fractionalBits)))
例:
raw = 42.42f
の場合
42.42f * (1 << 8)
=42.42f * 256f
=10859.52f
roundf(10859.52f)
=10860
(0d)
10860
== (0b)10 1010 0110 1100
roundf()
:浮動小数点の小数部を丸める。(四捨五入)
toInt()
, toFloat()
raw = 10
の場合:
fixPointValue(10 << 8)
-> (0b)1010 0000 0000
== (0d)2560
- **
toInt()
**
int toInt(void) constConverts the fixed-point value to an integer value (truncates the fractional part).Definition Fixed.cpp:101
toInt()
->2560 >> 8
-> (0b)1010
== (0d)10
- **
toFloat()
**
float Fixed::toFloat(void) const { return static_cast<float>(this->fixedPointValue) / (1 << fractionalBits); }float toFloat(void) constConverts the fixed-point value to a floating-point value.Definition Fixed.cpp:92
toFloat()
->2560.0f / 256.0f
=10.0f
を返します。分数
256.0f
は(1 << franctionalBits)
==1 << 8
==256
が暗黙的にfloat型にキャストされます。
<<
のオーバーロード
o << fixed.toFloat();return o;}basic_ostream< _CharT, _Traits > & operator<<(basic_ostream< _CharT, _Traits > &__os, const basic_string< _CharT, _Traits, _Alloc > &__str)ios_base & fixed(ios_base &__base)
- 出力ストリームの参照 :
std::ostream &o
- Fixedオブジェクトの定数参照 :
const Fixed &fixed
- 出力ストリームにオブジェクトを出力 :
o << fixed.toFloat();
>>
の命令が直接用意されており、コンパイラはその命令をそのまま利用することがあります。ただし、CPUによっては、算術右シフト命令がなく、複数の命令を組み合わせて同等の動作を実現することが必要となり、その方法が異なることがありました。 floor()
or ゼロ方向へ丸める truncate()
floor()
と同等)
floor()
関数と固定小数点数の動作イメージ 機能:小数部を切り捨てる。(その数よりも小さい整数値を返します。)
-> cppreference.com
* 負の数の例:
floor(42.42f) = 42.0f
fixedPointValue(42.42f) -> 10860 -> toInt() -> 42
floor(-5.2f) = -6.0f
fixedPointValue(-5.2f) -> -1331 -> toInt() -> -6
固定小数点数の最小分解能を考慮した数値比較
++// 固定小数点数の最小分解能を考慮した許容範囲void fixedEquals(float expected, float actual) {int k = 1;float epsilon = (1.0f / (1 << 8)) * k; // fractionalBits は 8 と仮定ASSERT_NEAR(expected, actual, epsilon);}分母0による除算の挙動
++void checkDivisionByZero(Fixed &a) {// std::cerr の出力をキャプチャするためのstringstreamstd::stringstream ss_cerr;// std::cerr の内容を元に戻す// エラーメッセージのチェックstd::string expected_error = "Division by zero is undefined. Returning maximum float.";ASSERT_NE(ss_cerr.str().find(expected_error), std::string::npos)// 返り値が最大 float 値に近いかのチェックfloat max_float = std::numeric_limits<float>::max();// NG// ASSERT_FLOAT_EQ(result.toFloat(), max_float);// より緩やかな比較が必要な場合は ASSERT_NEAR を使用// float epsilon = 1e-6; // 適宜調整// ASSERT_NEAR(result.toFloat(), max_float, epsilon);}ostream cerrbasic_streambuf< _CharT, _Traits > * rdbuf() const__string_type str() const__stringbuf_type * rdbuf() conststatic constexpr _Tp max() noexceptstatic const size_type npos
Files :
* コピー代入演算子の注意点:constのメンバーを持つクラスは、コピー代入演算子をカプセル化するのを避けるか、慎重に検討する方がいい。
![]()
placement new
を使うなどあるが、直感的な挙動とは異なる可能性がある。この演習ではコピー代入演算子を使う用途がないため、ダミー用の実装をしています。 (代入せず、*this自分自身を返すだけ)
直接メンバー変数を使って数値の取り出しと代入をしていましたが、setRawBits()
, getRawBits()
を使うことでより安全かつ可読性の高いコードができる。という意見がありました。
セッター関数およびコピー代入演算子に関する考え。
結論:前者のコードに落ち着きました。
例:
2560.0f
/256.0f
=10.0f
元の浮動小数点数は固定小数点の表記にするためにスケーリング係数を乗じた値が保持される。
スケーリング係数で割ることで、この逆の処理ができる。(スケーリング反転)
スケーリング係数:
1 << fractionalBits
==256
浮動小数点数にビットシフトをすると、符号・指数部・仮数部のスケール反転とは異なる操作となり、大きく異なる値になることがある。
基本的には、
x << n
は整数に2のn乗を掛ける操作にあたり、浮動小数点数の構造上適していません。
main
: 安定版
feature/ex00
: 各課題の機能開発を行う。
docs
: ここにpushすると、GitHub Actions workflowが動作してドキュメントが公開されます。
C++ Moduleのプロジェクト毎にDoxygenを使ったドキュメントをまとめています。
doxygen用のcssテーマは、Doxygen Awesomeを使用。