【完全ガイド】C++で実装する6つの四捨五入テクニック – プロが教えるベストプラクティス

四捨五入の基礎知識と実装における注意点

数学的な四捨五入の定義と特殊なケース

四捨五入は一見単純な演算に見えますが、プログラミングで実装する際には様々な考慮点があります。まずは基本的な定義と特殊なケースについて理解しましょう。

数学的な四捨五入の基本ルール:

  • 5未満の数字は切り捨て
  • 5以上の数字は切り上げ

しかし、以下のような特殊なケースに注意が必要です:

  1. 負の数の四捨五入
float number = -3.5;
// -3.5の四捨五入は-4になる
// 多くのプログラマーが見落としがちなポイント
  1. 丁度5の場合の扱い
float number = 2.5;
// 伝統的な四捨五入では3に切り上げ
// しかし、銀行の丸め処理では偶数への丸めを採用することもある
  1. ゼロ付近の値
float number = 0.5;
// 正の数と同じルールで1に切り上げ
float number2 = -0.5;
// 負の数と同じルールで-1に切り下げ

浮動小数点における四捨五入の落とし穴

C++での浮動小数点数の四捨五入には、いくつかの重要な技術的な課題があります:

  1. 浮動小数点の精度限界
double value = 2.675;
// 実際のメモリ上での値:2.67499999999999982
std::cout << std::setprecision(17) << value << std::endl;
// 期待する四捨五入結果:2.68
// 実際の結果:2.67

この問題に対処するためのテクニック:

// イプシロンを使用した比較
const double epsilon = 1e-10;
double roundWithEpsilon(double value, int places) {
    double multiplier = std::pow(10.0, places);
    return std::round((value + epsilon) * multiplier) / multiplier;
}
  1. 桁数の扱い
// 不適切な実装例
double bad_round(double value, int places) {
    return std::round(value * 100) / 100;  // 固定で2桁の場合のみ対応
}

// より柔軟な実装例
double better_round(double value, int places) {
    double multiplier = std::pow(10.0, places);
    return std::round(value * multiplier) / multiplier;
}
  1. オーバーフローの危険性
// 危険な実装例
double dangerous_round(double value, int places) {
    double multiplier = std::pow(10.0, places);
    // 大きな値でオーバーフローの可能性あり
    return std::round(value * multiplier) / multiplier;
}

// より安全な実装例
double safe_round(double value, int places) {
    if (std::abs(value) > std::pow(10.0, std::numeric_limits<double>::digits10 - places)) {
        throw std::overflow_error("Value too large for specified precision");
    }
    double multiplier = std::pow(10.0, places);
    return std::round(value * multiplier) / multiplier;
}

これらの基礎知識と注意点を踏まえた上で、具体的な実装方法や各種テクニックについては後続のセクションで詳しく解説していきます。次のセクションでは、C++標準ライブラリで提供されている四捨五入関数について詳しく見ていきましょう。

C++標準ライブラリで利用できる四捨五入関数

std::roundの特徴と正しい使い方

C++11以降で導入されたstd::roundは、最も直接的な四捨五入関数です。この関数は<cmath>ヘッダーに含まれており、浮動小数点数を最も近い整数値に丸めます。

#include <cmath>
#include <iostream>
#include <iomanip>

int main() {
    // 基本的な使用方法
    double value1 = 3.7;
    std::cout << std::round(value1) << std::endl;  // 出力: 4

    double value2 = 3.2;
    std::cout << std::round(value2) << std::endl;  // 出力: 3

    // 負の数の扱い
    double value3 = -3.7;
    std::cout << std::round(value3) << std::endl;  // 出力: -4

    // 0.5のケース
    double value4 = 3.5;
    std::cout << std::round(value4) << std::endl;  // 出力: 4
}

std::roundのバリエーション:

// float版
float value_f = 3.7f;
float rounded_f = std::roundf(value_f);

// long double版
long double value_ld = 3.7L;
long double rounded_ld = std::roundl(value_ld);

特定の小数点以下の桁数で四捨五入する関数の実装:

double roundToDecimalPlaces(double value, int places) {
    const double multiplier = std::pow(10.0, places);
    return std::round(value * multiplier) / multiplier;
}

// 使用例
double value = 3.14159;
std::cout << std::fixed << std::setprecision(2);
std::cout << roundToDecimalPlaces(value, 2) << std::endl;  // 出力: 3.14

std::floor と std::ceil を使った四捨五入の実装

時にはstd::floor(切り捨て)とstd::ceil(切り上げ)を組み合わせて独自の四捨五入ロジックを実装する必要があります。

#include <cmath>

// カスタム四捨五入関数の実装
double customRound(double value) {
    // 正の数と負の数で別々に処理
    if (value >= 0.0) {
        return std::floor(value + 0.5);
    } else {
        return std::ceil(value - 0.5);
    }
}

より高度な実装例(偶数への丸めを行うバンカーズ・ラウンディング):

double bankersRound(double value) {
    double integerPart;
    double fractionalPart = std::modf(value, &integerPart);

    if (std::fabs(fractionalPart) != 0.5) {
        return std::round(value);
    }

    // 0.5の場合は偶数に丸める
    if (std::fmod(integerPart, 2.0) == 0.0) {
        return integerPart;
    } else {
        return value > 0 ? integerPart + 1 : integerPart - 1;
    }
}

// 使用例
std::cout << bankersRound(2.5) << std::endl;   // 出力: 2
std::cout << bankersRound(3.5) << std::endl;   // 出力: 4
std::cout << bankersRound(-2.5) << std::endl;  // 出力: -2

パフォーマンスを考慮した実装:

// 高速な四捨五入実装(小数点以下1桁の場合)
int fastRound(double value) {
    return (value > 0.0) ? 
           static_cast<int>(value + 0.5) : 
           static_cast<int>(value - 0.5);
}

// SIMD最適化を考慮した実装
#include <immintrin.h>

void vectorizedRound(double* input, double* output, size_t size) {
    for (size_t i = 0; i < size; i += 2) {
        __m128d values = _mm_load_pd(&input[i]);
        __m128d rounded = _mm_round_pd(values, _MM_FROUND_TO_NEAREST_INT);
        _mm_store_pd(&output[i], rounded);
    }
}

注意点:

  1. 精度の要件に応じて適切な型(float, double, long double)を選択する
  2. 大きな値を扱う場合はオーバーフローに注意
  3. パフォーマンスクリティカルな場所ではfastRoundvectorizedRoundの使用を検討
  4. 金融計算ではbankersRoundの使用を検討

これらの標準ライブラリ関数を基礎として、次のセクションでは具体的なユースケース別の実装テクニックについて詳しく見ていきます。

目的別の四捨五入実装テクニック

銀行取引向けの偶数丸めの実装方法

金融システムでは、公平な四捨五入を実現するために偶数丸め(Bankers Rounding)が広く使用されています。これは、0.5の場合に常に切り上げるのではなく、最も近い偶数に丸める方式です。

#include <cmath>
#include <iostream>
#include <decimal>

class MoneyRounder {
private:
    // 小数点以下の桁数を保持
    int decimals;

public:
    explicit MoneyRounder(int decimal_places = 2) : decimals(decimal_places) {}

    // 偶数丸めを実装
    double roundCurrency(double amount) {
        // スケールファクターの計算
        double scale = std::pow(10.0, decimals);
        double scaled = amount * scale;

        // 0.5かどうかを判定
        double fraction = std::abs(scaled) - std::floor(std::abs(scaled));
        if (std::abs(fraction - 0.5) < 1e-10) {
            // 0.5の場合は偶数になるように調整
            double floored = std::floor(scaled);
            if (std::fmod(floored, 2.0) == 0.0) {
                return floored / scale;
            } else {
                return (floored + 1.0) / scale;
            }
        }

        // 0.5以外は通常の四捨五入
        return std::round(scaled) / scale;
    }

    // 複数の通貨金額をバッチ処理
    std::vector<double> batchRoundCurrency(const std::vector<double>& amounts) {
        std::vector<double> result;
        result.reserve(amounts.size());
        for (const auto& amount : amounts) {
            result.push_back(roundCurrency(amount));
        }
        return result;
    }
};

// 使用例
void demonstrateBankingRounding() {
    MoneyRounder rounder(2);  // 小数点以下2桁で丸め

    std::cout << std::fixed << std::setprecision(2);
    std::cout << rounder.roundCurrency(2.135) << std::endl;  // 2.14
    std::cout << rounder.roundCurrency(2.125) << std::endl;  // 2.12 (偶数丸め)
    std::cout << rounder.roundCurrency(2.145) << std::endl;  // 2.14
}

パフォーマンスを重視した高速な四捨五入の実装

大量のデータを処理する場合や、リアルタイム処理が必要な場合には、パフォーマンスを最適化した実装が必要です。

#include <immintrin.h>
#include <vector>

class FastRounder {
public:
    // SIMD命令を使用した高速な四捨五入
    static void roundArraySIMD(const double* input, double* output, size_t size) {
        // 16バイトアライメント確認
        size_t aligned_size = size & ~1ULL;

        #pragma omp parallel for
        for (size_t i = 0; i < aligned_size; i += 2) {
            __m128d values = _mm_load_pd(&input[i]);
            __m128d rounded = _mm_round_pd(values, _MM_FROUND_TO_NEAREST_INT);
            _mm_store_pd(&output[i], rounded);
        }

        // 残りの要素を処理
        for (size_t i = aligned_size; i < size; ++i) {
            output[i] = std::round(input[i]);
        }
    }

    // キャッシュフレンドリーな実装
    static void roundArrayCacheOptimized(std::vector<double>& values) {
        constexpr size_t CACHE_LINE = 64;  // 一般的なキャッシュラインサイズ
        const size_t block_size = CACHE_LINE / sizeof(double);

        #pragma omp parallel for
        for (size_t i = 0; i < values.size(); i += block_size) {
            size_t end = std::min(i + block_size, values.size());
            for (size_t j = i; j < end; ++j) {
                values[j] = std::round(values[j]);
            }
        }
    }
};

指定桁数での四捨五入実装

異なる精度要件に対応できる柔軟な実装を提供します。

class PrecisionRounder {
public:
    // 指定桁数での四捨五入(正の桁数は小数点以下、負の桁数は整数部)
    static double roundToSignificantDigits(double value, int digits) {
        if (value == 0.0) return 0.0;

        double scale = std::pow(10.0, digits - 1 - std::floor(std::log10(std::abs(value))));
        return std::round(value * scale) / scale;
    }

    // 科学的表記法での四捨五入
    static std::pair<double, int> scientificNotationRound(double value, int significant_digits) {
        if (value == 0.0) return {0.0, 0};

        int exponent = static_cast<int>(std::floor(std::log10(std::abs(value))));
        double mantissa = value / std::pow(10.0, exponent);

        mantissa = std::round(mantissa * std::pow(10.0, significant_digits - 1)) / 
                  std::pow(10.0, significant_digits - 1);

        return {mantissa, exponent};
    }

    // 範囲を指定した四捨五入
    template<typename T>
    static T roundWithinRange(double value, T min_value, T max_value) {
        double rounded = std::round(value);
        return static_cast<T>(std::clamp(rounded, static_cast<double>(min_value), 
                                                static_cast<double>(max_value)));
    }
};

// 使用例
void demonstratePrecisionRounding() {
    PrecisionRounder rounder;

    // 有効数字での四捨五入
    std::cout << rounder.roundToSignificantDigits(123.456, 4) << std::endl;  // 123.5

    // 科学的表記法での四捨五入
    auto [mantissa, exponent] = rounder.scientificNotationRound(12345.67, 3);
    std::cout << mantissa << "e" << exponent << std::endl;  // 1.23e4

    // 範囲指定での四捨五入
    std::cout << rounder.roundWithinRange<int>(123.6, 0, 100) << std::endl;  // 100
}

これらの実装は、それぞれのユースケースに応じて最適化されています:

  1. 金融取引向け実装は、正確性と公平性を重視
  2. パフォーマンス重視の実装は、SIMD命令とキャッシュ最適化を活用
  3. 精度指定の実装は、柔軟性と再利用性を重視

実際の使用時には、要件に応じて適切な実装を選択し、必要に応じてカスタマイズすることをお勧めします。

四捨五入における精度とエラー処理

浮動小数点数の精度限界への対処法

浮動小数点数の精度限界は、四捨五入操作において重要な課題となります。以下では、主な問題点とその対処法を解説します。

#include <cmath>
#include <limits>
#include <stdexcept>
#include <iostream>

class PrecisionHandler {
private:
    // 浮動小数点の比較用イプシロン
    static constexpr double epsilon = std::numeric_limits<double>::epsilon() * 100;

public:
    // 浮動小数点数の比較
    static bool isApproximatelyEqual(double a, double b) {
        return std::abs(a - b) <= epsilon * std::max(std::abs(a), std::abs(b));
    }

    // 精度を考慮した四捨五入
    static double preciseRound(double value, int places) {
        // 有効桁数チェック
        if (places > std::numeric_limits<double>::digits10) {
            throw std::invalid_argument("Requested precision exceeds double precision");
        }

        // スケールファクターの計算
        double scale = std::pow(10.0, places);
        double scaled = value * scale;

        // 丸め誤差の補正
        if (std::abs(scaled - std::round(scaled)) < epsilon) {
            scaled = std::round(scaled);
        }

        return scaled / scale;
    }

    // 数値の正規化チェック
    static bool isNormalNumber(double value) {
        return std::fpclassify(value) == FP_NORMAL;
    }
};

// 使用例
void demonstratePrecisionHandling() {
    PrecisionHandler handler;

    // 精度の問題がある例
    double problematic = 2.675;
    std::cout << std::fixed << std::setprecision(15);
    std::cout << "Raw value: " << problematic << std::endl;
    std::cout << "Precise rounded: " << handler.preciseRound(problematic, 2) << std::endl;

    // 非正規化数のチェック
    double tiny = std::numeric_limits<double>::denorm_min();
    std::cout << "Is normal: " << std::boolalpha << handler.isNormalNumber(tiny) << std::endl;
}

オーバーフローとアンダーフローの防止策

数値計算におけるオーバーフローとアンダーフローは、深刻なバグの原因となります。これらを適切に処理する方法を見ていきます。

class NumericRangeHandler {
private:
    template<typename T>
    static bool willMultiplicationOverflow(T a, T b) {
        if (a > 0) {
            if (b > 0) {
                return a > std::numeric_limits<T>::max() / b;
            }
            return b < std::numeric_limits<T>::min() / a;
        }
        if (b > 0) {
            return a < std::numeric_limits<T>::min() / b;
        }
        return a != 0 && b < std::numeric_limits<T>::max() / a;
    }

public:
    // 安全な四捨五入実装
    static double safeRound(double value, int places) {
        try {
            // 範囲チェック
            if (std::isnan(value) || std::isinf(value)) {
                throw std::domain_error("Input is NaN or infinite");
            }

            // スケールファクターの計算とオーバーフローチェック
            double scale = std::pow(10.0, std::abs(places));
            if (willMultiplicationOverflow(value, scale)) {
                throw std::overflow_error("Scaling would cause overflow");
            }

            // アンダーフローチェック
            if (std::abs(value) < std::numeric_limits<double>::min() * scale) {
                return 0.0;  // 値が小さすぎる場合は0を返す
            }

            // 四捨五入の実行
            double scaled = value * scale;
            return std::round(scaled) / scale;
        } catch (const std::exception& e) {
            // エラーログの記録
            std::cerr << "Error in safeRound: " << e.what() << std::endl;
            throw;  // 上位層での処理のために再スロー
        }
    }

    // 範囲チェック付き四捨五入
    template<typename T>
    static T boundedRound(double value, T min_bound, T max_bound) {
        if (value < static_cast<double>(min_bound) || 
            value > static_cast<double>(max_bound)) {
            throw std::out_of_range("Value outside allowed range");
        }

        return static_cast<T>(std::round(value));
    }

    // エラー回復機能付き四捨五入
    static double resilientRound(double value, int places, double fallback) {
        try {
            return safeRound(value, places);
        } catch (const std::exception&) {
            return fallback;  // エラー時は指定された値を返す
        }
    }
};

// エラーハンドリングの実装例
void errorHandlingExample() {
    NumericRangeHandler handler;

    try {
        // 通常の使用例
        double result1 = handler.safeRound(123.456, 2);

        // 範囲チェック付きの使用例
        int result2 = handler.boundedRound(123.456, 0, 1000);

        // フォールバック付きの使用例
        double result3 = handler.resilientRound(1e308, 2, 0.0);

    } catch (const std::overflow_error& e) {
        std::cerr << "Overflow error: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "Range error: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Unexpected error: " << e.what() << std::endl;
    }
}

これらの実装のポイントは以下の通りです:

  1. 精度の保証:
  • イプシロンを使用した浮動小数点数の比較
  • 有効桁数のチェック
  • 非正規化数の検出と処理
  1. エラー処理:
  • オーバーフロー/アンダーフローの事前検出
  • 例外を使用した適切なエラー通知
  • フォールバック機能の実装
  1. 回復機能:
  • エラー発生時の代替値の提供
  • ログ記録によるデバッグサポート
  • 段階的なエラー処理戦略

これらの機能を組み合わせることで、堅牢で信頼性の高い四捨五入処理を実現できます。

クロスプラットフォームでの四捨五入の実装

異なるコンパイラでの動作の違いと対策

各プラットフォームやコンパイラによって浮動小数点数の扱いが異なる場合があります。以下では、これらの違いに対応する方法を解説します。

#include <cmath>
#include <cfenv>

class CrossPlatformRounder {
private:
    // プラットフォーム固有の設定を検出
    static bool isFeatureSupported() {
        #if defined(_MSC_VER)  // Visual Studio
            return true;
        #elif defined(__GNUC__)  // GCC
            return true;
        #elif defined(__clang__)  // Clang
            return true;
        #else
            return false;
        #endif
    }

    // 浮動小数点環境の設定を保存・復元するRAIIクラス
    class FenvGuard {
    private:
        std::fenv_t saved_fenv;
    public:
        FenvGuard() {
            std::fegetenv(&saved_fenv);
        }
        ~FenvGuard() {
            std::fesetenv(&saved_fenv);
        }
    };

public:
    // プラットフォーム互換の四捨五入実装
    static double platformIndependentRound(double value) {
        // 浮動小数点環境の一時的な保存
        FenvGuard guard;

        #if defined(_MSC_VER)
            _controlfp(_RC_NEAR, _MCW_RC);  // Windows向け丸めモード設定
        #else
            std::fesetround(FE_TONEAREST);  // POSIX向け丸めモード設定
        #endif

        return std::round(value);
    }

    // プラットフォーム固有の最適化を使用した実装
    static double optimizedRound(double value) {
        #if defined(_MSC_VER) && defined(_M_X64)
            // Windows x64向け最適化
            return _mm_cvtsd_f64(_mm_round_sd(_mm_set_sd(value), _mm_set_sd(0.0), 
                                _MM_FROUND_TO_NEAREST_INT));
        #elif defined(__GNUC__) && defined(__SSE4_1__)
            // GCC/SSE4.1向け最適化
            return __builtin_round(value);
        #else
            // 汎用実装
            return std::round(value);
        #endif
    }
};

// コンパイラ固有の警告を制御
class WarningController {
public:
    static void suppressWarnings() {
        #if defined(_MSC_VER)
            #pragma warning(push)
            #pragma warning(disable: 4723)  // 潜在的なゼロ除算
        #elif defined(__GNUC__)
            #pragma GCC diagnostic push
            #pragma GCC diagnostic ignored "-Wfloat-equal"
        #endif
    }

    static void restoreWarnings() {
        #if defined(_MSC_VER)
            #pragma warning(pop)
        #elif defined(__GNUC__)
            #pragma GCC diagnostic pop
        #endif
    }
};

プラットフォーム独立な実装のベストプラクティス

クロスプラットフォーム開発では、移植性と保守性を重視した実装が重要です。

class PortableRoundingImplementation {
private:
    // プラットフォーム非依存の定数定義
    static constexpr double EPSILON = 1e-10;

    // コンパイル時定数の利用
    template<typename T>
    static constexpr bool isPowerOfTwo(T value) {
        return value > 0 && (value & (value - 1)) == 0;
    }

public:
    // 移植性の高い実装
    template<typename T>
    static T portableRound(double value) {
        static_assert(std::is_arithmetic<T>::value, 
                     "Template parameter must be an arithmetic type");

        // 無限大とNaNのチェック
        if (!std::isfinite(value)) {
            throw std::domain_error("Input must be a finite number");
        }

        // プラットフォーム非依存の実装
        double integer_part;
        double fractional_part = std::modf(value, &integer_part);

        if (std::abs(fractional_part) < EPSILON) {
            return static_cast<T>(integer_part);
        }

        if (fractional_part > 0.5 || 
            (std::abs(fractional_part - 0.5) < EPSILON && integer_part >= 0)) {
            integer_part += 1.0;
        } else if (fractional_part < -0.5 || 
                   (std::abs(fractional_part + 0.5) < EPSILON && integer_part < 0)) {
            integer_part -= 1.0;
        }

        return static_cast<T>(integer_part);
    }

    // 異なる数値型間の安全な変換
    template<typename From, typename To>
    static To safeCast(From value) {
        static_assert(std::is_arithmetic<From>::value && std::is_arithmetic<To>::value,
                     "Both types must be arithmetic");

        if (value > static_cast<From>(std::numeric_limits<To>::max()) ||
            value < static_cast<From>(std::numeric_limits<To>::min())) {
            throw std::overflow_error("Value cannot be safely converted");
        }

        return static_cast<To>(value);
    }

    // 浮動小数点演算の移植性チェック
    static void verifyPortability() {
        // IEEEの浮動小数点形式をサポートしているか確認
        static_assert(std::numeric_limits<double>::is_iec559,
                     "IEEE 754 floating-point format is required");

        // エンディアンの確認
        const int endian_check = 1;
        const bool is_little_endian = *reinterpret_cast<const char*>(&endian_check) == 1;

        if (!is_little_endian) {
            std::cerr << "Warning: Running on big-endian platform" << std::endl;
        }
    }
};

// 使用例
void demonstrateCrossPlatformRounding() {
    CrossPlatformRounder rounder;
    PortableRoundingImplementation portable;

    // プラットフォーム互換の四捨五入
    double value1 = 3.5;
    std::cout << rounder.platformIndependentRound(value1) << std::endl;

    // 移植性の高い実装の使用
    try {
        int rounded = portable.portableRound<int>(value1);
        std::cout << rounded << std::endl;

        // 大きな値の安全な変換
        long large_value = portable.safeCast<double, long>(1e10);
        std::cout << large_value << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

クロスプラットフォーム実装のポイント:

  1. コンパイラ対応:
  • プリプロセッサマクロを使用したプラットフォーム検出
  • コンパイラ固有の最適化オプションの制御
  • プラットフォーム固有の警告の管理
  1. 移植性の確保:
  • プラットフォーム非依存の定数とアルゴリズムの使用
  • テンプレートを活用した型安全性の確保
  • 例外処理による安全な境界値チェック
  1. 性能最適化:
  • プラットフォーム固有の最適化命令の利用
  • RAII手法による資源管理
  • コンパイル時最適化の活用

これらの実装により、異なるプラットフォームでも一貫した動作と最適な性能を実現できます。

四捨五入のユニットテスト実装

エッジケースを考慮したテストケースの設計

四捨五入処理のテストでは、通常のケースだけでなく、様々なエッジケースを考慮する必要があります。以下では、Google Testフレームワークを使用した包括的なテストスイートを実装します。

#include <gtest/gtest.h>
#include <cmath>
#include <limits>
#include <vector>

// テスト対象のクラス(前述の実装から)
class RoundingImplementation {
public:
    static double round(double value, int places) {
        double scale = std::pow(10.0, places);
        return std::round(value * scale) / scale;
    }
};

// 基本的なテストケース
class RoundingBasicTest : public ::testing::Test {
protected:
    RoundingImplementation rounder;

    // 許容誤差の定義
    static constexpr double epsilon = 1e-10;

    // 浮動小数点数の比較ヘルパー関数
    bool isApproximatelyEqual(double a, double b) {
        return std::abs(a - b) <= epsilon * std::max(std::abs(a), std::abs(b));
    }
};

// 基本的な四捨五入のテスト
TEST_F(RoundingBasicTest, BasicRoundingCases) {
    // 通常の四捨五入ケース
    EXPECT_TRUE(isApproximatelyEqual(rounder.round(3.14159, 2), 3.14));
    EXPECT_TRUE(isApproximatelyEqual(rounder.round(3.14559, 2), 3.15));

    // ゼロ付近の値
    EXPECT_TRUE(isApproximatelyEqual(rounder.round(0.0001, 2), 0.00));
    EXPECT_TRUE(isApproximatelyEqual(rounder.round(-0.0001, 2), 0.00));

    // 整数値
    EXPECT_TRUE(isApproximatelyEqual(rounder.round(100.0, 2), 100.00));
    EXPECT_TRUE(isApproximatelyEqual(rounder.round(-100.0, 2), -100.00));
}

// エッジケースのテスト
class RoundingEdgeCaseTest : public RoundingBasicTest {
protected:
    void SetUp() override {
        // テストごとの初期化が必要な場合はここに記述
    }
};

TEST_F(RoundingEdgeCaseTest, ExtremeValues) {
    // 最大値付近
    EXPECT_NO_THROW(rounder.round(std::numeric_limits<double>::max(), 2));

    // 最小値付近
    EXPECT_NO_THROW(rounder.round(std::numeric_limits<double>::min(), 2));

    // 非正規化数
    EXPECT_NO_THROW(rounder.round(std::numeric_limits<double>::denorm_min(), 2));
}

TEST_F(RoundingEdgeCaseTest, SpecialValues) {
    // NaNの処理
    EXPECT_TRUE(std::isnan(rounder.round(std::numeric_limits<double>::quiet_NaN(), 2)));

    // 無限大の処理
    EXPECT_TRUE(std::isinf(rounder.round(std::numeric_limits<double>::infinity(), 2)));
    EXPECT_TRUE(std::isinf(rounder.round(-std::numeric_limits<double>::infinity(), 2)));
}

// 境界値のテスト
class RoundingBoundaryTest : public RoundingBasicTest {
protected:
    struct TestCase {
        double input;
        int places;
        double expected;
    };

    std::vector<TestCase> getBoundaryTestCases() {
        return {
            {5.5, 0, 6.0},    // 典型的な境界
            {-5.5, 0, -6.0},  // 負の数の境界
            {2.5, 0, 3.0},    // 0.5での切り上げ
            {-2.5, 0, -3.0},  // -0.5での切り下げ
            {2.05, 1, 2.1},   // 小数点以下1桁での境界
            {2.995, 2, 3.00}  // 小数点以下2桁での境界
        };
    }
};

TEST_F(RoundingBoundaryTest, BoundaryValues) {
    for (const auto& test_case : getBoundaryTestCases()) {
        EXPECT_TRUE(isApproximatelyEqual(
            rounder.round(test_case.input, test_case.places),
            test_case.expected
        )) << "Failed for input: " << test_case.input 
           << ", places: " << test_case.places;
    }
}

異なる入力値での動作検証方法

様々な入力パターンに対する動作を検証するパラメータ化テストを実装します。

class RoundingParameterizedTest : 
    public RoundingBasicTest,
    public ::testing::WithParamInterface<std::tuple<double, int, double>> {
protected:
    double getInput() const { return std::get<0>(GetParam()); }
    int getPlaces() const { return std::get<1>(GetParam()); }
    double getExpected() const { return std::get<2>(GetParam()); }
};

// パラメータ化テストの実行
TEST_P(RoundingParameterizedTest, ParameterizedRounding) {
    EXPECT_TRUE(isApproximatelyEqual(
        rounder.round(getInput(), getPlaces()),
        getExpected()
    )) << "Failed for input: " << getInput() 
       << ", places: " << getPlaces();
}

// テストケースの定義
INSTANTIATE_TEST_SUITE_P(
    RoundingTests,
    RoundingParameterizedTest,
    ::testing::Values(
        std::make_tuple(3.14159, 2, 3.14),
        std::make_tuple(3.14559, 2, 3.15),
        std::make_tuple(3.999999, 2, 4.00),
        std::make_tuple(-3.14159, 2, -3.14),
        std::make_tuple(-3.14559, 2, -3.15),
        std::make_tuple(0.0, 2, 0.00)
    )
);

// パフォーマンステスト
class RoundingPerformanceTest : public RoundingBasicTest {
protected:
    static constexpr size_t ITERATIONS = 1000000;
    std::vector<double> test_data;

    void SetUp() override {
        test_data.reserve(ITERATIONS);
        for (size_t i = 0; i < ITERATIONS; ++i) {
            test_data.push_back(static_cast<double>(i) / 1000.0);
        }
    }
};

TEST_F(RoundingPerformanceTest, BulkRoundingPerformance) {
    auto start = std::chrono::high_resolution_clock::now();

    for (const auto& value : test_data) {
        volatile double result = rounder.round(value, 2);
        (void)result;  // 最適化の防止
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    // 実行時間の検証
    EXPECT_LT(duration.count(), 1000)  // 1秒未満であることを確認
        << "Performance test took " << duration.count() << "ms";
}

// カバレッジ向上のための追加テスト
TEST_F(RoundingBasicTest, RoundingPrecisionLimits) {
    // 様々な小数点以下の桁数でのテスト
    for (int places = 0; places <= 15; ++places) {
        double value = 1.0 / std::pow(10.0, places);
        EXPECT_NO_THROW(rounder.round(value, places))
            << "Failed for places: " << places;
    }
}

テスト実装のポイント:

  1. テストケースの網羅性:
  • 基本的なケース
  • エッジケース
  • 境界値
  • 特殊な入力値
  1. テスト設計の原則:
  • 各テストは独立して実行可能
  • テストの意図が明確
  • エラーメッセージが有用
  1. パフォーマンス考慮:
  • 実行時間の測定
  • リソース使用の確認
  • 最適化の影響の考慮
  1. メンテナンス性:
  • テストコードの構造化
  • 共通機能の抽出
  • 適切なドキュメント化

これらのテストにより、四捨五入実装の信頼性と品質を確保できます。