第2章:課題の実装

はじめに

本章では、CPP Module 02の各演習を実装します。

---

1. ex00: My First Class in Orthodox Canonical Form

1.1 課題の要件

  • Orthodox Canonical Formに従ったFixedクラスを作成
  • privateメンバ: _rawBits(int)と_fractionalBits(static const int = 8)
  • デフォルトコンストラクタ、コピーコンストラクタ、代入演算子、デストラクタ
  • getRawBitssetRawBitsメンバ関数
  • 1.2 実装

    // Fixed.hpp
    #ifndef FIXED_HPP
    #define FIXED_HPP
    
    #include <iostream>
    
    class Fixed {
    private:
        int                 _rawBits;
        static const int    _fractionalBits = 8;
    
    public:
        Fixed();
        Fixed(const Fixed& other);
        Fixed& operator=(const Fixed& other);
        ~Fixed();
    
        int     getRawBits() const;
        void    setRawBits(int const raw);
    };
    
    #endif
    
    // Fixed.cpp
    #include "Fixed.hpp"
    
    Fixed::Fixed() : _rawBits(0) {
        std::cout << "Default constructor called" << std::endl;
    }
    
    Fixed::Fixed(const Fixed& other) {
        std::cout << "Copy constructor called" << std::endl;
        *this = other;
    }
    
    Fixed& Fixed::operator=(const Fixed& other) {
        std::cout << "Copy assignment operator called" << std::endl;
        if (this != &other) {
            this->_rawBits = other.getRawBits();
        }
        return *this;
    }
    
    Fixed::~Fixed() {
        std::cout << "Destructor called" << std::endl;
    }
    
    int Fixed::getRawBits() const {
        std::cout << "getRawBits member function called" << std::endl;
        return this->_rawBits;
    }
    
    void Fixed::setRawBits(int const raw) {
        this->_rawBits = raw;
    }
    
    // main.cpp
    #include "Fixed.hpp"
    
    int main() {
        Fixed a;
        Fixed b(a);
        Fixed c;
    
        c = b;
    
        std::cout << a.getRawBits() << std::endl;
        std::cout << b.getRawBits() << std::endl;
        std::cout << c.getRawBits() << std::endl;
    
        return 0;
    }
    

    1.3 期待される出力

    Default constructor called
    Copy constructor called
    Copy assignment operator called
    getRawBits member function called
    Default constructor called
    Copy assignment operator called
    getRawBits member function called
    getRawBits member function called
    0
    getRawBits member function called
    0
    getRawBits member function called
    0
    Destructor called
    Destructor called
    Destructor called
    

    ---

    2. ex01: Towards a more useful fixed-point number class

    2.1 課題の要件

  • ex00のクラスを拡張
  • int/floatコンストラクタを追加
  • toFloattoIntメンバ関数を追加
  • <<演算子のオーバーロード

2.2 実装

// Fixed.hpp
#ifndef FIXED_HPP
#define FIXED_HPP

#include <iostream>
#include <cmath>

class Fixed {
private:
    int                 _rawBits;
    static const int    _fractionalBits = 8;

public:
    Fixed();
    Fixed(const int n);
    Fixed(const float n);
    Fixed(const Fixed& other);
    Fixed& operator=(const Fixed& other);
    ~Fixed();

    int     getRawBits() const;
    void    setRawBits(int const raw);
    float   toFloat() const;
    int     toInt() const;
};

std::ostream& operator<<(std::ostream& os, const Fixed& fixed);

#endif

// Fixed.cpp
#include "Fixed.hpp"

Fixed::Fixed() : _rawBits(0) {
    std::cout << "Default constructor called" << std::endl;
}

Fixed::Fixed(const int n) : _rawBits(n << _fractionalBits) {
    std::cout << "Int constructor called" << std::endl;
}

Fixed::Fixed(const float n) : _rawBits(roundf(n * (1 << _fractionalBits))) {
    std::cout << "Float constructor called" << std::endl;
}

Fixed::Fixed(const Fixed& other) {
    std::cout << "Copy constructor called" << std::endl;
    *this = other;
}

Fixed& Fixed::operator=(const Fixed& other) {
    std::cout << "Copy assignment operator called" << std::endl;
    if (this != &other) {
        this->_rawBits = other.getRawBits();
    }
    return *this;
}

Fixed::~Fixed() {
    std::cout << "Destructor called" << std::endl;
}

int Fixed::getRawBits() const {
    return this->_rawBits;
}

void Fixed::setRawBits(int const raw) {
    this->_rawBits = raw;
}

float Fixed::toFloat() const {
    return static_cast<float>(this->_rawBits) / (1 << _fractionalBits);
}

int Fixed::toInt() const {
    return this->_rawBits >> _fractionalBits;
}

std::ostream& operator<<(std::ostream& os, const Fixed& fixed) {
    os << fixed.toFloat();
    return os;
}

// main.cpp
#include "Fixed.hpp"

int main() {
    Fixed a;
    Fixed const b(10);
    Fixed const c(42.42f);
    Fixed const d(b);

    a = Fixed(1234.4321f);

    std::cout << "a is " << a << std::endl;
    std::cout << "b is " << b << std::endl;
    std::cout << "c is " << c << std::endl;
    std::cout << "d is " << d << std::endl;

    std::cout << "a is " << a.toInt() << " as integer" << std::endl;
    std::cout << "b is " << b.toInt() << " as integer" << std::endl;
    std::cout << "c is " << c.toInt() << " as integer" << std::endl;
    std::cout << "d is " << d.toInt() << " as integer" << std::endl;

    return 0;
}

2.3 固定小数点変換の詳細

整数から固定小数点数:

整数 10 を固定小数点数に変換:
10 << 8 = 10 * 256 = 2560
内部表現: 2560 = 0x0A00

浮動小数点数から固定小数点数:

42.42 を固定小数点数に変換:
42.42 * 256 = 10859.52
roundf(10859.52) = 10860
内部表現: 10860 = 0x2A6C

固定小数点数から浮動小数点数:

内部表現 10860 を浮動小数点数に変換:
10860 / 256.0 = 42.421875
(元の42.42との誤差は精度による)

2.4 期待される出力

Default constructor called
Int constructor called
Float constructor called
Copy constructor called
Copy assignment operator called
Float constructor called
Copy assignment operator called
Destructor called
a is 1234.43
b is 10
c is 42.4219
d is 10
a is 1234 as integer
b is 10 as integer
c is 42 as integer
d is 10 as integer
Destructor called
Destructor called
Destructor called
Destructor called

---

3. ex02: Now we're talking

3.1 課題の要件

  • 6つの比較演算子: >, <, >=, <=, ==, !=
  • 4つの算術演算子: +, -, *, /
  • 4つのインクリメント/デクリメント演算子: 前置/後置 ++, --
  • 静的メンバ関数: min, max

3.2 実装

// Fixed.hpp
#ifndef FIXED_HPP
#define FIXED_HPP

#include <iostream>
#include <cmath>

class Fixed {
private:
    int                 _rawBits;
    static const int    _fractionalBits = 8;

public:
    // Orthodox Canonical Form
    Fixed();
    Fixed(const int n);
    Fixed(const float n);
    Fixed(const Fixed& other);
    Fixed& operator=(const Fixed& other);
    ~Fixed();

    // Getter/Setter
    int     getRawBits() const;
    void    setRawBits(int const raw);

    // Conversion
    float   toFloat() const;
    int     toInt() const;

    // Comparison operators
    bool operator>(const Fixed& rhs) const;
    bool operator<(const Fixed& rhs) const;
    bool operator>=(const Fixed& rhs) const;
    bool operator<=(const Fixed& rhs) const;
    bool operator==(const Fixed& rhs) const;
    bool operator!=(const Fixed& rhs) const;

    // Arithmetic operators
    Fixed operator+(const Fixed& rhs) const;
    Fixed operator-(const Fixed& rhs) const;
    Fixed operator*(const Fixed& rhs) const;
    Fixed operator/(const Fixed& rhs) const;

    // Increment/Decrement operators
    Fixed& operator++();    // pre-increment
    Fixed operator++(int);  // post-increment
    Fixed& operator--();    // pre-decrement
    Fixed operator--(int);  // post-decrement

    // Static member functions
    static Fixed& min(Fixed& a, Fixed& b);
    static const Fixed& min(const Fixed& a, const Fixed& b);
    static Fixed& max(Fixed& a, Fixed& b);
    static const Fixed& max(const Fixed& a, const Fixed& b);
};

std::ostream& operator<<(std::ostream& os, const Fixed& fixed);

#endif

// Fixed.cpp
#include "Fixed.hpp"

// ===== Orthodox Canonical Form =====

Fixed::Fixed() : _rawBits(0) {}

Fixed::Fixed(const int n) : _rawBits(n << _fractionalBits) {}

Fixed::Fixed(const float n) : _rawBits(roundf(n * (1 << _fractionalBits))) {}

Fixed::Fixed(const Fixed& other) : _rawBits(other._rawBits) {}

Fixed& Fixed::operator=(const Fixed& other) {
    if (this != &other) {
        this->_rawBits = other._rawBits;
    }
    return *this;
}

Fixed::~Fixed() {}

// ===== Getter/Setter =====

int Fixed::getRawBits() const {
    return this->_rawBits;
}

void Fixed::setRawBits(int const raw) {
    this->_rawBits = raw;
}

// ===== Conversion =====

float Fixed::toFloat() const {
    return static_cast<float>(this->_rawBits) / (1 << _fractionalBits);
}

int Fixed::toInt() const {
    return this->_rawBits >> _fractionalBits;
}

// ===== Comparison operators =====

bool Fixed::operator>(const Fixed& rhs) const {
    return this->_rawBits > rhs._rawBits;
}

bool Fixed::operator<(const Fixed& rhs) const {
    return this->_rawBits < rhs._rawBits;
}

bool Fixed::operator>=(const Fixed& rhs) const {
    return this->_rawBits >= rhs._rawBits;
}

bool Fixed::operator<=(const Fixed& rhs) const {
    return this->_rawBits <= rhs._rawBits;
}

bool Fixed::operator==(const Fixed& rhs) const {
    return this->_rawBits == rhs._rawBits;
}

bool Fixed::operator!=(const Fixed& rhs) const {
    return this->_rawBits != rhs._rawBits;
}

// ===== Arithmetic operators =====

Fixed Fixed::operator+(const Fixed& rhs) const {
    Fixed result;
    result.setRawBits(this->_rawBits + rhs._rawBits);
    return result;
}

Fixed Fixed::operator-(const Fixed& rhs) const {
    Fixed result;
    result.setRawBits(this->_rawBits - rhs._rawBits);
    return result;
}

Fixed Fixed::operator*(const Fixed& rhs) const {
    Fixed result;
    // 乗算後、小数ビット分をシフト
    result.setRawBits((this->_rawBits * rhs._rawBits) >> _fractionalBits);
    return result;
}

Fixed Fixed::operator/(const Fixed& rhs) const {
    Fixed result;
    // 除算前に小数ビット分をシフト
    result.setRawBits((this->_rawBits << _fractionalBits) / rhs._rawBits);
    return result;
}

// ===== Increment/Decrement operators =====

Fixed& Fixed::operator++() {
    this->_rawBits++;
    return *this;
}

Fixed Fixed::operator++(int) {
    Fixed temp(*this);
    ++(*this);
    return temp;
}

Fixed& Fixed::operator--() {
    this->_rawBits--;
    return *this;
}

Fixed Fixed::operator--(int) {
    Fixed temp(*this);
    --(*this);
    return temp;
}

// ===== Static member functions =====

Fixed& Fixed::min(Fixed& a, Fixed& b) {
    return (a < b) ? a : b;
}

const Fixed& Fixed::min(const Fixed& a, const Fixed& b) {
    return (a < b) ? a : b;
}

Fixed& Fixed::max(Fixed& a, Fixed& b) {
    return (a > b) ? a : b;
}

const Fixed& Fixed::max(const Fixed& a, const Fixed& b) {
    return (a > b) ? a : b;
}

// ===== Stream operator =====

std::ostream& operator<<(std::ostream& os, const Fixed& fixed) {
    os << fixed.toFloat();
    return os;
}

// main.cpp
#include "Fixed.hpp"

int main() {
    Fixed a;
    Fixed const b(Fixed(5.05f) * Fixed(2));

    std::cout << a << std::endl;
    std::cout << ++a << std::endl;
    std::cout << a << std::endl;
    std::cout << a++ << std::endl;
    std::cout << a << std::endl;

    std::cout << b << std::endl;

    std::cout << Fixed::max(a, b) << std::endl;

    return 0;
}

3.3 演算の詳細

乗算の仕組み:

a = 5.05 (内部: 1293)
b = 2 (内部: 512)

1293 * 512 = 662016
662016 >> 8 = 2586
2586 / 256.0 = 10.1016 ≈ 10.1

インクリメントの仕組み:

++操作は_rawBitsを1増加
1 / 256 = 0.00390625
これが固定小数点数の最小単位

3.4 期待される出力

0
0.00390625
0.00390625
0.00390625
0.0078125
10.1016
10.1016

---

4. ex03: BSP(Bonus)

4.1 課題の要件

  • Pointクラスを作成
  • bsp関数で点が三角形内にあるかを判定

4.2 実装

// Point.hpp
#ifndef POINT_HPP
#define POINT_HPP

#include "Fixed.hpp"

class Point {
private:
    Fixed const _x;
    Fixed const _y;

public:
    Point();
    Point(const float x, const float y);
    Point(const Point& other);
    Point& operator=(const Point& other);
    ~Point();

    Fixed getX() const;
    Fixed getY() const;
};

bool bsp(Point const a, Point const b, Point const c, Point const point);

#endif

// Point.cpp
#include "Point.hpp"

Point::Point() : _x(0), _y(0) {}

Point::Point(const float x, const float y) : _x(x), _y(y) {}

Point::Point(const Point& other) : _x(other._x), _y(other._y) {}

Point& Point::operator=(const Point& other) {
    // constメンバのため代入不可
    // 何もしない
    (void)other;
    return *this;
}

Point::~Point() {}

Fixed Point::getX() const {
    return this->_x;
}

Fixed Point::getY() const {
    return this->_y;
}

// bsp.cpp
#include "Point.hpp"

// 外積の符号を計算
static Fixed sign(Point const& p1, Point const& p2, Point const& p3) {
    return (p1.getX() - p3.getX()) * (p2.getY() - p3.getY())
         - (p2.getX() - p3.getX()) * (p1.getY() - p3.getY());
}

bool bsp(Point const a, Point const b, Point const c, Point const point) {
    Fixed d1 = sign(point, a, b);
    Fixed d2 = sign(point, b, c);
    Fixed d3 = sign(point, c, a);

    bool has_neg = (d1 < Fixed(0)) || (d2 < Fixed(0)) || (d3 < Fixed(0));
    bool has_pos = (d1 > Fixed(0)) || (d2 > Fixed(0)) || (d3 > Fixed(0));

    // 点が辺上にある場合は除外(d1, d2, d3のいずれかが0)
    if (d1 == Fixed(0) || d2 == Fixed(0) || d3 == Fixed(0)) {
        return false;
    }

    return !(has_neg && has_pos);
}

// main.cpp
#include "Point.hpp"
#include <iostream>

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

    // テストケース
    Point inside(5.0f, 5.0f);       // 内部
    Point outside(0.0f, 5.0f);      // 外部
    Point on_edge(5.0f, 0.0f);      // 辺上
    Point on_vertex(0.0f, 0.0f);    // 頂点上

    std::cout << "Inside: " << (bsp(a, b, c, inside) ? "true" : "false") << std::endl;
    std::cout << "Outside: " << (bsp(a, b, c, outside) ? "true" : "false") << std::endl;
    std::cout << "On edge: " << (bsp(a, b, c, on_edge) ? "true" : "false") << std::endl;
    std::cout << "On vertex: " << (bsp(a, b, c, on_vertex) ? "true" : "false") << std::endl;

    return 0;
}

4.3 アルゴリズムの解説

外積を使った判定:

三角形ABCを反時計回りに辿ると:

  • 点Pが内部にある場合、AB×AP、BC×BP、CA×CPはすべて同じ符号
  • 点Pが外部にある場合、符号が混在
  • 三角形 ABC:
    A(0,0), B(10,0), C(5,10)
    
    点P(5,5)について:
    AB = (10,0), AP = (5,5)
    AB × AP = 10*5 - 0*5 = 50 > 0
    
    BC = (-5,10), BP = (-5,5)
    BC × BP = -5*5 - 10*(-5) = -25 + 50 = 25 > 0
    
    CA = (-5,-10), CP = (0,-5)
    CA × CP = -5*(-5) - (-10)*0 = 25 > 0
    
    すべて正 → 点Pは三角形内
    

    4.4 期待される出力

    Inside: true
    Outside: false
    On edge: false
    On vertex: false
    

    ---

    まとめ

    本章で実装した内容:

  • ex00: Orthodox Canonical Formの基本実装
  • ex01: 固定小数点数の変換機能
  • ex02: 演算子オーバーロードの完全実装
  • ex03: 幾何学的アルゴリズム(BSP)