C++で学ぶ9種類のdouble型の最大値と精度の限界

C言語でダブル型を学ぶ初心者の手引きC言語
この記事は約17分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●C++のdouble型とは?

C++において、double型は浮動小数点数を表現するための重要なデータ型の1つです。

浮動小数点数は、整数部と小数部を持つ実数値を表現することができ、科学技術計算やグラフィックスプログラミングなど、幅広い分野で活用されています。

○double型の基本概念

double型は、倍精度浮動小数点数(double-precision floating-point number)を表現するためのデータ型です。

この型は、IEEE 754規格に基づいて定義されており、64ビットの記憶領域を使用します。

double型の値は、非常に広い範囲の実数値を表現することができ、C++における数値計算の中心的な役割を担っています。

double型の値は、次のような形式で表現されます。

±m×2e

ここで、𝑚は仮数(mantissa)、𝑒は指数(exponent)を表します。

仮数部は小数点以下の桁を表し、指数部は2のべき乗を表します。

この表現方式により、double型は非常に広い範囲の値を効率的に表現することができるのです。

○サンプルコード1:基本的なdouble型の使用法

それでは、実際にdouble型を使ったサンプルコードを見てみましょう。

下記のコードは、double型の変数を宣言し、値を代入して表示するシンプルな例です。

#include <iostream>
using namespace std;

int main() {
    double x = 3.14159265358979323846;
    double y = 2.71828182845904523536;

    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    cout << "x + y = " << x + y << endl;

    return 0;
}

実行結果↓

x = 3.14159
y = 2.71828
x + y = 5.85987

ご覧の通り、double型の変数xとyに実数値を代入し、それらの値を出力しています。

また、x + yのように、double型の変数同士の演算も可能です。

●double型の最大値と精度の限界

C++のdouble型は非常に広い範囲の値を表現できますが、無限大ではありません。

double型にも最大値と精度の限界があるのです。

これらの限界を理解することは、正確で信頼性の高いプログラムを書くために欠かせません。

○double型で表現できる最大値

double型で表現できる最大値は、約1.7976931348623157 × 10^308です。

この値は、std::numeric_limitsクラスを使用することで取得できます。

#include <iostream>
#include <limits>
using namespace std;

int main() {
    cout << "double型の最大値: " << numeric_limits<double>::max() << endl;
    return 0;
}

実行結果↓

double型の最大値: 1.79769e+308

この最大値を超えた場合、オーバーフローが発生します。

オーバーフローが起きると、予期しない結果が生じる可能性があるため、注意が必要です。

○サンプルコード2:最大値を超えたときの挙動

では、double型の最大値を超えるとどうなるのでしょうか?

下記のサンプルコードを見てみましょう。

#include <iostream>
#include <limits>
using namespace std;

int main() {
    double max_val = numeric_limits<double>::max();
    double overflow_val = max_val * 2;

    cout << "最大値: " << max_val << endl;
    cout << "オーバーフロー値: " << overflow_val << endl;

    return 0;
}

実行結果↓

最大値: 1.79769e+308
オーバーフロー値: inf

ご覧の通り、最大値の2倍の値を計算しようとすると、”inf”(無限大)が出力されました。

これは、オーバーフローが発生したことを表しています。

オーバーフローが起きると、値が正しく表現できなくなるため、プログラムの動作が不安定になる可能性があります。

○double型での精度の限界

double型は広い範囲の値を表現できますが、無限の精度を持っているわけではありません。

double型の精度は、約15〜17桁です。

つまり、double型では小数点以下15〜17桁までの値を正確に表現できるということです。

この精度の限界は、浮動小数点数の表現方式に起因しています。

double型では、値を仮数部と指数部に分けて表現するため、無限の精度を持つことはできないのです。

○サンプルコード3:精度の限界を表すプログラム

double型の精度の限界を実際に確認してみましょう。

下記のサンプルコードは、小数点以下の桁数を増やしながら値を出力するプログラムです。

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    double x = 0.1;

    cout << "小数点以下15桁: " << setprecision(15) << x << endl;
    cout << "小数点以下16桁: " << setprecision(16) << x << endl;
    cout << "小数点以下17桁: " << setprecision(17) << x << endl;
    cout << "小数点以下18桁: " << setprecision(18) << x << endl;

    return 0;
}

実行結果↓

小数点以下15桁: 0.100000000000000
小数点以下16桁: 0.1000000000000000
小数点以下17桁: 0.10000000000000001
小数点以下18桁: 0.100000000000000006

小数点以下15桁までは正確に表現されていますが、16桁目以降は誤差が生じていることがわかります。

この誤差は、double型の精度の限界によるものです。

●プログラミングにおけるdouble型の応用例

C++のdouble型は、その高い精度と広い表現範囲により、様々な分野で活用されています。

ここでは、数値計算とグラフィックスプログラミングにおけるdouble型の応用例を見ていきましょう。

○サンプルコード4:数値計算におけるdouble型の利用

数値計算は、科学技術分野で広く用いられており、高い精度が求められます。

下記のサンプルコードは、double型を使用して、sin関数とcos関数の値を計算する例です。

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    double angle_deg = 45.0;  // 角度(度)
    double angle_rad = angle_deg * M_PI / 180.0;  // 角度(ラジアン)

    double sin_val = sin(angle_rad);
    double cos_val = cos(angle_rad);

    cout << "角度: " << angle_deg << "度" << endl;
    cout << "sin(" << angle_deg << "): " << sin_val << endl;
    cout << "cos(" << angle_deg << "): " << cos_val << endl;

    return 0;
}

実行結果↓

角度: 45度
sin(45): 0.707107
cos(45): 0.707107

このコードでは、角度をdouble型の変数angle_degに格納し、それをラジアンに変換してangle_radに代入しています。

そして、sin関数とcos関数を使用して、角度に対応するsin値とcos値を計算しています。

数値計算では、double型の高い精度が重要な役割を果たします。

角度の微小な変化も正確に捉えることができるため、より正確な計算結果を得ることができるのです。

○サンプルコード5:グラフィックスプログラミングにおけるdouble型の使用

グラフィックスプログラミングにおいても、double型は重要な役割を担っています。

下記のサンプルコードは、double型を使用して、2次元座標上の点の位置を表現する例です。

#include <iostream>
using namespace std;

struct Point {
    double x;
    double y;
};

int main() {
    Point p1 = {1.5, 2.8};
    Point p2 = {4.2, 3.7};

    double distance = sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2));

    cout << "点P1: (" << p1.x << ", " << p1.y << ")" << endl;
    cout << "点P2: (" << p2.x << ", " << p2.y << ")" << endl;
    cout << "2点間の距離: " << distance << endl;

    return 0;
}

実行結果↓

点P1: (1.5, 2.8)
点P2: (4.2, 3.7)
2点間の距離: 2.86356

このコードでは、Point構造体を定義し、double型のメンバ変数xとyを使用して、2次元座標上の点の位置を表現しています。

そして、2点間の距離を計算するために、ピタゴラスの定理を使用しています。

グラフィックスプログラミングでは、オブジェクトの位置や大きさを正確に表現することが重要です。

double型を使用することで、座標の微小な変化も正確に捉えることができ、より滑らかで自然なグラフィックスを実現できるのです。

●よくあるエラーと対処法

C++のdouble型を使用する際、私たちはしばしば予期せぬエラーに遭遇することがあります。

これらのエラーは、プログラムの動作に大きな影響を与える可能性があるため、適切に対処することが重要です。

ここでは、浮動小数点数に関連するよくあるエラーとその理由、そして対処法について解説します。

○浮動小数点のエラーとその理由

浮動小数点数を扱う際に発生するエラーの多くは、浮動小数点数の表現方式に起因しています。

double型の値は、仮数部と指数部に分けて表現されるため、ある種の数値は正確に表現できないのです。

例えば、0.1という値は、2進数では正確に表現できません。

そのため、double型で0.1を表現しようとすると、わずかな誤差が生じます。

この誤差は、計算を繰り返すことで蓄積し、予期せぬ結果につながる可能性があります。

○サンプルコード6:エラー処理の実装例

では、浮動小数点数のエラーにどのように対処すればよいのでしょうか?

下記のサンプルコードは、誤差を考慮した比較の方法を表しています。

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    double a = 0.1 + 0.2;
    double b = 0.3;

    cout << "a = " << a << endl;
    cout << "b = " << b << endl;

    if (a == b) {
        cout << "a と b は等しい" << endl;
    } else {
        cout << "a と b は等しくない" << endl;
    }

    if (abs(a - b) < 1e-9) {
        cout << "a と b は等しい(誤差を考慮)" << endl;
    } else {
        cout << "a と b は等しくない(誤差を考慮)" << endl;
    }

    return 0;
}

実行結果↓

a = 0.3
b = 0.3
a と b は等しくない
a と b は等しい(誤差を考慮)

このコードでは、0.1 + 0.2と0.3を比較しています。通常の等価比較(==)では、わずかな誤差のために等しくないと判定されます。

しかし、abs関数を使用して二つの値の差の絶対値を計算し、それが一定の閾値(ここでは1e-9)より小さいかどうかを判定することで、誤差を考慮した比較を行うことができます。

○サンプルコード7:より安全なdouble型の利用方法

浮動小数点数のエラーを避けるためには、可能な限り整数型を使用することが有効です。

下記のサンプルコードは、金額の計算にdouble型ではなく整数型を使用する例です。

#include <iostream>
using namespace std;

int main() {
    int price = 1000;  // 価格(円)
    int quantity = 3;  // 数量

    int total = price * quantity;  // 合計金額(円)

    cout << "価格: " << price << "円" << endl;
    cout << "数量: " << quantity << endl;
    cout << "合計金額: " << total << "円" << endl;

    return 0;
}

実行結果↓

価格: 1000円
数量: 3
合計金額: 3000円

このコードでは、金額をdouble型ではなくint型で表現しています。

金額を整数(円)で扱うことで、浮動小数点数の誤差を回避することができます。

ただし、整数型を使用する場合は、小数点以下の情報が失われることに注意が必要です。

状況に応じて、適切なデータ型を選択することが大切でしょう。

●double型の高度なテクニックと最適化

C++のdouble型を使いこなすには、その基本的な概念や応用例を理解するだけでは不十分です。

より高度なテクニックを身につけ、プログラムのパフォーマンスを最適化することが重要でしょう。

ここでは、メモリ効率の良いdouble型の利用法と計算速度の最適化テクニックについて解説します。

○サンプルコード8:メモリ効率の良いdouble型の利用法

大規模なプログラムでは、メモリ使用量が重要な問題となります。

下記のサンプルコードは、double型の配列をメモリ効率良く利用する方法を表しています。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // double型の配列を動的に確保
    int n = 1000000;  // 配列の要素数
    vector<double> arr(n);

    // 配列の要素を初期化
    for (int i = 0; i < n; i++) {
        arr[i] = i * 0.1;
    }

    // 配列の要素を処理
    double sum = 0.0;
    for (int i = 0; i < n; i++) {
        sum += arr[i];
    }

    cout << "合計: " << sum << endl;

    return 0;
}

このコードでは、vectorクラスを使用してdouble型の配列を動的に確保しています。

vectorクラスを使用することで、必要なメモリを動的に割り当てることができ、メモリ使用量を最小限に抑えることができます。

また、配列の要素を処理する際には、ループ内で不必要な計算を避けることが重要です。

上記のコードでは、配列の要素を単純に加算しているだけですが、より複雑な処理を行う場合は、ループ内の計算を最適化することでメモリ効率を改善できるでしょう。

○サンプルコード9:計算速度を最適化するテクニック

数値計算の速度は、プログラムのパフォーマンスに大きな影響を与えます。

下記のサンプルコードは、計算速度を最適化するテクニックの一つである、一時変数の活用方法を表しています。

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    int n = 1000000;  // 計算の反復回数

    // 一時変数を使用しない場合
    double result1 = 0.0;
    for (int i = 0; i < n; i++) {
        result1 += sqrt(i) * sqrt(i);
    }

    // 一時変数を使用する場合
    double result2 = 0.0;
    for (int i = 0; i < n; i++) {
        double temp = sqrt(i);
        result2 += temp * temp;
    }

    cout << "一時変数を使用しない場合: " << result1 << endl;
    cout << "一時変数を使用する場合:   " << result2 << endl;

    return 0;
}

このコードでは、sqrt関数を使用して平方根を計算し、その結果を二乗しています。

一時変数を使用しない場合、sqrt関数が各ループで2回呼び出されますが、一時変数を使用することで、sqrt関数の呼び出しを1回に減らすことができます。

一時変数の活用は、計算速度の最適化に効果的なテクニックの一つです。

複雑な計算を行う場合、一時変数を適切に使用することで、関数の呼び出し回数を減らし、計算速度を向上させることができるでしょう。

ただし、一時変数の使用はメモリ使用量を増加させる可能性があるため、メモリ効率とのバランスを考慮する必要があります。

状況に応じて、適切な最適化手法を選択することが重要です。

まとめ

C++のdouble型について、その基本概念から応用例、エラー対処法、高度なテクニックまで、幅広く解説してきました。

double型の特性を理解し、適切に使いこなすことは、C++プログラマにとって欠かせないスキルです。

本記事で紹介したサンプルコードを通じて、double型の様々な側面を体験的に学ぶことができたのではないでしょうか。

精度の限界やエラー処理など、double型特有の問題にも対処する方法を身につけることができたと思います。

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