FLP03-C. 浮動小数点エラーを検知して処理する
浮動小数点演算中のエラーは、プログラマが演算前のオペランドの検証を重視しているときには見過ごされがちである。浮動小数点演算中のエラーは特定しにくく診断も難しいが、多くの場合、エラーの特定と診断の効果はそのためのコストを上回る。このレコメンデーションでは、浮動小数点演算中のエラーを捕捉する方法を提示する。
次のコードは未定義の動作を引き起こす。
int j = 0; int iResult = 1 / j;
ほとんどの処理系では、整数のゼロ除算は回復不能なエラーとなり、一般的には診断メッセージが出力されプログラムが停止する。
double x = 0.0; double dResult = 1 / x;
浮動小数点数のゼロ除算も未定義の動作を引き起こすことがあるが、ほとんどの処理系ではこれを回復不能なエラーとは扱わない。追加の対策を講じておかなければ、これはメッセージを発しないエラーになる。
浮動小数点エラーの発生を判別するための最も可搬性のある方法として、C99 によって fenv.h で規定されている浮動小数点例外の機能を使用することができる[ISO/IEC 9899:1999]。
ただし、C99 の浮動小数点例外関数にも問題はある。浮動小数点の例外と型変換の相互作用に関連して次の注意点がある。
- 浮動小数点数から整数への変換によって、「無効な」浮動小数点例外が発生する。これが発生した場合、その整数の値は未定義となり、使用するべきではない。
- ほとんどの処理系では、任意の負の浮動小数点値、または「大きな」正の浮動小数点値から符号無し integer 型や符号付き char 型への変換を「無効」と報告しない(tflt2int.c を参照)。
- 整数でない浮動小数点値を整数に変換すると、「不正確(inexact)」という浮動小数点例外が発生する。
浮動小数点数の型変換については、「FLP34-C. 浮動小数点の型変換が新しい型の範囲に収まるようにする」を参照のこと。
C99 は、すべての処理系に対して浮動小数点例外の使用を義務付けてはいない。fenv.h の中の例外マクロはすべて、対応する例外が使用できる場合に限り定義される。IEC 60559(以前の IEEE-754)浮動小数点演算を使用する処理系に限り、C99 で定義されている 5 つすべての例外を使用できる必要がある。それでも、これらの関数は浮動小数点例外処理用の最も可搬性の高い解決法である。
可搬性は損なわれるが安全性は高くなる可能性のある解決法は、その処理系が提供する機能を使用することである。この方法を採用する場合、そのシステムに関する注意点をよく理解しておく必要がある。表 6-1 は一般的なオペレーティングシステムの基本的な注意点をまとめたものである。
| オペレーティングシステム | 浮動小数点エラーの処理方法 |
|---|---|
|
Linux Solaris 10 AIX 5.3 HP-UX 11.31 Mac OS X 10.5 |
C99 の浮動小数点例外関数を使用する。 |
| Windows | C99 の浮動小数点例外関数、または _fpieee_flt [MSDN] が提供している構造化例外処理を使用する。 |
違反コード
以下のコード例では、エラーを検査せずに、浮動小数点演算を実行している。浮動小数点演算後にエラーを検出するように書かれているため、範囲の検査は意図的に省略されている。
void fpOper_noErrorChecking(void) {
/* ... */
double a = 1e-40, b, c = 0.1;
float x = 0, y;
/* 不正確でアンダーフロー */
y = a;
/* ゼロ演算で除算 */
b = y / x;
/* 不正確(精度低下) */
c = sin(30) * a;
/* ... */
}
しかし、例外状態(コメント部分)が発生し、予期せぬ演算結果をもたらすことがある。
適合コード (C99)
以下の解決法では、C99 の浮動小数点エラー処理用の標準関数を使用している。
#include <fenv.h>
#pragma STDC FENV_ACCESS ON
void fpOper_fenv(void) {
double a = 1e-40, b, c = 0.1;
float x = 0, y;
int fpeRaised;
/* ... */
feclearexcept(FE_ALL_EXCEPT);
/* a を y に格納することは不正確でアンダーフロー: */
y = a;
fpeRaised = fetestexcept(FE_ALL_EXCEPT);
/* fpeRaised には FE_INEXACT と FE_UNDERFLOW が設定される */
feclearexcept(FE_ALL_EXCEPT);
/* ゼロ除算 */
b = y / x;
fpeRaised = fetestexcept(FE_ALL_EXCEPT);
/* fpeRaised には FE_DIVBYZERO が設定される */
feclearexcept(FE_ALL_EXCEPT);
c = sin(30) * a;
fpeRaised = fetestexcept(FE_ALL_EXCEPT);
/* fpeRaised には FE_INEXACT が設定される */
feclearexcept(FE_ALL_EXCEPT);
/* ... */
}
適合コード (Windows)
Microsoft Visual Studio 2008 およびそれ以前のバージョンでは、浮動小数点エラーを処理する C99 関数を使用できない。Windows は、_statusfp()、_statusfp2()、および _clearfp() を使用した別の方法を提供している。
void fpOper_usingStatus(void) {
/* ... */
double a = 1e-40, b, c;
float x = 0, y;
unsigned int rv = _clearfp();
/* y に格納することは不正確でアンダーフロー: */
y = a;
rv = _clearfp(); /* rv には _SW_INEXACT と _SW_UNDERFLOW が設定される */
/* ゼロ除算 */
b = y / x; rv = _clearfp(); /* rv には _SW_ZERODIVIDE が設定される */
/* 不正確 */
c = sin(30) * a; rv = _clearfp(); /* rv には _SW_INEXACT が設定される */
/* ... */
}
適合コード (Windows 構造化例外処理)
Microsoft Visual Studio 2008 では、浮動小数点演算処理用に構造化例外処理(SEH)も使用している。SEH はエラーに関する詳細情報を提供し、エラー条件を発生させた浮動小数点演算の結果をプログラマが変更できるようにしている。
void fp_usingSEH(void) {
/* ... */
double a = 1e-40, b, c = 0.1;
float x = 0, y;
unsigned int rv ;
unmask_fpsr();
_try {
/* y に格納することは不正確でアンダーフロー */
y = a;
/* ゼロ除算 */
b = y / x;
/* 不正確 */
c = sin(30) * a;
}
_except (_fpieee_flt(
GetExceptionCode(),
GetExceptionInformation(),
fpieee_handler)) {
{
printf ("fpieee_handler: EXCEPTION_EXECUTE_HANDLER");
}
/* ... */
}
void unmask_fpsr(void) {
unsigned int u;
unsigned int control_word;
_controlfp_s(&control_word, 0, 0);
u = control_word & ~(_EM_INVALID
| _EM_DENORMAL
| _EM_ZERODIVIDE
| _EM_OVERFLOW
| _EM_UNDERFLOW
| _EM_INEXACT);
_controlfp_s( &control_word, u, _MCW_EM);
return ;
}
int fpieee_handler(_FPIEEE_RECORD *ieee) {
/* ... */
switch (ieee->RoundingMode) {
case _FpRoundNearest:
/* ... */
break;
/* その他の丸めモードとしては _FpRoundMinusInfinity、
* _FpRoundPlusInfinity、_FpRoundChopped がある */
/* ... */
}
switch (ieee->Precision) {
case _FpPrecision24:
/* ... */
break;
/* その他の精度としては _FpPrecision53 がある*/
/* ... */
}
switch (ieee->Operation) {
case _FpCodeAdd:
/* ... */
break;
/* その他の演算としては次がある: _FpCodeSubtract、_FpCodeMultiply、
* _FpCodeDivide、_FpCodeSquareRoot、_FpCodeCompare、
* _FpCodeConvert、_FpCodeConvertTrunc */
/* ... */
}
/*
* ビットマップ ieee->Cause を処理。
* ビットマップ ieee->Enable を処理。
* ビットマップ ieee->Status を処理。
* オペランド ieee->Operand1 を処理し、
* 形式と値を評価。
* オペランド ieee->Operand2 を処理し、
* 形式と値を評価。
* 結果 ieee->Result を処理し、
* 形式と値を評価。
* 結果は、ieee->Cause で指定された演算に従って設定し、
* 結果形式は
* ieee->Result での指定に従う。
*/
/* ... */
}
リスク評価
浮動小数点エラーが検出されないと、プログラム効率の低下、不正確な結果、ソフトウェアの脆弱性が発生することがある。演算によって NaN(not a number: 非数)値が発生した場合、ほとんどのプロセッサは一瞬(最大 1 秒、または 32 ビットのデスクトッププロセッサの場合は 1 秒以上)停止する。
| レコメンデーション | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
|---|---|---|---|---|---|
| FLP03-C | 低 | 中 | 高 | P2 | L3 |
自動検出
Compass/ROSE は、浮動小数点演算が feclearexcept() と fetestexcept() で囲まれていることを確認することで、このレコメンデーションの違反を検出できる。float または double への型変換、除算(ゼロ以外であることが判明していない数での除算)、乗算を検索する必要がある。これをすべての浮動小数点演算全体に適用することを推奨する。
参考情報
- [IEEE 754]
- [Intel 01]
- [Keil 08]
- [MITRE 07] CWE ID 369, "Divide By Zero"
- [MSDN] "fpieee_flt (CRT)"
- [Open Group 04] "fenv.h - Floating-point environment"
- [SecurityFocus 07]
翻訳元
これは以下のページを翻訳したものです。
