浮動小数点数
浮動小数点数演算の中間結果
多くのコンピュータでは、 有効精度の大きい演算をしたからといって精度の低い演算より時間がかかるということはありません。 したがって、内部の一時変数では可能な限り大きな精度を用いて数値計算をするのが、 理にかなっています。この方針は、 ハードウェアの最大公約数を仮定して言語を作ろうということではなく、 対象とするハードの性能を最大限引き出せるようにしよう、 ということです。
浮動小数点演算式の途中結果では、 その式の型の定めるよりも 大きな精度で数を保持できます。 型によって最小の精度は規定していますが、 最大については限度を設けていません。 実装ノート: Intel x86 では、 例えば、中間結果にはハードウェアが対応している最大の 80bit の精度を利用することができると期待されます (必須ではありませんが)。
一時変数や共通部分式をうまくつかうことで、 最適化されたコードが、 その前よりもより正確な答えを返すこともありえます。
アルゴリズムは、保証される最小の精度でも動作するように書かれるべきです。 しかし同時に、 実際の精度がもっと高かったとしても精度が落ちたり失敗したりすることはないように書かねばなりません。 real型ではなく float や double を使うのは、次の場合に限られます:
- 巨大配列のメモリ消費の軽減
- 精度よりも速度が重要な場合
- データや関数引数の、Cとの互換性
浮動小数点数の定数畳み込み
演算対象の型が何であっても、定数畳み込みは real 型かそれ以上の精度の元で実行されます。 定数畳み込みは、IEEE 754 の規則に従い round-to-nearest モードで行われます。
浮動小数点数の定数は、内部的には、実際の型が何であっても、 real かそれ以上の精度で保持されています。 余分な精度は、定数畳み込みの際に使用されます。 畳み込み処理の結果を定数値に反映させる処理は、 コンパイル処理のできるだけ終わりの段階に行われます。例えば:
const float f = 0.2f;
writefln(f - 0.2);
は 0 を表示します。constでないstatic変数の値はコンパイル時伝搬されませんので、 次の例では:
static float f = 0.2f;
writefln(f - 0.2);
表示は 2.98023e-09 となります。16進浮動小数点数リテラルを使うことで、 精度や丸めに依存せずに指定のビットパターンを持った値を扱うことも可能です。 0.2f の16進表現を知るには次のコードを実行します。
import std.stdio;
void main() {
writefln("%a", 0.2f);
}
0x1.99999ap-3 が表示されます。これを16進リテラルとして用いて:
const float f = 0x1.99999ap-3f;
writefln(f - 0.2);
と書くと、表示は 2.98023e-09 になります。
コンパイラの設定や、最適化の設定、インライン化の設定に応じて、 定数畳み込みの行われる範囲は変わります。 従って、これらの設定の違いに依存して、 浮動小数点演算の結果が変化する可能性があります。
丸め制御
IEEE 754 の定める浮動小数点数演算には、4つの丸めモードを設定できる機能があります。 これらは、 std.c.fenv に定義された関数で制御します。
関数の中で浮動小数点演算の丸めモードを変更した場合、 関数を抜ける前に元に戻さなければいけません。 このルールに (たとえばインラインアセンブラを使って) 違反した場合、 以降の計算に適用される丸めモードは未定義となります。
例外フラグ
IEEE 754 では、 計算の間に起きる例外に関するフラグの設定について定められています:
FE_INVALID |
FE_DENORMAL |
FE_DIVBYZERO |
FE_OVERFLOW |
FE_UNDERFLOW |
FE_INEXACT |
これらは、 std.c.fenv に定義された関数でセット/解除します。
浮動小数点数の比較
通常の < <= > >= == != 比較演算子に加えて、 D では浮動小数特有の演算子がさらに追加されています。 その一覧は !<>= <> <>= !<= !< !>= !> !<> で、 それぞれの意味はCのNCEG拡張に準拠しています。 詳細は 浮動小数点数の比較 をご覧下さい。
浮動小数点演算の変形
実装によっては、 計算強度の削減(つまり実行時間の削減)のために、 浮動小数点に関する計算式を変形することがあります。 浮動小数演算は数学的な規則に正確に従うわけではないため、 いくつかのプログラミング言語で行われうる変形であっても、 ある種の変形は正しくないことがあります。
以下の変形は、 IEEEの規則に従うと結果が変わる可能性があるため、 Dでは行われません。
変形 | コメント |
---|---|
x + 0 → x | x が -0 のときに間違い |
x - 0 → x | x が ±0 で-∞に向かう丸めの時に間違い |
-x ↔ 0 - x | x が +0 のときに間違い |
x - x → 0 | x が NaN か ±∞ のときに間違い |
x - y ↔ -(y - x) | (1-1=+0) だが -(1-1)=-0 なので間違い |
x * 0 → 0 | x が NaN か ±∞ のときに間違い |
x / c ↔ x * (1/c) | (1/c) が正確に表現できないとき間違い |
x != x → false | x が NaN のときに間違い |
x == x → true | x が NaN のときに間違い |
x !op y ↔ !(x op y) | x か y が NaN のときに間違い |
もちろん、 副作用が変わるような変形も行われることはありません。