浮動小数点数
浮動小数点数演算の中間結果
多くのコンピュータでは、有効精度の大きい演算をしたからといって 精度の低い演算より時間がかかるということはありません。 したがって、内部の一時変数では可能な限り大きな精度を用いて 数値計算をするのが、理にかなっています。この方針は、 ハードウェアの最大公約数を仮定して言語を作ろうということではなく、 対象とするハードの性能を最大限引き出せるようにしよう、 ということです。
浮動小数点演算式の途中結果では、 その式の型の定めるよりも 大きな精度で数を保持できます。 型によって最小の精度は規定していますが、 最大については限度を設けていません。 実装ノート: 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 になります。
コンパイラの設定や、最適化の設定、インライン化の設定に応じて、 定数畳み込みの行われる範囲は変わります。 従って、これらの設定の違いに依存して、 浮動小数点演算の結果が変化する可能性があります。
複素数, 虚数型
既存の言語では、既にある型システムに複素数型を載せようと、 驚くほどの努力がつぎ込まれてきました。 テンプレート, 構造体, 演算子オーバーロードなど...、そしてどれも、究極的には失敗です。 それは何故なら、複素数演算の意味論を表現することが出来ないためコンパイラにプログラマの意図が 伝わらず、意味に基づいた最適化をすることができないからです。
これは、新しい型を加え去れすれば解決します。新しい型の追加は、コンパイラがその型の'正しい' 意味を知って全て正しく実装することを意味しています。プログラマは、正確な(少なくとも、 修正可能な(^^;)複素数の実装に頼ることができます。
複素数型を持ち込むことは、同時に虚数型の必要性を意味します。 虚数型を導入することで、実部が0の場合の最適化などによる、 パフォーマンスの改善がみこまれます。
虚数リテラルは、接尾辞iをもちます:
ireal j = 1.3i;
複素数リテラルには特別な構文はありません。 単純に実数と虚数を足してください:
cdouble cd = 3.6 + 4i; creal c = 4.5 + 2i;
複素数、実数、虚数は二つのプロパティを持ちます:
.re 実部を得る (虚数の場合は0) .im 実数型で虚部を得る (実数の場合は0)
例えば:
cd.re は double型の 4.5 cd.im は double型の 2 c.re は real型の 4.5 c.im は real型の 2 j.im は real型の 1.3 j.re は real型の 0
丸め制御
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 のときに間違い |
もちろん、 副作用が変わるような変形も行われることはありません。