C++のstd::roundについて

AI実装検定のご案内

目次

std::roundの基本仕様

std::roundは、浮動小数点数を最も近い整数値へ丸めるための関数です。

使用するには、<cmath>ヘッダーをインクルードします。

#include <cmath>

double result = std::round(3.7);

この場合、resultの値は4.0になります。

ただし、丸めた結果が整数に見えても、戻り値の型は基本的に浮動小数点型です。

戻り値の型

std::roundは、引数の浮動小数点型に応じた値を返します。

一般的な型については、次のように考えられます。

float       std::round(float value);
double      std::round(double value);
long double std::round(long double value);

使用例は次のとおりです。

auto a = std::round(3.7f);  // float
auto b = std::round(3.7);   // double
auto c = std::round(3.7L);  // long double

例えば、次のコードでは、結果の値は4.0ですが、型はdoubleです。

double result = std::round(3.7);

整数型の値が必要な場合は、型変換やstd::lroundstd::llroundなどを使用します。

.5の丸め方

std::roundでは、最も近い整数値へ丸められます。

小数部分がちょうど.5の場合は、ゼロから遠い方向へ丸められます。

std::round(1.5);   //  2.0
std::round(2.5);   //  3.0
std::round(-1.5);  // -2.0
std::round(-2.5);  // -3.0

負数の扱い

負数を扱う場合は、丸める方向に注意が必要です。

例えば、-1.5-1-2の中間にあります。

どちらも同じ距離ですが、ゼロから遠い-2が選ばれます。

std::round(-1.5);  // -2.0

std::roundの動作は、次のように整理できます。

最も近い整数値を選び、両側が同じ距離の場合はゼロから遠い側を選ぶ。

「切り上げ」という表現に注意

std::roundを「.5なら切り上げる」と説明すると、負数では誤解が生じる可能性があります。

例えば、std::ceilは正の無限大方向へ丸める関数です。

std::ceil(-1.5);   // -1.0
std::round(-1.5);  // -2.0

そのため、std::roundの中間値の処理は、次のように表現するのが適切です。

  • 中間値はゼロから遠い方向へ丸める
  • round half away from zero

整数を引数に渡した場合

std::roundには、整数型の値を渡すこともできます。

auto result = std::round(10);

この場合、整数値は浮動小数点型へ変換されたうえで処理されます。

一般的には、次のような処理と考えられます。

auto result = std::round(static_cast<double>(10));

結果は10.0で、型は通常doubleです。

もともと整数である値を丸めても結果は変わらないため、整数値に対してstd::roundを使用する必要は基本的にありません。

std::roundfstd::roundl

std::roundには、型を明示した関数も用意されています。

std::roundf(value);  // float用
std::roundl(value);  // long double用

使用例は次のとおりです。

float a = std::roundf(1.6f);
long double b = std::roundl(1.6L);

ただし、通常は引数の型に応じて適切なオーバーロードが選択されるため、std::roundを使用すれば十分です。

float f = 1.6f;
long double ld = 1.6L;

auto a = std::round(f);
auto b = std::round(ld);

std::lroundstd::llround

丸めた結果を整数型で受け取りたい場合は、std::lroundまたはstd::llroundを使用できます。

std::lround(value);   // longを返す
std::llround(value);  // long longを返す

使用例

long a = std::lround(2.5);          // 3
long b = std::lround(-2.5);         // -3

long long c = std::llround(2.5);    // 3
long long d = std::llround(-2.5);   // -3

丸め方はstd::roundと同じで、ちょうど中間の場合はゼロから遠い方向へ丸められます。

戻り値の範囲に注意

std::lroundstd::llroundは整数型を返すため、丸めた結果が戻り値の型に収まる必要があります。

例えば、次のようなコードでは、値がlongの範囲を超える可能性があります。

double value = 1.0e300;
long result = std::lround(value);

また、無限大やNaNは、通常の整数値として表現できません。

#include <cmath>
#include <limits>

std::lround(std::numeric_limits<double>::infinity());
std::lround(std::numeric_limits<double>::quiet_NaN());

このような値を渡すと、浮動小数点例外が発生する可能性があります。

std::lroundstd::llroundを使用する場合は、丸めた結果が戻り値型の範囲に収まることを確認する必要があります。

static_cast<int>で変換する場合

std::roundの結果をintへ変換することもできます。

int value = static_cast<int>(std::round(3.7));

この場合、value4になります。

ただし、丸めた値がintの範囲を超える場合は、安全に変換できません。

double value = 1.0e100;
int result = static_cast<int>(std::round(value));

このようなコードでは、変換結果が正しく定義されない可能性があります。

範囲を確認して変換する方法

整数型へ安全に変換したい場合は、NaNや無限大、変換先の範囲を事前に確認します。

#include <cmath>
#include <limits>
#include <optional>

std::optional<int> roundToInt(double value)
{
    if (!std::isfinite(value)) {
        return std::nullopt;
    }

    const double rounded = std::round(value);

    if (rounded < static_cast<double>(std::numeric_limits<int>::min()) ||
        rounded > static_cast<double>(std::numeric_limits<int>::max())) {
        return std::nullopt;
    }

    return static_cast<int>(rounded);
}

使用例は次のとおりです。

if (auto result = roundToInt(3.7)) {
    // *resultは4
}

変換先の範囲を超える可能性がある場合は、必ず事前に確認することが重要です。

floorceiltruncとの違い

std::roundと混同されやすい関数に、std::floorstd::ceilstd::truncがあります。

それぞれ丸める方向が異なります。

std::round

最も近い整数値へ丸めます。

中間値の場合は、ゼロから遠い方向へ丸めます。

std::round(3.7);   //  4.0
std::round(-3.7);  // -4.0
std::round(2.5);   //  3.0
std::round(-2.5);  // -3.0

std::floor

負の無限大方向へ丸めます。

std::floor(3.7);   //  3.0
std::floor(-3.7);  // -4.0

常に、引数以下の最大の整数値を返します。

std::ceil

正の無限大方向へ丸めます。

std::ceil(3.2);   //  4.0
std::ceil(-3.2);  // -3.0

常に、引数以上の最小の整数値を返します。

std::trunc

小数部分を取り除き、ゼロ方向へ丸めます。

std::trunc(3.7);   //  3.0
std::trunc(-3.7);  // -3.0

各関数の比較

入力roundfloorceiltrunc
3.23.03.04.03.0
3.74.03.04.03.0
-3.2-3.0-4.0-3.0-3.0
-3.7-4.0-4.0-3.0-3.0

特に負数を扱う場合は、「切り上げ」や「切り捨て」という言葉だけではなく、どの方向へ丸めるのかを確認する必要があります。

小数第N位で丸める方法

std::roundは、整数値への丸めを行う関数です。

小数第1位や小数第2位で丸める場合は、値を拡大してからstd::roundを使用し、最後に元の大きさへ戻します。

小数第1位で丸める

double value = 12.345;
double result = std::round(value * 10.0) / 10.0;

数学的には、小数第2位を基準として小数第1位まで丸める処理になります。

小数第2位で丸める

double value = 12.345;
double result = std::round(value * 100.0) / 100.0;

数学的な十進数として考えれば、結果は12.35です。

汎用的な関数の例

#include <cmath>

double roundToDigits(double value, int digits)
{
    const double scale = std::pow(10.0, digits);
    return std::round(value * scale) / scale;
}

使用例は次のとおりです。

double a = roundToDigits(12.3456, 2);
double b = roundToDigits(12.3456, 3);

ただし、この方法は十進数として完全に正確な丸めを保証するものではありません。

浮動小数点数の誤差

floatdoubleは、多くの十進小数を二進数で正確に表現できません。

例えば、1.005という値も、内部では正確な1.005ではなく、わずかに異なる値として保持される可能性があります。

double value = 1.005;
double result = std::round(value * 100.0) / 100.0;

数学的には1.01を期待する処理ですが、途中の浮動小数点誤差によって、期待と異なる結果になる場合があります。

内部の値を確認する方法

十分な桁数を指定して出力すると、内部で保持されている近似値を確認できます。

#include <iomanip>
#include <iostream>

int main()
{
    double value = 1.005;

    std::cout << std::setprecision(17)
              << value << '\n';

    std::cout << std::setprecision(17)
              << value * 100.0 << '\n';
}

std::round自体が誤った丸めを行っているのではなく、std::roundへ渡される時点で、値が理想的な十進数からずれている可能性があります。

roundToDigitsを使用する際の注意点

次の関数は、一般的な計算や表示用途では利用できます。

double roundToDigits(double value, int digits)
{
    const double scale = std::pow(10.0, digits);
    return std::round(value * scale) / scale;
}

ただし、次の問題が発生する可能性があります。

  • std::pow(10.0, digits)が大きすぎる場合にオーバーフローする
  • digitsが非常に小さい場合にアンダーフローする
  • value * scaleが浮動小数点型の範囲を超える
  • 二進浮動小数点誤差により、十進表記どおりに丸められない
  • NaNや無限大が渡される可能性がある

そのため、金融計算や請求計算など、十進数としての正確性が必要な処理には適していません。

金額を扱う場合の注意

金額など、十進単位で正確に管理する必要がある値をdoubleで保持すると、浮動小数点誤差が問題になる可能性があります。

例えば、ドルをセント単位の整数で保持する方法があります。

#include <cstdint>

// 12.34ドルを1234セントとして保持する
std::int64_t amountInCents = 1234;

日本円で1円未満を扱わない場合は、円単位の整数として保持できます。

std::int64_t amountInYen = 1234;

ただし、税率、利息、為替、単価などの計算では、最小通貨単位より細かい値が必要になる場合があります。

その場合は、次のような設計を検討します。

  • 計算上の最小単位を決めて整数で保持する
  • 固定小数点型を使用する
  • 十進数を扱えるライブラリを使用する
  • 丸めるタイミングと丸め規則を明確にする

std::roundと表示桁数の違い

std::roundは、値そのものを計算上丸める関数です。

double rounded = std::round(12.7);

一方、std::fixedstd::setprecisionは、出力時の表示形式を指定します。

#include <iomanip>
#include <iostream>

int main()
{
    double value = 12.3456;

    std::cout << std::fixed
              << std::setprecision(2)
              << value << '\n';
}

表示結果は、通常次のようになります。

12.35

ただし、変数valueの内部の値が12.35に変更されたわけではありません。

値を丸める場合

double value = 12.3456;
double rounded = std::round(value * 100.0) / 100.0;

表示だけを小数第2位までにする場合

std::cout << std::fixed
          << std::setprecision(2)
          << value;

std::setprecisionでは文字列へ変換する際に表示上の丸めが行われますが、元の変数の値は変更されません。

特殊な値の扱い

IEEE 754に対応する一般的な環境では、特殊な値は次のように扱われます。

std::round(+0.0);      // +0.0
std::round(-0.0);      // -0.0
std::round(+INFINITY); // +∞
std::round(-INFINITY); // -∞
std::round(NAN);       // NaN

無限大は無限大のまま、NaNはNaNのまま返されます。

負のゼロ

負の小数をゼロへ丸めた場合、結果が-0.0になることがあります。

double result = std::round(-0.4);

IEEE 754に対応する環境では、result-0.0になります。

数値比較では、通常の0.0と等しいと判定されます。

result == 0.0  // true

ただし、符号はstd::signbitで確認できます。

#include <cmath>

bool negative = std::signbit(result);

通常の処理では問題になることは少ないものの、数値計算では符号付きゼロの存在を考慮する必要がある場合があります。

浮動小数点の丸めモード

C++では、浮動小数点環境の丸めモードを変更できる場合があります。

#include <cfenv>

しかし、std::roundは現在設定されている丸めモードに依存しません。

常に、最も近い整数値へ丸め、中間値ではゼロから遠い方向を選びます。

#include <cfenv>
#include <cmath>

#pragma STDC FENV_ACCESS ON

int main()
{
    std::fesetround(FE_UPWARD);

    double a = std::round(1.4);   // 1.0
    double b = std::round(-1.5);  // -2.0
}

丸めモードに従う関数

現在の浮動小数点丸めモードに従って処理したい場合は、次の関数を使用できます。

std::nearbyint
std::rint
std::lrint
std::llrint

std::nearbyintstd::rintは、どちらも現在の丸めモードに従いますが、浮動小数点例外の扱いに違いがあります。

一般的には、次のように整理できます。

  • std::nearbyintは、通常、不正確例外を発生させない
  • std::rintは、不正確例外を発生させる可能性がある

浮動小数点環境はプログラム全体へ影響する可能性があるため、丸めモードの変更は慎重に行う必要があります。

最近接偶数丸めとの違い

std::roundは、最近接偶数丸めとは異なります。

最近接偶数丸めは、銀行丸めやround half to evenと呼ばれることがあります。

std::roundの場合

1.5 → 2
2.5 → 3
3.5 → 4
4.5 → 5

すべての中間値が、ゼロから遠い方向へ丸められます。

最近接偶数丸めの場合

1.5 → 2
2.5 → 2
3.5 → 4
4.5 → 4

中間値の場合、結果が偶数になる側を選びます。

そのため、会計処理や統計処理で「四捨五入」と指定されている場合でも、具体的な丸め規則を確認する必要があります。

std::chrono::roundとの違い

std::chrono::roundは、時間の長さを指定した単位へ丸める関数です。

数値用のstd::roundとは、中間値の扱いが異なります。

std::chrono::roundでは、下側と上側が同じ距離の場合、結果のカウント値が偶数になる側が選ばれます。

#include <chrono>

using namespace std::chrono_literals;

auto a = std::chrono::round<std::chrono::seconds>(1500ms);
auto b = std::chrono::round<std::chrono::seconds>(2500ms);

結果は次のようになります。

1.5秒 → 2秒
2.5秒 → 2秒

数値用のstd::roundでは、2.53.0になります。

std::round(2.5);  // 3.0

同じroundという名前でも、丸め規則が異なるため注意が必要です。

C++23以降のconstexpr

std::roundはC++11で追加された関数です。

C++23以降では、条件を満たす場合にコンパイル時評価が可能です。

#include <cmath>

constexpr double value = std::round(3.6);

static_assert(value == 4.0);

C++23としてコンパイルする場合は、次のように指定します。

g++ -std=c++23 main.cpp

ただし、関数がconstexprであっても、すべての引数やすべての状況でコンパイル時評価できるとは限りません。

定数評価中に許可されていない処理やエラー条件が含まれる場合は、定数式として成立しない可能性があります。

std::roundと整数型の範囲

std::roundは浮動小数点型を返すため、intlongの範囲を超える値でも、戻り値の浮動小数点型で表現できる場合は処理できます。

double value = 1.0e100;
double result = std::round(value);

非常に大きな浮動小数点値では、精度の制限により、もともと小数部分を表現できないことがあります。

その場合、値はすでに浮動小数点表現上の整数値になっています。

一方、std::lroundstd::llroundは整数型を返すため、戻り値の範囲に収まる必要があります。

関数戻り値範囲に関する注意
std::round浮動小数点型戻り値の浮動小数点型で表現できる必要がある
std::lroundlong結果がlongの範囲に収まる必要がある
std::llroundlong long結果がlong longの範囲に収まる必要がある

実用的なサンプル

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

double roundToDigits(double value, int digits)
{
    const double scale = std::pow(10.0, digits);
    return std::round(value * scale) / scale;
}

int main()
{
    const double value = -12.3456;

    const double integerRounded = std::round(value);
    const double twoDigitsRounded = roundToDigits(value, 2);
    const long long integerValue = std::llround(value);

    std::cout << "元の値: "
              << value << '\n';

    std::cout << "整数値への丸め: "
              << integerRounded << '\n';

    std::cout << "小数第2位への丸め: "
              << std::fixed
              << std::setprecision(2)
              << twoDigitsRounded << '\n';

    std::cout << "long longとして丸め: "
              << integerValue << '\n';
}

一般的な環境では、次のような結果になります。

元の値: -12.3456
整数値への丸め: -12
小数第2位への丸め: -12.35
long longとして丸め: -12

ただし、roundToDigitsは二進浮動小数点を利用しているため、すべての十進小数について期待どおりの結果を保証するものではありません。

まとめ

std::roundを使用する際に重要なポイントは、次のとおりです。

  • 最も近い整数値へ丸める
  • 中間値はゼロから遠い方向へ丸める
  • 戻り値は基本的に浮動小数点型
  • 負数では-1.5-2.0になる
  • 現在の浮動小数点丸めモードには依存しない
  • 整数型の結果が必要な場合はstd::lroundstd::llroundを利用できる
  • 整数型へ変換する場合は範囲確認が必要
  • 小数第N位への丸めでは浮動小数点誤差に注意する
  • 金額など正確な十進計算が必要な処理には、整数や固定小数点型などを検討する

基本的な動作は、次のコードで確認できます。

std::round(1.4);   //  1.0
std::round(1.5);   //  2.0
std::round(2.5);   //  3.0
std::round(-1.4);  // -1.0
std::round(-1.5);  // -2.0
std::round(-2.5);  // -3.0

以上、C++のstd::roundについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次