注意すべき重要な点の 1 つは、C 言語が最初に次のような計算を指定したことです。
float a=b+c+d;
b、c、および d を使用可能な最長の浮動小数点型 (たまたま type double
) に変換し、それらを加算してから、結果を に変換しfloat
ます。このようなセマンティクスは、コンパイラにとっては単純であり、プログラマにとっては便利でしたが、少し問題がありました。数値を格納するための最も効率的な形式は、計算を実行するための最も効率的な形式と同じではありません。浮動小数点ハードウェアのないマシンでは、必ずしも正規化されていない 64 ビットの仮数として格納された値と、個別に格納された 15 ビットの指数および符号に対して計算を実行してから、64 ビットとして格納された値を操作する方が高速です。少しdouble
これは、すべての操作の前にアンパックし、その後正規化して再パックする必要があります (次の操作のためにすぐにアンパックする場合でも)。マシンが中間結果をより長い形式で保持することで、速度と精度の両方が向上しました。ANSI C では type でこれが許可されていlong double
ます。
残念ながら、ANSI C は、可変引数関数が、すべての浮動小数点値を に変換するかlong double
、すべて に変換するdouble
か、またはfloat
ととしてdouble
渡して渡すdouble
かを示す手段を提供できませんでした。このような機能があれば、との値を区別する必要のないコードを簡単に作成できたはずです。残念ながら、そのような機能がないということは、システム上でlong double
long double
double
long double
double
long double
異なるタイプのコードは区別を気にする必要があり、そうでないシステムでは気にしません。これは、型が同じであるシステムで書かれた多くのコードが、そうでないシステムで壊れることを意味します。コンパイラ ベンダは、最も簡単な修正は単にlong double
be と同義にしdouble
、中間計算を正確に保持できる型を提供しないことであると判断しました。
中間計算を表現できない型で実行するのは悪いことなので、論理的なことは計算float
を type として実行することであると判断した人もいましたfloat
。type を使用するよりも高速なハードウェア プラットフォームもありますがdouble
、多くの場合、精度に望ましくない結果をもたらします。検討:
float triangleArea(float a, float b, float c)
{
long double s = (a+b+c)/2.0;
return sqrt((s-a)*(s-b)*(s-c)*c);
}
を使用して中間計算が実行されるシステムではlong double
、これにより良好な精度が得られます。中間計算が として実行されるシステムでは、これにより、a、b、および c がすべて正確に表現できる場合でも、恐ろしいfloat
精度が得られる可能性があります。たとえば、a と b が 16777215.0f で c が 4.0f の場合、 の値は16777217.0 になるはずですが、a、b、c の合計を として計算すると、1677216.0 になります。これにより、正しい値の半分未満の面積が得られます。a と c が 16777215.0f で b が 4.0f の場合 (同じ数値で順序が異なる)、16777218.0 として計算され、50% 大きすぎる領域が生成されます。s
float
s
x86 では良い結果が得られる計算 (多くのコンパイラは、プログラマーには役に立たなくても 80 ビット型に積極的にプロモートします) があるが、x64 ではお粗末な結果になる場合は、次のような計算があると思います。これを超えると、オペランドまたは最終結果よりも高い精度で中間ステップを実行する必要があります。上記のメソッドの最初の行を次のように変更します。
long double s = ((long double)a+b+c)/2.0;
低精度で計算を実行し、不正確な結果を高精度の変数に格納するのではなく、中間計算を高精度で実行するように強制します。