第3章:テストとデバッグ

はじめに

本章では、CPP Module 02のテストとデバッグ方法を解説します。

---

1. Orthodox Canonical Formのテスト

1.1 コンストラクタの呼び出し順序

void testOCF() {
    std::cout << "=== OCF Test ===" << std::endl;

    Fixed a;                    // Default constructor
    Fixed b(a);                 // Copy constructor
    Fixed c;                    // Default constructor
    c = a;                      // Copy assignment operator

    std::cout << "=== End OCF Test ===" << std::endl;
}
// デストラクタはスコープ終了時に呼ばれる

1.2 期待される出力パターン

=== OCF Test ===
Default constructor called
Copy constructor called
Default constructor called
Copy assignment operator called
=== End OCF Test ===
Destructor called
Destructor called
Destructor called

1.3 よくある間違い

コピーコンストラクタで代入演算子を呼ぶ問題:

// よくある実装
Fixed::Fixed(const Fixed& other) {
    *this = other;  // 代入演算子を呼ぶ
}

// この場合、出力は:
// Copy constructor called
// Copy assignment operator called

この実装自体は動作しますが、効率的ではありません。

---

2. 固定小数点数の精度テスト

2.1 変換精度の確認

void testPrecision() {
    std::cout << "=== Precision Test ===" << std::endl;

    // 整数変換
    Fixed i1(0);
    Fixed i2(42);
    Fixed i3(-42);

    std::cout << "int 0: " << i1 << std::endl;
    std::cout << "int 42: " << i2 << std::endl;
    std::cout << "int -42: " << i3 << std::endl;

    // 浮動小数点変換
    Fixed f1(0.0f);
    Fixed f2(42.42f);
    Fixed f3(-42.42f);
    Fixed f4(0.5f);
    Fixed f5(0.25f);

    std::cout << "float 0.0: " << f1 << std::endl;
    std::cout << "float 42.42: " << f2 << std::endl;
    std::cout << "float -42.42: " << f3 << std::endl;
    std::cout << "float 0.5: " << f4 << std::endl;
    std::cout << "float 0.25: " << f5 << std::endl;
}

2.2 精度の限界

8ビットの小数部では、最小単位は 1/256 = 0.00390625 です。

void testMinUnit() {
    Fixed a;
    Fixed b;

    a.setRawBits(1);  // 最小単位
    std::cout << "Minimum unit: " << a << std::endl;

    b.setRawBits(256);  // = 1.0
    std::cout << "256 raw bits: " << b << std::endl;
}

2.3 toInt()の丸め

void testToInt() {
    Fixed f1(42.9f);
    Fixed f2(42.1f);
    Fixed f3(-42.9f);

    std::cout << "42.9 -> " << f1.toInt() << std::endl;   // 42
    std::cout << "42.1 -> " << f2.toInt() << std::endl;   // 42
    std::cout << "-42.9 -> " << f3.toInt() << std::endl;  // -42
}

---

3. 演算子のテスト

3.1 比較演算子

void testComparison() {
    Fixed a(10);
    Fixed b(20);
    Fixed c(10);

    std::cout << "=== Comparison Test ===" << std::endl;
    std::cout << "a(10) > b(20): " << (a > b) << std::endl;   // 0
    std::cout << "a(10) < b(20): " << (a < b) << std::endl;   // 1
    std::cout << "a(10) >= c(10): " << (a >= c) << std::endl; // 1
    std::cout << "a(10) == c(10): " << (a == c) << std::endl; // 1
    std::cout << "a(10) != b(20): " << (a != b) << std::endl; // 1
}

3.2 算術演算子

void testArithmetic() {
    Fixed a(10);
    Fixed b(3);

    std::cout << "=== Arithmetic Test ===" << std::endl;
    std::cout << "10 + 3 = " << (a + b) << std::endl;   // 13
    std::cout << "10 - 3 = " << (a - b) << std::endl;   // 7
    std::cout << "10 * 3 = " << (a * b) << std::endl;   // 30
    std::cout << "10 / 3 = " << (a / b) << std::endl;   // 3.33...
}

3.3 インクリメント/デクリメント

void testIncrement() {
    Fixed a;
    std::cout << "=== Increment Test ===" << std::endl;

    std::cout << "Initial: " << a << std::endl;         // 0
    std::cout << "++a: " << ++a << std::endl;           // 0.00390625
    std::cout << "After ++a: " << a << std::endl;       // 0.00390625
    std::cout << "a++: " << a++ << std::endl;           // 0.00390625
    std::cout << "After a++: " << a << std::endl;       // 0.0078125
}

3.4 min/max

void testMinMax() {
    Fixed a(5.5f);
    Fixed b(10.5f);

    std::cout << "=== Min/Max Test ===" << std::endl;
    std::cout << "min(5.5, 10.5): " << Fixed::min(a, b) << std::endl;  // 5.5
    std::cout << "max(5.5, 10.5): " << Fixed::max(a, b) << std::endl;  // 10.5

    // const版のテスト
    Fixed const c(3.3f);
    Fixed const d(7.7f);
    std::cout << "min(const 3.3, const 7.7): " << Fixed::min(c, d) << std::endl;
}

---

4. BSPのテスト

4.1 基本テストケース

void testBSP() {
    // 三角形: (0,0), (10,0), (5,10)
    Point a(0.0f, 0.0f);
    Point b(10.0f, 0.0f);
    Point c(5.0f, 10.0f);

    std::cout << "=== BSP Test ===" << std::endl;

    // 明らかに内部
    Point p1(5.0f, 3.0f);
    std::cout << "(5, 3) inside: " << bsp(a, b, c, p1) << std::endl;  // true

    // 明らかに外部
    Point p2(0.0f, 10.0f);
    std::cout << "(0, 10) outside: " << bsp(a, b, c, p2) << std::endl;  // false

    // 辺上
    Point p3(5.0f, 0.0f);
    std::cout << "(5, 0) on edge: " << bsp(a, b, c, p3) << std::endl;  // false

    // 頂点上
    Point p4(0.0f, 0.0f);
    std::cout << "(0, 0) on vertex: " << bsp(a, b, c, p4) << std::endl;  // false
}

4.2 エッジケース

void testBSPEdgeCases() {
    std::cout << "=== BSP Edge Cases ===" << std::endl;

    // 非常に小さい三角形
    Point a1(0.0f, 0.0f);
    Point b1(0.1f, 0.0f);
    Point c1(0.05f, 0.1f);
    Point p1(0.05f, 0.05f);
    std::cout << "Tiny triangle: " << bsp(a1, b1, c1, p1) << std::endl;

    // 点が三角形の境界に非常に近い
    Point a2(0.0f, 0.0f);
    Point b2(10.0f, 0.0f);
    Point c2(5.0f, 10.0f);
    Point p2(0.01f, 0.01f);
    std::cout << "Near boundary: " << bsp(a2, b2, c2, p2) << std::endl;
}

4.3 視覚的なデバッグ

void visualDebug() {
    Point a(0.0f, 0.0f);
    Point b(10.0f, 0.0f);
    Point c(5.0f, 10.0f);

    // 10x10のグリッドでテスト
    std::cout << "Visual grid (. = outside, * = inside):" << std::endl;
    for (int y = 10; y >= 0; y--) {
        for (int x = 0; x <= 10; x++) {
            Point p(static_cast<float>(x), static_cast<float>(y));
            if (bsp(a, b, c, p))
                std::cout << "* ";
            else
                std::cout << ". ";
        }
        std::cout << std::endl;
    }
}

---

5. よくある間違いと対処法

5.1 乗算のオーバーフロー

// 問題: 大きな数の乗算でオーバーフロー
Fixed a(1000);
Fixed b(1000);
Fixed c = a * b;  // オーバーフローの可能性

// 内部計算:
// 1000 << 8 = 256000
// 256000 * 256000 = 65536000000 (intの範囲を超える)

対処法: long longを使用するか、値の範囲を制限する。

5.2 除算のゼロチェック

// 問題: ゼロ除算
Fixed a(10);
Fixed b(0);
Fixed c = a / b;  // 未定義動作

// 対処法: チェックを追加
Fixed Fixed::operator/(const Fixed& rhs) const {
    if (rhs.getRawBits() == 0) {
        std::cerr << "Error: Division by zero" << std::endl;
        return Fixed(0);
    }
    Fixed result;
    result.setRawBits((this->_rawBits << _fractionalBits) / rhs._rawBits);
    return result;
}

5.3 自己代入のチェック

// 自己代入のチェックを忘れると問題になる場合がある
Fixed& Fixed::operator=(const Fixed& other) {
    if (this != &other) {  // 重要!
        this->_rawBits = other._rawBits;
    }
    return *this;
}

5.4 constメンバの代入演算子

// Pointクラスでconstメンバがある場合
class Point {
    Fixed const _x;  // const!
    Fixed const _y;  // const!

    // 代入演算子は何もできない
    Point& operator=(const Point& other) {
        (void)other;
        return *this;
    }
};

---

6. 提出前チェックリスト

ex00

  • [ ] デフォルトコンストラクタがある
  • [ ] コピーコンストラクタがある
  • [ ] コピー代入演算子がある
  • [ ] デストラクタがある
  • [ ] 出力メッセージが正確
  • ex01

  • [ ] int/floatコンストラクタがある
  • [ ] toFloat()/toInt()が正しく動作
  • [ ] <<演算子がオーバーロードされている
  • [ ] roundf()を使用している
  • ex02

  • [ ] 6つの比較演算子がある
  • [ ] 4つの算術演算子がある
  • [ ] 前置/後置インクリメント/デクリメントがある
  • [ ] min/max静的関数がある(const版も)
  • ex03

  • [ ] Pointクラスがある
  • [ ] bsp関数が正しく動作
  • [ ] 辺上・頂点上の点はfalseを返す
  • ---

    まとめ

    CPP Module 02のテストでは以下を確認:

  • OCF: 4つの特殊メンバ関数の呼び出し順序
  • 精度: 固定小数点数の変換精度
  • 演算子: すべての演算子の動作
  • BSP: 幾何学的判定の正確性