C++での数値丸め処理の基礎知識
数値丸め処理が必要となるケース
数値の丸め処理は、実務のプログラミングにおいて頻繁に必要となる操作です。主に以下のようなケースで利用されます:
- 金額計算での利用
- 税額計算時の端数処理
- 割引計算後の価格調整
- 為替レート換算後の金額調整
- 科学技術計算での活用
- 測定値の有効桁数の調整
- 計算結果の精度管理
- 統計処理でのデータ正規化
- ユーザーインターフェースでの表示
- グラフ表示時の軸目盛りの調整
- パーセンテージ表示時の小数点以下の制御
- プログレスバーの進捗率計算
- リソース割り当ての最適化
- メモリ配分時のアライメント調整
- タイムスライス制御での時間切り上げ
- ディスク容量計算時の境界値調整
C++標準ライブラリが提供する丸め関数の種類
C++標準ライブラリ(<cmath>)には、様々な丸め処理を実現するための関数が用意されています:
- 基本的な丸め関数
#include <cmath> double value = 3.7; double rounded = std::round(value); // 四捨五入(3.7 → 4.0) double floored = std::floor(value); // 切り捨て(3.7 → 3.0) double ceiled = std::ceil(value); // 切り上げ(3.7 → 4.0) double trunced = std::trunc(value); // 零方向への丸め(3.7 → 3.0)
- 浮動小数点数の剰余を求める関数
double fmod_result = std::fmod(5.7, 2.0); // 浮動小数点数の剰余(5.7 % 2.0 → 1.7) double remainder = std::remainder(5.7, 2.0); // IEEE 754準拠の剰余計算
- 近接整数への丸め関数
double value = -3.7; double nearest = std::nearbyint(value); // 最近接整数への丸め(バンカーズ丸め、FE_TONEAREST使用) long rounded_long = std::lround(value); // long型への丸め(-3.7 → -4) long long rounded_ll = std::llround(value); // long long型への丸め
- 型変換を伴う丸め関数
float value = 3.7f; int rounded_int = std::rint(value); // 現在の丸めモードに従って整数値へ double rounded_dbl = std::rint(value); // double型として結果を返す
これらの関数は、以下のような特徴を持っています:
| 関数名 | 主な用途 | 戻り値の型 | 特徴 |
|---|---|---|---|
| round | 一般的な四捨五入 | 浮動小数点数 | 最も一般的に使用される |
| floor | 切り捨て処理 | 浮動小数点数 | 常に小さい方の整数 |
| ceil | 切り上げ処理 | 浮動小数点数 | 常に大きい方の整数 |
| trunc | 小数部の切り捨て | 浮動小数点数 | 0方向への丸め |
| nearbyint | 最近接整数への丸め | 浮動小数点数 | 現在の丸めモードを使用 |
| lround/llround | 整数型への変換を伴う丸め | long/long long | 整数型が必要な場合に使用 |
各関数は特定のユースケースに最適化されており、適切な関数を選択することで、より効率的で正確な丸め処理を実現できます。次のセクションでは、最も一般的に使用されるstd::round関数について、詳しく解説していきます。
std::roundの使い方と実装例
基本的な使用方法と戻り値の型
std::round関数は、C++11以降で標準ライブラリの<cmath>ヘッダに含まれる数値丸め関数です。最も近い整数値への丸めを行う際に使用されます。
基本的な関数シグネチャ:
float round(float arg); double round(double arg); long double round(long double arg); float roundf(float arg); // C言語との互換性のための別名 double roundl(long double arg); // C言語との互換性のための別名
戻り値の特徴:
- 引数と同じ型の浮動小数点数を返します
- 小数部が0.5未満の場合は切り捨て
- 小数部が0.5以上の場合は切り上げ
- 戻り値は浮動小数点数型ですが、整数値を表現します
実際のコード例で見るround関数の動作
以下に、std::round関数の実践的な使用例を示します:
- 基本的な丸め処理
#include <iostream>
#include <cmath>
int main() {
// 基本的な丸め処理の例
double values[] = {3.3, 3.5, 3.7, -3.3, -3.5, -3.7};
std::cout << "基本的な丸め処理のデモ:\n";
for (double val : values) {
std::cout << "round(" << val << ") = " << std::round(val) << "\n";
}
return 0;
}
実行結果:
基本的な丸め処理のデモ: round(3.3) = 3 round(3.5) = 4 round(3.7) = 4 round(-3.3) = -3 round(-3.5) = -4 round(-3.7) = -4
- 実践的な使用例:パーセンテージ計算
#include <iostream>
#include <cmath>
#include <iomanip>
double calculate_percentage(int value, int total, int decimal_places = 1) {
// 指定された小数点以下の桁数でパーセンテージを計算
double multiplier = std::pow(10.0, decimal_places);
double percentage = (static_cast<double>(value) / total) * 100.0;
// round関数を使用して指定桁数で丸める
return std::round(percentage * multiplier) / multiplier;
}
int main() {
int successes = 42;
int total_trials = 97;
std::cout << std::fixed << std::setprecision(1);
std::cout << "成功率: "
<< calculate_percentage(successes, total_trials)
<< "%\n";
return 0;
}
- 金額計算での利用例
#include <iostream>
#include <cmath>
class PriceCalculator {
public:
// 税込価格の計算(1円単位で丸める)
static int calculate_tax_included_price(double price, double tax_rate = 0.10) {
return static_cast<int>(std::round(price * (1 + tax_rate)));
}
// 割引後価格の計算(10円単位で丸める)
static int calculate_discounted_price(double price, double discount_rate) {
double discounted = price * (1 - discount_rate);
return static_cast<int>(std::round(discounted / 10.0) * 10);
}
};
int main() {
double original_price = 1234.56;
// 税込価格の計算(1円単位)
int tax_included = PriceCalculator::calculate_tax_included_price(original_price);
// 20%割引価格の計算(10円単位)
int discounted = PriceCalculator::calculate_discounted_price(original_price, 0.20);
std::cout << "元の価格: " << original_price << "円\n"
<< "税込価格: " << tax_included << "円\n"
<< "割引価格: " << discounted << "円\n";
return 0;
}
- エッジケースの処理
#include <iostream>
#include <cmath>
#include <limits>
void demonstrate_edge_cases() {
// 特殊なケースの処理
std::cout << "エッジケースの処理:\n";
// 非常に大きな数値の丸め
double large_num = std::numeric_limits<double>::max() / 2;
std::cout << "大きな数値: " << std::round(large_num) << "\n";
// 非常に小さな数値の丸め
double tiny_num = std::numeric_limits<double>::min();
std::cout << "小さな数値: " << std::round(tiny_num) << "\n";
// ±0の処理
std::cout << "正のゼロ: " << std::round(0.0) << "\n";
std::cout << "負のゼロ: " << std::round(-0.0) << "\n";
// NaNとInfinityの処理
std::cout << "NaN: " << std::round(std::numeric_limits<double>::quiet_NaN()) << "\n";
std::cout << "Infinity: " << std::round(std::numeric_limits<double>::infinity()) << "\n";
}
これらの例は、std::round関数の実際の使用シーンを示しています。注意点として:
- 精度が重要な計算では、浮動小数点数の特性を理解した上で使用する
- 金額計算では、丸め誤差の蓄積を防ぐため、適切なタイミングで丸め処理を行う
- エッジケースの処理を考慮したロバストな実装を心がける
- パフォーマンスが重要な場合は、丸め処理の回数を最小限に抑える
次のセクションでは、さまざまな丸め方式とその実装方法について詳しく解説していきます。
さまざまな丸め方式とその実装方法
切り上げ(ceil)と切り捨て(floor)の実装
std::ceilとstd::floorは、それぞれ数値を切り上げ・切り捨てる際に使用する基本的な関数です。
- 基本的な使用方法
#include <iostream>
#include <cmath>
#include <iomanip>
void demonstrate_basic_rounding() {
double values[] = {3.1, 3.5, 3.7, -3.1, -3.5, -3.7};
std::cout << std::fixed << std::setprecision(1);
for (double val : values) {
std::cout << "値: " << val
<< " → ceil: " << std::ceil(val)
<< " | floor: " << std::floor(val) << "\n";
}
}
- カスタム切り上げ・切り捨て関数の実装
class CustomRounder {
public:
// 指定された倍数への切り上げ
static double ceil_to_multiple(double value, double multiple) {
return std::ceil(value / multiple) * multiple;
}
// 指定された倍数への切り捨て
static double floor_to_multiple(double value, double multiple) {
return std::floor(value / multiple) * multiple;
}
// 指定桁数での切り上げ
static double ceil_to_precision(double value, int decimals) {
double factor = std::pow(10.0, decimals);
return std::ceil(value * factor) / factor;
}
// 指定桁数での切り捨て
static double floor_to_precision(double value, int decimals) {
double factor = std::pow(10.0, decimals);
return std::floor(value * factor) / factor;
}
};
バンカーズ丸めの実装とその特徴
バンカーズ丸め(偶数丸め)は、統計処理や金融計算で使用される特殊な丸め方式です。
#include <iostream>
#include <cmath>
class BankersRounding {
public:
// バンカーズ丸めの実装
static double round(double value, int decimals = 0) {
double factor = std::pow(10.0, decimals);
double scaled = value * factor;
double fraction = scaled - std::floor(scaled);
if (fraction == 0.5) {
// 最近接偶数への丸め
double floor_val = std::floor(scaled);
return (static_cast<int>(floor_val) % 2 == 0 ? floor_val : std::ceil(scaled)) / factor;
} else {
// 通常の四捨五入
return std::round(scaled) / factor;
}
}
// 使用例を示すデモ関数
static void demonstrate() {
double test_values[] = {2.5, 3.5, 4.5, -2.5, -3.5, -4.5};
std::cout << "バンカーズ丸めのデモ:\n";
for (double val : test_values) {
std::cout << val << " → " << round(val) << "\n";
}
}
};
任意の小数点位置での丸め処理の実装
より複雑な丸め処理のニーズに対応するためのカスタム実装を紹介します。
- 汎用的な丸め処理クラス
#include <iostream>
#include <cmath>
#include <stdexcept>
class PrecisionRounder {
public:
enum class RoundingMode {
Round, // 四捨五入
Ceil, // 切り上げ
Floor, // 切り捨て
Bankers // バンカーズ丸め
};
// 指定された精度と方式で丸め処理を行う
static double round(double value, int decimals, RoundingMode mode = RoundingMode::Round) {
if (decimals < 0) {
throw std::invalid_argument("小数点以下の桁数は0以上である必要があります");
}
double factor = std::pow(10.0, decimals);
double scaled = value * factor;
switch (mode) {
case RoundingMode::Round:
return std::round(scaled) / factor;
case RoundingMode::Ceil:
return std::ceil(scaled) / factor;
case RoundingMode::Floor:
return std::floor(scaled) / factor;
case RoundingMode::Bankers:
return bankers_round(scaled) / factor;
default:
throw std::invalid_argument("不正な丸めモードが指定されました");
}
}
private:
static double bankers_round(double value) {
double integer_part;
double fractional_part = std::modf(value, &integer_part);
if (std::abs(fractional_part) != 0.5) {
return std::round(value);
}
// 最近接偶数への丸め
return (static_cast<long long>(integer_part) % 2 == 0) ?
integer_part :
integer_part + ((integer_part > 0) ? 1 : -1);
}
};
// 使用例
void demonstrate_precision_rounding() {
double test_value = 123.456789;
std::cout << "元の値: " << test_value << "\n\n";
// 異なる精度での丸め処理
for (int decimals = 0; decimals <= 4; ++decimals) {
std::cout << decimals << "桁での丸め結果:\n";
std::cout << "四捨五入: "
<< PrecisionRounder::round(test_value, decimals,
PrecisionRounder::RoundingMode::Round) << "\n";
std::cout << "切り上げ: "
<< PrecisionRounder::round(test_value, decimals,
PrecisionRounder::RoundingMode::Ceil) << "\n";
std::cout << "切り捨て: "
<< PrecisionRounder::round(test_value, decimals,
PrecisionRounder::RoundingMode::Floor) << "\n";
std::cout << "バンカーズ: "
<< PrecisionRounder::round(test_value, decimals,
PrecisionRounder::RoundingMode::Bankers) << "\n\n";
}
}
- 金額計算のための特殊な丸め処理
class MoneyRounder {
public:
enum class Currency {
JPY, // 日本円(1円単位)
USD, // 米ドル(セント単位)
EUR // ユーロ(セント単位)
};
static double round_currency(double amount, Currency currency) {
switch (currency) {
case Currency::JPY:
return std::round(amount); // 1円単位
case Currency::USD:
case Currency::EUR:
return std::round(amount * 100.0) / 100.0; // セント単位
default:
throw std::invalid_argument("不正な通貨が指定されました");
}
}
// 端数処理ルールに基づく丸め処理
static int round_by_rule(double amount, int unit = 1) {
if (unit <= 0) {
throw std::invalid_argument("丸め単位は正の値である必要があります");
}
return static_cast<int>(std::round(amount / unit) * unit);
}
};
これらの実装例は、以下のような特徴を持っています:
| 丸め方式 | 主な用途 | 特徴 |
|---|---|---|
| 四捨五入 | 一般的な数値の丸め | 最も一般的で理解しやすい |
| 切り上げ | 予算計算、リソース確保 | 常に大きい方に丸める |
| 切り捨て | 在庫管理、容量計算 | 常に小さい方に丸める |
| バンカーズ丸め | 統計処理、金融計算 | バイアスを最小限に抑える |
これらの丸め処理を実装する際の注意点:
- 浮動小数点数の精度限界を考慮する
- エッジケース(極値、NaN、無限大など)の処理を適切に行う
- パフォーマンスとメモリ使用量を考慮する
- ドメイン固有の要件(通貨の単位など)を反映させる
次のセクションでは、丸め処理における注意点と対策について詳しく解説していきます。
丸め処理における注意点と対策
浮動小数点の精度限界と対処法
浮動小数点数の精度限界は、丸め処理を実装する際の重要な考慮点です。以下に主な課題と対策を示します:
- 精度の限界を考慮した比較処理
#include <iostream>
#include <cmath>
#include <limits>
class FloatingPointComparison {
public:
// 浮動小数点数の比較用イプシロン
static constexpr double epsilon = std::numeric_limits<double>::epsilon();
// 浮動小数点数の等価性チェック
static bool is_approximately_equal(double a, double b,
double epsilon_multiplier = 1.0) {
return std::abs(a - b) <= epsilon * epsilon_multiplier;
}
// 丸め処理前の値の妥当性チェック
static bool is_valid_for_rounding(double value) {
return !std::isnan(value) &&
!std::isinf(value) &&
std::abs(value) <= std::numeric_limits<double>::max() / 2.0;
}
};
// 使用例
void demonstrate_precision_handling() {
double a = 0.1 + 0.2; // 0.30000000000000004
double b = 0.3; // 0.3
std::cout << "直接比較: " << (a == b ? "等しい" : "異なる") << "\n";
std::cout << "イプシロンを考慮した比較: "
<< (FloatingPointComparison::is_approximately_equal(a, b) ?
"等しい" : "異なる") << "\n";
}
- 精度を保証する丸め処理の実装
class PreciseRounder {
public:
// 固定小数点表現を使用した高精度な丸め処理
static double round_with_precision(double value, int decimals) {
if (!FloatingPointComparison::is_valid_for_rounding(value)) {
throw std::invalid_argument("不正な入力値です");
}
// 整数部での計算に変換
int64_t scale_factor = static_cast<int64_t>(std::pow(10, decimals));
int64_t scaled = static_cast<int64_t>(value * scale_factor +
(value >= 0 ? 0.5 : -0.5));
return static_cast<double>(scaled) / scale_factor;
}
// 累積誤差を防ぐための定期的な丸め処理
static double accumulated_sum_with_rounding(const std::vector<double>& values,
int decimals) {
double sum = 0.0;
for (double value : values) {
// 各ステップで丸め処理を行い、誤差の蓄積を防ぐ
sum = round_with_precision(sum + value, decimals);
}
return sum;
}
};
パフォーマンスを考慮した実装のポイント
パフォーマンスを最適化するための実装テクニックを紹介します:
- 高速な整数丸め処理
class FastRounder {
public:
// ビット演算を使用した高速な切り捨て(正の整数のみ)
static int fast_floor_to_power_of_two(int value, int power) {
int mask = (1 << power) - 1;
return value & ~mask;
}
// 乗除算を回避した高速な10の累乗計算
static constexpr int64_t power_of_ten(int n) {
static const int64_t powers[] = {
1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000
};
return (n >= 0 && n < 10) ? powers[n] :
static_cast<int64_t>(std::pow(10, n));
}
// キャッシュを活用した丸め処理
static double cached_round(double value, int decimals) {
static std::unordered_map<int, double> factor_cache;
auto it = factor_cache.find(decimals);
double factor = (it != factor_cache.end()) ?
it->second :
(factor_cache[decimals] = std::pow(10.0, decimals));
return std::round(value * factor) / factor;
}
};
- 並列処理を活用した大量データの丸め処理
#include <execution>
#include <algorithm>
#include <vector>
class ParallelRounder {
public:
// 並列処理による大量データの丸め処理
static std::vector<double> parallel_round(const std::vector<double>& values,
int decimals) {
std::vector<double> result(values.size());
double factor = std::pow(10.0, decimals);
std::transform(std::execution::par_unseq,
values.begin(), values.end(),
result.begin(),
[factor](double value) {
return std::round(value * factor) / factor;
});
return result;
}
};
クロスプラットフォームでの動作の違い
異なるプラットフォームでの一貫した動作を保証するための考慮点と対策を示します:
- プラットフォーム依存の問題に対する対策
class CrossPlatformRounder {
public:
// プラットフォーム非依存の丸め処理
static double platform_independent_round(double value) {
// 浮動小数点数の丸め方式を明示的に指定
return std::rint(value);
}
// 丸めモードの明示的な制御
class ScopedRoundingMode {
public:
ScopedRoundingMode(int mode) {
old_mode = std::fegetround();
std::fesetround(mode);
}
~ScopedRoundingMode() {
std::fesetround(old_mode);
}
private:
int old_mode;
};
// 丸めモードを指定した処理
static double round_with_mode(double value, int mode) {
ScopedRoundingMode scope(mode);
return std::rint(value);
}
};
- プラットフォーム間の差異をテストするためのユーティリティ
class RoundingTester {
public:
// 各プラットフォームでの動作を検証
static void verify_platform_behavior() {
std::cout << "プラットフォーム情報:\n"
<< "epsilon: " << std::numeric_limits<double>::epsilon() << "\n"
<< "丸めモード: " << std::fegetround() << "\n"
<< "最大精度: " << std::numeric_limits<double>::digits10 << "\n";
// クリティカルなケースのテスト
test_critical_values();
}
private:
static void test_critical_values() {
double test_values[] = {
0.5, -0.5, // 基本的な境界値
0.49999999999999994, // 0.5に非常に近い値
std::nextafter(0.5, 0.0), // 0.5のすぐ下の値
std::nextafter(0.5, 1.0) // 0.5のすぐ上の値
};
for (double value : test_values) {
std::cout << "値: " << value << "\n"
<< " std::round: " << std::round(value) << "\n"
<< " std::rint: " << std::rint(value) << "\n"
<< " std::nearbyint: " << std::nearbyint(value) << "\n";
}
}
};
実装時の主な注意点をまとめると:
| 分類 | 注意点 | 対策 |
|---|---|---|
| 精度 | 浮動小数点数の比較 | イプシロンを考慮した比較処理 |
| 精度 | 累積誤差 | 定期的な丸め処理の実施 |
| パフォーマンス | 計算コスト | ビット演算や累乗のキャッシュ化 |
| パフォーマンス | 大量データ処理 | 並列処理の活用 |
| クロスプラットフォーム | 丸めモードの違い | 明示的なモード指定 |
| クロスプラットフォーム | 精度の違い | プラットフォーム非依存の実装 |
次のセクションでは、これらの知識を活用した実践的な丸め処理の実装テクニックについて解説します。
実践的な丸め処理の実装テクニック
テンプレートを使用した汎用的な丸め処理の実装
テンプレートを活用することで、型に依存しない柔軟な丸め処理を実装できます。
- 基本的なテンプレート実装
#include <type_traits>
#include <cmath>
template<typename T>
class GenericRounder {
// 型の妥当性チェック
static_assert(std::is_arithmetic<T>::value,
"算術型である必要があります");
public:
// 基本的な丸め処理
static T round_to_precision(T value, int decimals) {
static_assert(std::is_floating_point<T>::value,
"浮動小数点型である必要があります");
T factor = std::pow(T(10), decimals);
return std::round(value * factor) / factor;
}
// 指定された倍数への丸め
template<typename U = T>
static typename std::enable_if<std::is_integral<U>::value, U>::type
round_to_multiple(U value, U multiple) {
return ((value + multiple / 2) / multiple) * multiple;
}
};
// 使用例
void demonstrate_generic_rounding() {
// 浮動小数点数の丸め
double d_value = 123.456789;
float f_value = 123.456f;
std::cout << "double型の丸め: "
<< GenericRounder<double>::round_to_precision(d_value, 2) << "\n";
std::cout << "float型の丸め: "
<< GenericRounder<float>::round_to_precision(f_value, 2) << "\n";
// 整数の丸め
int i_value = 123;
std::cout << "int型の倍数丸め: "
<< GenericRounder<int>::round_to_multiple(i_value, 10) << "\n";
}
- 高度なテンプレート実装
template<typename T,
template<typename> class RoundingPolicy = DefaultRoundingPolicy>
class AdvancedRounder {
public:
using value_type = T;
using policy_type = RoundingPolicy<T>;
// コンストラクタで丸め方式を設定
explicit AdvancedRounder(int decimals = 0)
: decimals_(decimals), policy_() {}
// 丸め処理の実行
T round(T value) const {
return policy_.round(value, decimals_);
}
// 複数値の一括丸め処理
template<typename Container>
Container round_all(const Container& values) const {
Container result;
result.reserve(values.size());
std::transform(values.begin(), values.end(),
std::back_inserter(result),
[this](const T& val) { return this->round(val); });
return result;
}
private:
int decimals_;
policy_type policy_;
};
// 丸め方式のポリシークラス
template<typename T>
class DefaultRoundingPolicy {
public:
T round(T value, int decimals) const {
T factor = std::pow(T(10), decimals);
return std::round(value * factor) / factor;
}
};
template<typename T>
class BankersRoundingPolicy {
public:
T round(T value, int decimals) const {
T factor = std::pow(T(10), decimals);
T scaled = value * factor;
return std::rint(scaled) / factor;
}
};
SIMDコマンドを活用した高速な丸め処理の実現
SIMD命令を使用することで、丸め処理のパフォーマンスを大幅に向上させることができます。
#include <immintrin.h>
#include <vector>
class SimdRounder {
public:
// AVX2を使用した高速な丸め処理
static void round_array_avx2(double* data, size_t size) {
// 8バイトアライメントチェック
if (reinterpret_cast<uintptr_t>(data) % 8 != 0) {
throw std::runtime_error("データがアライメントされていません");
}
size_t i = 0;
// AVX2による4要素同時処理
for (; i + 4 <= size; i += 4) {
__m256d vec = _mm256_load_pd(data + i);
vec = _mm256_round_pd(vec, _MM_FROUND_TO_NEAREST_INT);
_mm256_store_pd(data + i, vec);
}
// 残りの要素を通常の方法で処理
for (; i < size; ++i) {
data[i] = std::round(data[i]);
}
}
// SIMDを使用した高速な固定小数点丸め処理
static std::vector<double> round_to_decimals_simd(
const std::vector<double>& values, int decimals) {
std::vector<double> result(values.size());
double factor = std::pow(10.0, decimals);
size_t i = 0;
__m256d factor_vec = _mm256_set1_pd(factor);
__m256d inv_factor_vec = _mm256_set1_pd(1.0 / factor);
for (; i + 4 <= values.size(); i += 4) {
__m256d vec = _mm256_load_pd(values.data() + i);
vec = _mm256_mul_pd(vec, factor_vec);
vec = _mm256_round_pd(vec, _MM_FROUND_TO_NEAREST_INT);
vec = _mm256_mul_pd(vec, inv_factor_vec);
_mm256_store_pd(result.data() + i, vec);
}
for (; i < values.size(); ++i) {
result[i] = std::round(values[i] * factor) / factor;
}
return result;
}
};
単体テストで確認すべきエッジケース
丸め処理の信頼性を確保するために、以下のようなテストケースを実装します。
#include <gtest/gtest.h>
#include <limits>
#include <cmath>
class RoundingTests : public ::testing::Test {
protected:
static constexpr double epsilon = std::numeric_limits<double>::epsilon();
// 値の近似的な比較
bool is_approximately_equal(double a, double b,
double epsilon_multiplier = 1.0) {
return std::abs(a - b) <= epsilon * epsilon_multiplier;
}
};
TEST_F(RoundingTests, BasicRoundingTests) {
// 基本的なケース
EXPECT_EQ(std::round(3.3), 3.0);
EXPECT_EQ(std::round(3.7), 4.0);
EXPECT_EQ(std::round(-3.3), -3.0);
EXPECT_EQ(std::round(-3.7), -4.0);
}
TEST_F(RoundingTests, EdgeCases) {
// エッジケース
EXPECT_EQ(std::round(0.0), 0.0);
EXPECT_EQ(std::round(-0.0), -0.0);
EXPECT_TRUE(std::isnan(std::round(std::nan(""))));
EXPECT_EQ(std::round(std::numeric_limits<double>::infinity()),
std::numeric_limits<double>::infinity());
}
TEST_F(RoundingTests, PrecisionCriticalCases) {
// 精度が重要なケース
double almost_half = 0.5 - epsilon;
double just_over_half = 0.5 + epsilon;
EXPECT_EQ(std::round(0.5), 0.0); // バンカーズ丸めの場合
EXPECT_EQ(std::round(almost_half), 0.0);
EXPECT_EQ(std::round(just_over_half), 1.0);
}
TEST_F(RoundingTests, LargeNumberTests) {
// 大きな数値のテスト
double large_number = std::numeric_limits<double>::max() / 2.0;
EXPECT_NO_THROW(std::round(large_number));
double small_number = std::numeric_limits<double>::min();
EXPECT_NO_THROW(std::round(small_number));
}
// カスタム実装のテスト例
TEST_F(RoundingTests, CustomImplementationTests) {
// GenericRounderのテスト
double test_value = 123.456789;
auto rounded = GenericRounder<double>::round_to_precision(test_value, 2);
EXPECT_TRUE(is_approximately_equal(rounded, 123.46));
// AdvancedRounderのテスト
AdvancedRounder<double> rounder(2);
EXPECT_TRUE(is_approximately_equal(rounder.round(test_value), 123.46));
// SIMDRounderのテスト
std::vector<double> test_values = {1.1, 2.2, 3.3, 4.4};
auto simd_result = SimdRounder::round_to_decimals_simd(test_values, 1);
EXPECT_EQ(simd_result.size(), test_values.size());
EXPECT_TRUE(is_approximately_equal(simd_result[0], 1.1));
}
実装時の主なポイントをまとめると:
| 実装アプローチ | メリット | デメリット | 使用シーン |
|---|---|---|---|
| テンプレート実装 | 型の汎用性が高い | コンパイル時間が増加 | 異なる数値型での利用 |
| SIMD実装 | 処理が高速 | 実装が複雑 | 大量データの処理 |
| ポリシーベース | 柔軟な動作変更 | 設計が複雑化 | 要件変更が多い場合 |
実装時の注意点:
- テンプレートの特殊化で型ごとの最適な実装を提供
- SIMDを使用する際はアライメントに注意
- エッジケースを網羅的にテスト
- パフォーマンスとメンテナンス性のバランスを考慮
これらの実装テクニックを適切に組み合わせることで、効率的で信頼性の高い丸め処理を実現できます。