JPCERT コーディネーションセンター

FLP05-C. 非正規化数を使用しない

C 言語の処理系のほとんどは、浮動小数点表現に IEEE 754 標準を採用している。この浮動小数点表現では、float の符号化に 1 ビットの符号部、8 ビットの指数部、および 23 ビットの仮数部を使用する。double も同じ方法で符号化され使用されるが、1 ビットの符号部、11 ビットの指数部、および 52 ビットの仮数部を使用する点が異なる。これらのビットは s(符号部)、M(仮数部)、E(指数部)の値を符号化する。その後、浮動小数点数は (-1)^s * M * 2^E として計算される。

通常、暗黙ビットであるため実際の形式には含まれない先頭の 1 と仮数部の全ビットが、有効数字を表現するために使われる。したがって float の精度は通常、24 ビットの有効数字精度であり、double の精度は通常、53 ビットの有効数字精度である。このような数値を正規化数と呼ぶ。精度が固定されているという意味では、すべての浮動小数点数には限界がある。(「FLP00-C. 浮動小数点数の限界を理解する」を参照。)

ただし、使用可能な指数ビット数が少なく、正常に符号化できないほど非常に小さい数字を表現するには、仮数ビットを使用して指数範囲を拡大する。これらのビットは有効ビットの精度としては機能しないため、極端に小さな数字の全体的な精度は通常よりも低い。このような数値を非正規化数と呼ぶ。精度が求められる場合に正規化数を使用したとしても、リスクをもたらすことがある(「FLP02-C. 精度の高い演算が必要な場合は浮動小数点数の使用を避ける」を参照)。しかし、正規化数だけを使用する場合に比べて、非正規化数のほうがはるかに制限が多い。

非正規化数を使用することで、浮動小数点数の精度が著しく損なわれるため、使用するべきではない。

違反コード

以下のコードは浮動小数点数から非正規化数を計算しようと試みたあとで、値を元に戻そうとしている。これは非常に精度が低い操作である。

#include <stdio.h>
float x = 1/3.0;
printf("Original      : %e\n", x);
x = x * 7e-45;
printf("Denormalized? : %e\n", x);
x = x / 7e-45;
printf("Restored      : %e\n", x);

このコードの出力は、IEEE 754 float を採用する処理系では次のようになる。

Original      : 3.333333e-01
Denormalized? : 2.802597e-45
Restored      : 4.003710e-01
適合コード

非正規化数を使用するコードを作成しないこと。float が非正規化数を生成している場合、double を代わりに使用せよ。

#include <stdio.h>
double x = 1/3.0;
printf("Original      : %e\n", x);
x = x * 7e-45;
printf("Denormalized? : %e\n", x);
x = x / 7e-45;
printf("Restored      : %e\n", x);
Original      : 3.333333e-01
Denormalized? : 2.333333e-45
Restored      : 3.333333e-01

double を使用しても非正規化数が生成される場合は、long double を使用することで問題が解消できる場合がある(処理系によっては、long double の指数範囲は double と同じ)。long double を使用しても非正規化数が生成される場合、他の解決法を探す必要がある。

非正規化数の出力

非正規化数を使用した場合に動作が処理系定義となる関数があるため、非正規化数は手間がかかることもある。たとえば、書式文字列で %a または %A という変換指定子を非正規化数に適用した場合に、処理系定義の結果を生じる可能性がある。

ISO/IEC 9899:TC3 のセクション 7.19.6.1 では次のように規定されている。

浮動小数点数を表す double 引数は 0xh.hhhh p±d の形式に変換される。ここでは 10 進数文字の前に 16 進数 1 桁(引数が正規化小数点数の場合はゼロ以外の値で、それ以外の場合は未規定)が配置される。

先頭にゼロを配置せずに、%a%A 指定子を使用すると間違いが生じやすい。

#include<stdio.h>
float x = 0x1p-125;
double y = 0x1p-1020;
printf("normalized float with %%e    : %e\n", x);
printf("normalized float with %%a    : %a\n", x);
x = 0x1p-140;
printf("denormalized float with %%e  : %e\n", x);
printf("denormalized float with %%a  : %a\n", x);
printf("normalized double with %%e   : %e\n", y);
printf("normalized double with %%a   : %a\n", y);
y = 0x1p-1050;
printf("denormalized double with %%e : %e\n", y);
printf("denormalized double with %%a : %a\n", y);
処理系固有の詳細

32 ビット Linux マシン上の gcc 4.3.2 を使用すると、このコードの出力は次のようになる。

normalized float with %e    : 2.350989e-38
normalized float with %a    : 0x1p-125
denormalized float with %e  : 7.174648e-43
denormalized float with %a  : 0x1p-140
normalized double with %e   : 8.900295e-308
normalized double with %a   : 0x1p-1020
denormalized double with %e : 8.289046e-317
denormalized double with %a : 0x0.0000001p-1022
リスク評価

浮動小数点数は近似値であり、非正規化浮動小数点数を使用すると、精度がさらに低下する。

レコメンデーション 深刻度 可能性 修正コスト 優先度 レベル
FLP05-C P4 L3
参考情報
翻訳元

これは以下のページを翻訳したものです。

FLP05-C. Don't use denormalized numbers

Top へ

Topへ
最新情報(RSSメーリングリストTwitter