HOWTO · C++

C++ で double を整数に丸める方法

この記事では、C++ で double を int に丸める方法を示します。

このページの内容

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <cmath>
#include <iostream>

int main() {
    std::cout << "round(2.3) = " << std::round(2.3) << '\n';
}
round(2.3) = 2

double を int に変換する前に、小数部分をどう扱うかを選びます。最も近い整数に丸めるには std::round、戻り値として long が必要なら std::lround、小数部分をゼロ方向に切り捨てるには std::trunc を使います。これらの関数は <cmath> で宣言されていますが、どれも直接 int を返すわけではありません。

round() で最も近い整数に丸める

std::round は最も近い整数値を選び、ちょうど中間の値はゼロから遠ざかる方向に丸めます。引数と同じ型の浮動小数点値を返すため、呼び出しただけでは double から int への変換は行われません。結果は現在の浮動小数点丸めモードにも左右されません。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <cmath>
#include <initializer_list>
#include <iostream>

int main() {
    for (double value : {2.3, 2.5, -2.5}) {
        std::cout << "round(" << value << ") = " << std::round(value) << '\n';
    }
}
round(2.3) = 2
round(2.5) = 3
round(-2.5) = -3

中間値の例では、ゼロの正負両側でこの規則を確認できます。結果を直接 int に変換する場合は、丸めた値が int で表現できることを確認してからキャストしてください。詳しくは最後のセクションを参照してください。

結果を整数型で得るには lround() を使う

std::lround は最も近い整数を選び、中間値をゼロから遠ざけるという点では同じですが、戻り値は long です。次の処理で long が必要な場合に便利で、途中で浮動小数点値を経由せずに済みます。ただし、特定の丸め結果を long で表現できないこともあります。結果が long の範囲外の場合はドメインエラーが発生することがあり、範囲外の動作を利用して long の値を得る方法は移植性がありません。有限の double ならすべて std::lround で表現可能な long にできる、とは限りません。

次の例では各演算をラベルで示し、lround の結果を long に格納します。切り捨ての例では負の小数を使い、std::trunc がゼロ方向に進むことを示します。

この例には C++11 以降が必要です。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

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

int main() {
    static_assert(std::is_same<decltype(std::lround(2.5)), long>::value,
                  "std::lround returns long");
    const long nearest = std::lround(2.5);
    const double chopped = std::trunc(-3.9);
    std::cout << "lround returns long: " << nearest << '\n';
    std::cout << std::fixed << std::setprecision(1)
              << "trunc(-3.9) = " << chopped << '\n';
}
lround returns long: 3
trunc(-3.9) = -3.0

格納先が int の場合は、変換する前に値がその型の範囲内にあるか確認してください。戻り値が整数型でも、より狭い型への変換が安全になるわけではありません。

trunc() で小数部分を切り捨てる

最も近い整数を選ぶのではなく小数部分を取り除きたい場合は、std::trunc を使います。この関数はゼロ方向に進みます。たとえば、std::trunc(3.9) は 3.0、std::trunc(-3.9) は -3.0 です。戻り値は浮動小数点値のままで、入力と同じ型です。これは std::floor とは異なります。floor は負の無限大方向に進むため、std::floor(-3.9) は -4.0 になります。

整数型への変換では、この違いが重要です。有限の浮動小数点値を整数型にキャストすると、小数部分は同じくゼロ方向に切り捨てられますが、最も近い値への丸めは行われません。後続のチェックや計算の前に、浮動小数点値として切り捨てた結果が必要なら std::trunc を使います。

結果を安全に int に変換する

ここまでの関数は戻り値の型が異なります。std::round(double) と std::trunc(double) は double を返し、std::lround(double) は long を返します。丸めた結果を int に格納するには、まず値を計算し、それが int で表現できる範囲にあるか確認します。浮動小数点値から整数型への変換では小数部分がゼロ方向に切り捨てられます。切り捨て後の値を変換先の型で表現できない場合、その動作は未定義です。std::round が返す整数値 rounded について、次の厳密な境界条件を使うと、キャストを保守的に int の範囲内に限定できます。

int の値ビット数に基づく厳密な境界条件を、丸めた double 値 rounded に対して使います。

static_assert(std::numeric_limits<int>::radix == 2 && std::numeric_limits<double>::radix == 2);

static_assert(std::numeric_limits<int>::digits < std::numeric_limits<double>::max_exponent);

const double limit = std::ldexp(1.0, std::numeric_limits<int>::digits);

if (rounded > -limit && rounded < limit) { int result = static_cast<int>(rounded); }

std::numeric_limits を使うには <limits> をインクルードします。<cmath> には std::ldexp が用意されています。2進数の int の値域は [-2^digits, 2^digits) です。ここで使う厳密な区間はその安全な内側の範囲なので、int の限界値を double や long double で正確に表現できるかどうかに依存しません。表現可能な場合でも、下端の値は意図的に除外されます。キャストはチェックに成功した場合に限って行ってください。NaN も比較条件を満たしません。チェックに失敗した場合は、エラーを報告する、より広い型のまま保持するなど、アプリケーションに応じた方針を明示してください。std::lround の long の結果を int に変換する場合も、同じチェックを行います。

要点として、浮動小数点値のまま最も近い整数に丸めるには std::round、最も近い整数を long として得るには std::lround、小数部分をゼロ方向に切り捨てるには std::trunc を使います。int への変換は、値が表現可能であることを確認してから行ってください。