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

AI実装検定のご案内

std::floorは、指定した数値を負の無限大方向へ丸める関数です。

数学では床関数と呼ばれ、次のように表されます。

[
\lfloor x \rfloor
]

簡単にいうと、引数として渡した値以下にある整数のうち、最も大きい値を返します。

#include <cmath>
#include <iostream>

int main()
{
    double value = 3.8;
    double result = std::floor(value);

    std::cout << result << '\n';  // 3
}

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

#include <cmath>
目次

std::floorの基本的な使い方

基本構文

std::floorは、次のように使用します。

std::floor(数値)

代表的な使用例は次のとおりです。

double result = std::floor(3.8);

この場合、resultには3.0が格納されます。

正の数に対する動作

正の数にstd::floorを使用すると、小数部分を取り除いたような結果になります。

std::floor(3.1);  // 3.0
std::floor(3.5);  // 3.0
std::floor(3.9);  // 3.0
std::floor(4.0);  // 4.0

ただし、std::floorは四捨五入ではありません。

std::floor(3.9);  // 3.0

小数部分が.9であっても、結果は4.0ではなく3.0です。

負の数に対する動作

std::floorを理解するうえで、特に注意したいのが負の数です。

std::floor(-3.1);  // -4.0
std::floor(-3.5);  // -4.0
std::floor(-3.9);  // -4.0
std::floor(-4.0);  // -4.0

-3.1以下の整数は、次のようになります。

..., -6, -5, -4

この中で最も大きい整数は-4です。

そのため、次の結果になります。

std::floor(-3.1);  // -4.0

std::floorは、数値を常に負の無限大方向へ丸めます。

 3.8 →  3
-3.8 → -4

std::floorを「切り捨て」と考える際の注意点

単純な小数部分の削除ではない

std::floorは「小数点以下を切り捨てる関数」と説明されることがあります。

正の数だけを扱う場合は、この説明でも大きな問題はありません。

しかし、負の数では誤解が生じます。

単純に小数部分を削除した場合は、次の結果になります。

-3.8 → -3

一方、std::floorでは次の結果になります。

-3.8 → -4

そのため、より正確には次のように覚えるのが適切です。

std::floorは、数値を負の無限大方向へ丸める関数

ゼロ方向へ小数部分を取り除きたい場合は、std::truncを使用します。

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

std::floorの戻り値

戻り値は整数型ではない

std::floorは整数相当の値を返しますが、戻り値の型は基本的に浮動小数点型です。

double result = std::floor(3.8);

この場合、resultの値は3.0で、型はdoubleです。

auto result = std::floor(3.8);

autoを使用した場合も、resultは通常doubleになります。

値が整数のように見えても、intではない点に注意してください。

floatを渡した場合

float型の値を渡した場合は、対応する浮動小数点型の結果が返されます。

float value = 3.8f;
float result = std::floor(value);

long doubleを渡した場合

long double型にも対応しています。

long double value = 3.8L;
long double result = std::floor(value);

通常のC++コードでは、型に応じたオーバーロードが自動的に選ばれるため、基本的にはstd::floorを使用すれば十分です。

std::floor(3.8f);
std::floor(3.8);
std::floor(3.8L);

std::floorの結果を整数型へ変換する方法

static_castを使用する

std::floorの結果をintとして使用したい場合は、明示的に型変換します。

double value = 3.8;

int result = static_cast<int>(
    std::floor(value)
);

std::cout << result << '\n';  // 3

ただし、変換する値がintの表現範囲内に収まっている必要があります。

範囲外の変換に注意する

浮動小数点数から整数型へ変換するとき、値が変換先の整数型で表現できない場合、動作は未定義になります。

そのため、外部入力や大きな値を扱う場合は、変換前に範囲を確認する必要があります。

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

int floor_to_int(double value)
{
    const double floored = std::floor(value);

    if (floored < static_cast<double>(
            std::numeric_limits<int>::min()) ||
        floored > static_cast<double>(
            std::numeric_limits<int>::max())) {
        throw std::out_of_range(
            "std::floorの結果がintの範囲外です"
        );
    }

    return static_cast<int>(floored);
}

単純なサンプルでは直接変換しても問題にならない場合がありますが、実用コードでは値の範囲を考慮することが重要です。

std::floorと整数キャストの違い

正の数では同じ結果になることがある

正の値では、std::floorと整数型へのキャストが同じ数値になることがあります。

static_cast<int>(3.8);  // 3
std::floor(3.8);        // 3.0

ただし、戻り値の型は異なります。

static_cast<int>(3.8) → int
std::floor(3.8)       → double

負の数では結果が異なる

負の数では、両者の違いが明確になります。

static_cast<int>(-3.8);  // -3
std::floor(-3.8);        // -4.0

浮動小数点数から整数型への変換では、小数部分が破棄されます。

これはゼロ方向への丸めに相当します。

一方、std::floorは負の無限大方向へ丸めます。

std::floorstatic_cast<int>
3.83.03
3.13.03
-3.1-4.0-3
-3.8-4.0-3

std::floorと他の丸め関数の違い

std::truncとの違い

std::truncは、小数部分をゼロ方向へ取り除く関数です。

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

一方、std::floorは負の無限大方向へ丸めます。

std::floor(3.8);   // 3.0
std::floor(-3.8);  // -4.0
std::floorstd::trunc
3.83.03.0
-3.8-4.0-3.0

負の数を扱う場合は、目的に応じて使い分ける必要があります。

std::ceilとの違い

std::ceilは、引数以上の整数のうち、最も小さい値を返します。

std::ceil(3.1);   // 4.0
std::ceil(3.9);   // 4.0
std::ceil(-3.1);  // -3.0
std::ceil(-3.9);  // -3.0

std::floorstd::ceilでは、丸める方向が反対です。

関数丸める方向
std::floor負の無限大方向
std::ceil正の無限大方向
double value = -3.2;

std::floor(value);  // -4.0
std::ceil(value);   // -3.0

std::roundとの違い

std::roundは、最も近い整数値へ丸める関数です。

std::round(3.2);   // 3.0
std::round(3.5);   // 4.0
std::round(3.8);   // 4.0

std::round(-3.2);  // -3.0
std::round(-3.5);  // -4.0
std::round(-3.8);  // -4.0

値がちょうど中間にある場合は、ゼロから遠ざかる方向へ丸めます。

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

代表的な丸め関数を比較すると、次のようになります。

floorceiltruncround
3.23433
3.83434
-3.2-4-3-3-3
-3.8-4-3-3-4
2.52323
-2.5-3-2-2-3

整数型の値を渡した場合

整数はそのままの値になる

std::floorには、整数型の値も渡せます。

std::floor(10);   // 10.0
std::floor(-10);  // -10.0

整数はもともと小数部分を持たないため、値は変化しません。

auto result = std::floor(10);

一般的な整数型を渡した場合、戻り値は通常doubleになります。

ただし、整数値に対してstd::floorを使用しても値は変わらないため、通常は使用する必要がありません。

int value = 10;

// std::floorを使う必要はない
int result = value;

特殊な値に対する動作

無限大

IEEE 754形式の浮動小数点演算をサポートする一般的な環境では、無限大はそのまま返されます。

std::floor(
    std::numeric_limits<double>::infinity()
);

結果は正の無限大です。

負の無限大もそのまま返されます。

std::floor(
    -std::numeric_limits<double>::infinity()
);

NaN

NaNを渡した場合は、NaNが返されます。

double nan =
    std::numeric_limits<double>::quiet_NaN();

double result = std::floor(nan);

ただし、NaNは通常の値と等価比較できません。

result == result;  // falseになる

NaNかどうかを調べる場合は、std::isnanを使用します。

std::isnan(result);

負のゼロ

IEEE 754環境では、負のゼロを渡すと負のゼロが保持されます。

double result = std::floor(-0.0);

負のゼロかどうかは、std::signbitで確認できます。

#include <cmath>
#include <iostream>

int main()
{
    const double result = std::floor(-0.0);

    std::cout << std::boolalpha
              << std::signbit(result)
              << '\n';  // true
}

なお、すべてのC++実装が無限大、NaN、符号付きゼロを同じようにサポートするとは限りません。

これらの説明は、主にIEEE 754に準拠した一般的な環境を前提としています。

浮動小数点数の誤差に注意する

入力値が整数よりわずかに小さい場合

std::floor自体が誤った丸めを行うわけではありません。

ただし、浮動小数点演算の誤差によって、入力値が想定よりわずかに小さくなることがあります。

たとえば、数学上は3.0になるはずの計算結果が、実際には次の値になっている場合があります。

2.9999999999999996

この値にstd::floorを適用すると、結果は2.0です。

double value = 2.9999999999999996;
double result = std::floor(value);

// resultは2.0

std::floorから見ると、入力値は確かに3.0未満であるため、動作自体は正しいものです。

金額計算での注意点

次のような計算では、浮動小数点誤差の影響を受ける可能性があります。

double price = 0.29;
double scaled = price * 100.0;

int cents = static_cast<int>(
    std::floor(scaled)
);

scaledが数学上の29.0ではなく、わずかに小さい値として表現された場合、std::floorの結果は28.0になります。

そのため、金額の保存や単純な集計では、円やセントなどの最小通貨単位を整数で管理する方法が有効です。

#include <cstdint>

std::int64_t price_in_cents = 29;

ただし、税率、為替、利息、按分などを扱う場合は、業務上の端数処理ルールも明確にする必要があります。

イプシロンを加える方法の注意点

固定値を無条件に加えない

浮動小数点誤差への対策として、次のようなコードが使用されることがあります。

double result = std::floor(value + 1e-9);

しかし、固定のイプシロンを無条件に加える方法は、常に正しいとは限りません。

たとえば、次の値を考えます。

double value = 2.9999999995;

この値に1e-9を加えると、本来は3.0未満だった値が3.0以上になる可能性があります。

std::floor(value);        // 2.0
std::floor(value + 1e-9); // 3.0になる可能性

この処理が正しいかどうかは、値の意味や許容誤差によって異なります。

イプシロンを使用する場合は、次の点を明確にする必要があります。

  • 入力値の範囲
  • 許容する誤差
  • 境界値の扱い
  • 数値の単位
  • 整数演算へ置き換えられないか

小数点以下の桁数を指定して切り下げる方法

小数第1位まで残す

小数第1位まで残して、それより下の桁を負の無限大方向へ丸める場合は、10倍してからstd::floorを適用します。

double value = 12.345;

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

// 12.3

処理の流れは次のとおりです。

12.345 × 10
→ 123.45

floor(123.45)
→ 123

123 ÷ 10
→ 12.3

小数第2位まで残す

小数第2位まで残す場合は、100倍します。

double value = 12.345;

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

// 12.34

負数の場合

負数では、負の無限大方向へ丸められる点に注意してください。

double value = -12.345;

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

// 理想的な実数計算では-12.35

ゼロ方向へ小数部分を取り除きたい場合は、std::truncを使用します。

double value = -12.345;

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

// 理想的な実数計算では-12.34

汎用関数にする場合の注意点

次のように、桁数を指定する関数を作ることもできます。

#include <cmath>
#include <stdexcept>

double floor_to_decimal_places(
    double value,
    int places)
{
    if (!std::isfinite(value)) {
        return value;
    }

    const double scale =
        std::pow(10.0, places);

    if (!std::isfinite(scale) ||
        scale == 0.0) {
        throw std::out_of_range(
            "桁数が処理可能範囲外です"
        );
    }

    const double scaled = value * scale;

    if (!std::isfinite(scaled)) {
        throw std::overflow_error(
            "スケーリング中にオーバーフローしました"
        );
    }

    return std::floor(scaled) / scale;
}

ただし、この方法でも二進浮動小数点数の誤差は避けられません。

金融や会計など、厳密な十進小数処理が必要な場面では、整数演算や十進数型の利用を検討する必要があります。

配列やコンテナの要素へ適用する方法

範囲for文を使用する

複数の値にstd::floorを適用する場合は、範囲for文を使用できます。

#include <cmath>
#include <iostream>
#include <vector>

int main()
{
    const std::vector<double> values{
        1.2,
        2.8,
        -1.2,
        -2.8
    };

    for (double value : values) {
        std::cout
            << std::floor(value)
            << '\n';
    }
}

出力は次のようになります。

1
2
-2
-3

std::transformを使用する

結果を別のコンテナへ格納したい場合は、std::transformを使用できます。

#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>

int main()
{
    const std::vector<double> values{
        1.2,
        2.8,
        -1.2,
        -2.8
    };

    std::vector<double> results(
        values.size()
    );

    std::transform(
        values.begin(),
        values.end(),
        results.begin(),
        [](double value) {
            return std::floor(value);
        }
    );

    for (double result : results) {
        std::cout << result << '\n';
    }
}

C++23以降のconstexpr対応

コンパイル時に評価できる

C++23以降では、std::floorを定数式として利用できます。

#include <cmath>

constexpr double result =
    std::floor(3.8);

static_assert(result == 3.0);

C++20以前では、標準ライブラリのstd::floorを定数式として利用できるとは限りません。

C++23の機能として使用する場合は、コンパイル時にC++23モードを指定します。

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

実際の対応状況は、使用しているコンパイラと標準ライブラリによって異なる場合があります。

std::chrono::floorとの違い

数値用のstd::floor

<cmath>に定義されているstd::floorは、数値を負の無限大方向へ丸めます。

std::floor(3.8);  // 3.0

時間用のstd::chrono::floor

C++17以降では、時間型を指定した単位へ丸めるstd::chrono::floorも使用できます。

#include <chrono>

using namespace std::chrono_literals;

auto duration = 1250ms;

auto seconds =
    std::chrono::floor<
        std::chrono::seconds
    >(duration);

// 1秒

負の時間では、次の結果になります。

auto duration = -1250ms;

auto seconds =
    std::chrono::floor<
        std::chrono::seconds
    >(duration);

// -2秒

-1.25秒以下で表現可能な最大の整数秒は-2秒だからです。

両者は名前が似ていますが、名前空間と用途が異なります。

std::floor(value);

std::chrono::floor<
    std::chrono::seconds
>(duration);

std::floorの実用例

座標からグリッド番号を求める

ゲーム、地図、シミュレーションなどでは、連続した座標をグリッド番号へ変換するためにstd::floorを使用できます。

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

int coordinate_to_grid(
    double coordinate,
    double cell_size)
{
    if (!std::isfinite(coordinate) ||
        !std::isfinite(cell_size) ||
        cell_size <= 0.0) {
        throw std::invalid_argument(
            "座標またはセル幅が不正です"
        );
    }

    const double index =
        std::floor(
            coordinate / cell_size
        );

    if (index <
            static_cast<double>(
                std::numeric_limits<int>::min()
            ) ||
        index >
            static_cast<double>(
                std::numeric_limits<int>::max()
            )) {
        throw std::out_of_range(
            "グリッド番号がintの範囲外です"
        );
    }

    return static_cast<int>(index);
}

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

coordinate_to_grid(25.0, 10.0);  // 2
coordinate_to_grid(-2.0, 10.0);  // -1

負数では、整数キャストと結果が異なります。

static_cast<int>(-2.0 / 10.0);  // 0
std::floor(-2.0 / 10.0);        // -1.0

原点より左側の座標をグリッド-1へ割り当てたい場合は、std::floorが適しています。

一定間隔へ切り下げる

数値を一定の間隔へ切り下げる場合にも利用できます。

#include <cmath>
#include <stdexcept>

double floor_to_step(
    double value,
    double step)
{
    if (!std::isfinite(value) ||
        !std::isfinite(step) ||
        step <= 0.0) {
        throw std::invalid_argument(
            "valueとstepには有効な値を指定してください"
        );
    }

    return std::floor(value / step)
        * step;
}

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

floor_to_step(27.0, 5.0);  // 25.0
floor_to_step(29.9, 5.0);  // 25.0
floor_to_step(30.0, 5.0);  // 30.0

負数では、負の無限大方向へ丸められます。

floor_to_step(-1.0, 5.0);  // -5.0

ゼロ方向へ丸めたい場合とは結果が異なるため、要件を確認して使用する必要があります。

区間や階級へ分類する

実数値を一定幅の区間へ分類する場合にも、std::floorが利用できます。

double value = 27.5;
double interval = 10.0;

int group = static_cast<int>(
    std::floor(value / interval)
);

// 2

この場合、値は次のように分類できます。

0以上10未満   → 0
10以上20未満  → 1
20以上30未満  → 2

負の値を含む場合も、一貫して負の無限大方向の区間へ分類されます。

std::floorのよくある間違い

<cmath>をインクルードしていない

std::floorを使用する場合は、通常<cmath>が必要です。

#include <cmath>

名前空間を付けていない

C++では、次のようにstd::を付けて呼び出すのが基本です。

std::floor(3.8);

環境によってはfloor(3.8)でも動作することがありますが、標準名前空間を明示したほうが安全です。

戻り値をintだと思っている

次の変数は通常doubleです。

auto value = std::floor(3.8);

整数型として使用する場合は、範囲を確認したうえで変換します。

int value = static_cast<int>(
    std::floor(3.8)
);

負数でも小数部分だけが消えると思っている

次の結果は-3.0ではありません。

std::floor(-3.8);  // -4.0

ゼロ方向へ小数部分を取り除きたい場合は、std::truncを使用します。

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

整数型への変換でも、結果はゼロ方向になります。

static_cast<int>(-3.8);  // -3

金額を安易にdoubleで処理している

次のような処理は、浮動小数点誤差の影響を受ける可能性があります。

std::floor(price * 100.0);

金額を扱う場合は、最小通貨単位の整数で管理する方法や、業務ルールに対応した十進演算の利用を検討してください。

整数除算で済む処理に使用している

ページ番号や件数の計算など、最初から整数だけで処理できる場合は、std::floorを使う必要はありません。

std::size_t page =
    item_index / items_per_page;

整数除算で要件を満たせる場合は、浮動小数点数へ変換しないほうが簡潔で安全です。

std::floorについてのまとめ

std::floorは、引数を負の無限大方向へ丸める関数です。

#include <cmath>

double result = std::floor(value);

代表的な結果は次のとおりです。

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

各丸め関数の違いは、次のように整理できます。

std::floor
→ 負の無限大方向へ丸める

std::ceil
→ 正の無限大方向へ丸める

std::trunc
→ ゼロ方向へ丸める

std::round
→ 最も近い整数へ丸める

std::floorは、座標からグリッド番号を求める処理、値を区間へ分類する処理、一定間隔への切り下げなどに適しています。

一方、次の点には注意が必要です。

  • 戻り値は整数型ではなく浮動小数点型
  • 負数では単純な小数部分の削除にならない
  • 整数型へ変換する場合は範囲確認が必要
  • 浮動小数点誤差の影響を受ける可能性がある
  • 厳密な金額計算や十進小数計算には適さない場合がある

特に負の数を扱う場合は、std::floorstd::trunc、整数キャストの違いを理解したうえで使い分けることが重要です。

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

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

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