浮動小数点を利用する際に知っておきたいこと

Last Update: feedback 共有

(※ 2014 年 10 月 28 日に Microsoft Japan Visual Studio Support Team Blog に公開した情報のアーカイブです。)
(※ 参照先ドキュメントの変更や廃止に伴い、リンク先を差し替えています。)

こんにちは。Visual Studio サポート チームです。

今回は、Visual Studio でアプリケーションを開発する時にデータ型として使用できる浮動小数点数についてのお話です。
本記事の内容は Visual C++、Visual C#、Visual Basic を対象としております。

浮動小数点数を使用する際の注意点

浮動小数点数はプログラムで小数を扱うために広く利用されていますが、少し癖のあるデータ型でもあるため、その特性をよく理解して利用しないと、思わぬ落とし穴に遭遇してしまうこともあります。
浮動小数点の特性として、注意した方が良いものは主に以下の 3 点となります。

a) 浮動小数点数の演算に固有の誤差が常に生じる可能性がある。

b) ビルド環境やオプションによって、同一のソースコードでも計算結果が変わる可能性がある。

c) 実行する CPU が変われば、同一の実行モジュールでも計算結果が変わる可能性がある。

a) については丸め誤差、桁落ち、情報落ちといった現象を考慮する必要があります。
情報工学の入門で必ず扱われるトピックのため、ご存じの開発者の方も多いかと思います。

一方、b) については、弊社サポートにお問い合わせいただくことが多いトピックです。
コンパイラなどツールのバージョン、Debug / Release モード、コード最適化、対象プラットフォーム、拡張命令セット等の設定により、同一のソースコードをビルドした場合でも、生成されるプログラムが機械語レベルで異なることがあるため、浮動小数点数演算に至るまでの処理内容や演算回数等が変化し、演算結果に影響する可能性がある点に注意が必要となります。

さらに、c) のように演算処理を実行する CPU や FPU の実装が異なれば、同一の実行モジュールであっても、演算結果が異なる可能性があります。

このような特性があるため、厳密な計算が求められる金額計算などに浮動小数点数を利用する際は、特に注意が必要となります。
浮動小数点数の演算結果を完全に一致させることを保証する方法はないため、以下の 2 点に注意して設計・プログラミングを行う必要があります。

方針1 : 浮動小数点数の演算結果については有効桁数の範囲で評価する

方針2 : それでも演算誤差の最小化が必要な場合は 10進形式の型の利用を検討する

小数計算を行う場合の設計・プログラミング方針

1). 浮動小数点数の演算結果については有効桁数の範囲で評価する
浮動小数点数を扱うデータ型には、有効桁数が定められています。
例えば、.NET Framework の float 型の場合、有効桁数 7 桁、double 型なら有効桁数は 15 ~ 16 桁程度と定義されています。

浮動小数点数の特性上、有効桁数以上の桁数に対する演算結果は不定であり、この部分を評価対象とするようなシステムの設計やプログラミングは避ける必要があります。
例えば、小数値の一致を評価したい場合、以下のコードでは、前者の比較演算子 “==” を使用する方法では、通常、意図した結果を得られません。

このため、後者のように誤差の発生を考慮した比較で代用する必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
Double val_1 = 0.1 + 0.2 + 0.3;
Double val_2 = 0.6;
const Double Eps = 0.000000000000001;
// 不適切な比較方法
if (val_1 == val_2)
{
Console.WriteLine("変数 val_1 と val_2 は等しい");
}
// 誤差を考慮した比較方法
if (Math.Abs(val_1 - val_2) < Eps)
{
Console.WriteLine("変数 val_1 と val_2 はほぼ等しい");
}

この他、整数への変換では、Math.Round、Math.Floor、Math.Ceiling といったメソッドや、整数型へのキャストを利用することができますが、それそれの動作を理解した上で、目的に合ったものをご利用ください。

2). それでも演算誤差の最小化が必要な場合は 10 進形式の型を利用を検討する
.NET Framework では、Decimal (10 進) 型が用意されております。
Decimal 型でも演算誤差は発生しますが、データの表現の仕方や演算の工夫により、通常の浮動小数点数と比較して誤差が発生しにくい特徴があります。

Decimal 型の詳細と利用方法については以下のドキュメントを参照してください。

Decimal 構造体
https://docs.microsoft.com/ja-jp/dotnet/api/system.decimal

なお、.NET Framework を利用せず、Visual C++ を利用される場合には、10 進形式の型は標準では提供しておりません。
Visual C++ で精度の高い 10 進数の演算を行いたい場合には、2 進化 10 進数 (BCD: Binary Coded Decimal) ライブラリなどを入手し、ご利用いただくといった方法がございます。

【コラム : 浮動小数点の演算にて微少な誤差がでるのはどうして?】

浮動小数点にて誤差が発生する要因は、まず根本的に、コンピューターが演算に使用する数値が 2 進数で管理されており、10 進数の小数が必ずしも 2 進数で正確に表現できない、という制限によるものです。
さらに、CPU の演算命令自体の性質や順序、コンパイラの最適化処理による影響等、複数の要因が関連して、10 進数のままの演算で考えた場合には同様の値が得られると期待される状況でも、実際にコンピューターの演算結果では誤差が発生する可能性があります。

この現象は Visual C++、Visual C#、Visual Basic の問題や制限によって生じているものではなく、浮動小数点で広く採用されている IEEE 浮動小数点表現に関する規格に則り、数値をバイナリ形式で格納していることから生じています。
例えば 10 進の 0.5 が、2 進では 0.1、10 進の 0.75 が 2 進では 0.11 と表されます。
2 進数の小数点以下の各ビット (0.1、0.01、0.001、0.0001 …) の重みは 10 進数の、 1/2、1/4、1/8、1/16 … に対応しており、10 進の小数を 2 進で表現する際は、これら各ビットの組み合わせによる近似値として表されます。

従いまして、全体のビット数を増やすことにより、10 進数に対する近似の精度を高くすることは可能ですが、2 進数で完全には表せない 10 進の小数の場合、無限小数となり、データ型がもつ精度以上の桁は切り捨てられます。

ご参考までに、以下に一般的な数値からの具体例を 2 つ記載させていただきます。

例 1) 0.05 を 2 進数で表すと、 0.0000110011001100… のような循環小数になり、途中で切り捨てられるため、演算処理をする際に誤差が生じます。

例 2) 0.3333… のように 10 進数で表現しても小数点以下の桁が無限になる数値は、2 進数で表現した場合も無限小数となり、途中で切り捨てられます。

IEEE 浮動小数点表現の規格は、Intel 系 CPU (Central Processing Unit : 中央演算処理装置) のコプロセッサ (数値演算プロセッサ) や、浮動小数点演算を行う、PC ベースのほとんどのプログラムで用いられています。

そのため、IEEE 浮動小数点表現の規格を採用しているコンピューターでは、浮動小数点の演算時は非常に微細な単位での差異が常に発生する可能性があります。

<参考情報>

浮動小数点数の精度の低下
https://docs.microsoft.com/ja-jp/cpp/build/why-floating-point-numbers-may-lose-precision

IEEE 浮動小数点表現
https://docs.microsoft.com/ja-jp/cpp/build/ieee-floating-point-representation


本ブログの内容は弊社の公式見解として保証されるものではなく、開発・運用時の参考情報としてご活用いただくことを目的としています。もし公式な見解が必要な場合は、弊社ドキュメント (https://docs.microsoft.comhttps://support.microsoft.com) をご参照いただくか、もしくは私共サポートまでお問い合わせください。