isnormal()リファレンスページは次のように述べています。
指定された浮動小数点数argが正規であるかどうか、つまり、ゼロ、非正規化数、無限大、またはNaNのいずれでもないかどうかを判別します。
数値がゼロ、無限、またはNaNであることは、それが何を意味するかを明確にしています。しかし、それはまた、異常と言います。数が非正規化数になるのはいつですか?
isnormal()リファレンスページは次のように述べています。
指定された浮動小数点数argが正規であるかどうか、つまり、ゼロ、非正規化数、無限大、またはNaNのいずれでもないかどうかを判別します。
数値がゼロ、無限、またはNaNであることは、それが何を意味するかを明確にしています。しかし、それはまた、異常と言います。数が非正規化数になるのはいつですか?
回答:
IEEE754標準では、浮動小数点数は2進科学記数法x = M × 2eとして表されます。ここで、Mは仮数、eは指数です。数学的には、あなたはいつも指数を選択することができる1≤ M <2 *ただし、コンピュータ表現で指数が唯一の有限の範囲を持つことができるので、そこにゼロより大きないくつかの数字がありますが、1.0未満×2 E分。これらの数字は、subnormalsまたはデノーマルです。
実際には、仮数は先頭1が常に存在するため、先頭1なしで格納されます。 、非正規化数(およびゼロ)を除いてます。したがって、指数が非最小の場合、暗黙の先行1があり、指数が最小の場合、そうではなく、数は非正規であると解釈されます。
*)より一般的には、1≤ M < B 任意の塩基のためのB科学的表記法。
isnomal
あるtrue
8ビットがすべてゼロである場合にはfalse
そうでない場合は?
001010
、として解釈され1.001010
ます。
IEEE754の基本
まず、IEEE754番号の基本が整理されていることを確認しましょう。
単精度(32ビット)に焦点を当てますが、すべてをすぐに他の精度に一般化できます。
形式は次のとおりです。
またはあなたが写真が好きなら:
ソース。
記号は単純です。0は正、1は負で、話は終わりです。
指数は8ビット長であるため、0〜255の範囲です。
指数は、次のようなオフセットがあるため、バイアスと呼ばれ-127
ます。
0 == special case: zero or subnormal, explained below
1 == 2 ^ -126
...
125 == 2 ^ -2
126 == 2 ^ -1
127 == 2 ^ 0
128 == 2 ^ 1
129 == 2 ^ 2
...
254 == 2 ^ 127
255 == special case: infinity and NaN
主要なビット規則
(以下は架空の架空の物語であり、実際の歴史的研究に基づくものではありません。)
IEEE 754を設計する一方で、エンジニアはすべての数値以外は、気づいた0.0
ものを持って、1
最初の数字としてバイナリで。例えば:
25.0 == (binary) 11001 == 1.1001 * 2^4
0.625 == (binary) 0.101 == 1.01 * 2^-1
どちらもその迷惑なことから始まります 1.
部分ます。
したがって、その数字がほぼすべての数値に1つの精度ビットを占めるようにするのは無駄です。
このため、彼らは「リーディングビットコンベンション」を作成しました。
番号は常に1で始まると想定してください
しかし、どのように対処するの0.0
ですか?さて、彼らは例外を作成することにしました:
0.0
バイト00 00 00 00
も表すように0.0
。これは見栄えがします。
これらのルールのみを考慮した場合、表現できるゼロ以外の最小の数値は次のようになります。
これは、先頭のビット規則により、16進数で次のようになります。
1.000002 * 2 ^ (-127)
ここ.000002
で、22個のゼロと1
で、最後にます。
私たちは取ることができませんfraction = 0
、そうでなければその数は0.0
。。
しかし、それから、鋭い美的感覚も持っていたエンジニアは、考えました:それは醜いではありませんか?まっすぐ0.0
から、適切な2の累乗でもないものにジャンプするということですか?どういうわけか、もっと小さな数を表現できませんでしたか?(OK、それは「醜い」よりも少し心配でした。実際、人々は計算に対して悪い結果を得ていました。以下の「非正規化数が計算を改善する方法」を参照してください)。
非正規化数
エンジニアはしばらく頭をかいて、いつものように別の良いアイデアで戻ってきました。新しいルールを作成するとどうなりますか?
指数が0の場合、次のようになります。
- 先頭ビットが0になります
- 指数は-126に固定されています(この例外がなかったかのように-127ではありません)
このような数は、非正規化数(または同義語である非正規化数)と呼ばれます。
このルールは、次のような数を即座に意味します。
はまだです0.0
。これは、追跡するルールが1つ少ないことを意味するため、一種のエレガントです。
つまり0.0
、私たちの定義によれば、実際には非正規化数です!
この新しいルールでは、非正規化数の最小値は次のようになります。
これは以下を表します:
1.0 * 2 ^ (-126)
次に、最大の非正規化数は次のとおりです。
これは次のようになります。
0.FFFFFE * 2 ^ (-126)
ここ.FFFFFE
でも、ドットの右側の23ビット1です。
これは、正気に聞こえる最小の非正規化数にかなり近いものです。
そして、ゼロ以外の最小の非正規化数は次のとおりです。
これは次のようになります。
0.000002 * 2 ^ (-126)
これもかなり近いように見えます0.0
!
それよりも小さい数字を表すための賢明な方法を見つけることができなかったため、エンジニアは満足し、猫の写真をオンラインで表示するか、70年代に行ったことを確認しました。
ご覧のとおり、非正規化数は精度と表現の長さの間でトレードオフを行います。
最も極端な例として、最小の非ゼロの非正規値:
0.000002 * 2 ^ (-126)
基本的に、32ビットではなく1ビットの精度があります。たとえば、2で割ると次のようになります。
0.000002 * 2 ^ (-126) / 2
私たちは実際に0.0
正確に到達します!
視覚化
私たちが学んだことについて幾何学的な直感を持つことは常に良い考えなので、ここに行きます。
与えられた各指数の線上にIEEE754浮動小数点数をプロットすると、次のようになります。
+---+-------+---------------+-------------------------------+
exponent |126| 127 | 128 | 129 |
+---+-------+---------------+-------------------------------+
| | | | |
v v v v v
-------------------------------------------------------------
floats ***** * * * * * * * * * * * *
-------------------------------------------------------------
^ ^ ^ ^ ^
| | | | |
0.5 1.0 2.0 4.0 8.0
それから、次のことがわかります。
*
)。それでは、指数0まで下げましょう。
非正規化数がないと、仮想的に次のようになります。
+---+---+-------+---------------+-------------------------------+
exponent | ? | 0 | 1 | 2 | 3 |
+---+---+-------+---------------+-------------------------------+
| | | | | |
v v v v v v
-----------------------------------------------------------------
floats * **** * * * * * * * * * * * *
-----------------------------------------------------------------
^ ^ ^ ^ ^ ^
| | | | | |
0 | 2^-126 2^-125 2^-124 2^-123
|
2^-127
非正規化数の場合、次のようになります。
+-------+-------+---------------+-------------------------------+
exponent | 0 | 1 | 2 | 3 |
+-------+-------+---------------+-------------------------------+
| | | | |
v v v v v
-----------------------------------------------------------------
floats * * * * * * * * * * * * * * * * *
-----------------------------------------------------------------
^ ^ ^ ^ ^ ^
| | | | | |
0 | 2^-126 2^-125 2^-124 2^-123
|
2^-127
2つのグラフを比較すると、次のことがわかります。
subnormalsは、指数の範囲の長さを倍増さ0
から、[2^-127, 2^-126)
へ[0, 2^-126)
正常以下の範囲のフロート間のスペースは、の場合と同じです[0, 2^-126)
。
範囲に[2^-127, 2^-126)
は、非正規化数がない場合の半分のポイント数があります。
それらのポイントの半分は、範囲の残りの半分を埋めるために行きます。
範囲に[0, 2^-127)
は非正規化数のあるポイントがいくつかありますが、そうでないポイントはありません。
このポイントの欠如は[0, 2^-127)
あまりエレガントではなく、非正規化数が存在する主な理由です!
ポイントは等間隔であるため:
[2^-128, 2^-127)
のポイントは半分です[2^-127, 2^-126)
-[2^-129, 2^-128)
ポイントの半分は[2^-128, 2^-127)
これは、非正規化数がサイズと精度の間のトレードオフであると言うときの意味です。
実行可能なCの例
それでは、実際のコードを試して、理論を検証してみましょう。
現在のほとんどすべてのデスクトップマシンでは、Cfloat
は単精度IEEE754浮動小数点数を表します。
これは特に私のUbuntu18.04 amd64 LenovoP51ラップトップに当てはまります。
その仮定の下で、すべてのアサーションは次のプログラムを渡します。
subnormal.c
#if __STDC_VERSION__ < 201112L
#error C11 required
#endif
#ifndef __STDC_IEC_559__
#error IEEE 754 not implemented
#endif
#include <assert.h>
#include <float.h> /* FLT_HAS_SUBNORM */
#include <inttypes.h>
#include <math.h> /* isnormal */
#include <stdlib.h>
#include <stdio.h>
#if FLT_HAS_SUBNORM != 1
#error float does not have subnormal numbers
#endif
typedef struct {
uint32_t sign, exponent, fraction;
} Float32;
Float32 float32_from_float(float f) {
uint32_t bytes;
Float32 float32;
bytes = *(uint32_t*)&f;
float32.fraction = bytes & 0x007FFFFF;
bytes >>= 23;
float32.exponent = bytes & 0x000000FF;
bytes >>= 8;
float32.sign = bytes & 0x000000001;
bytes >>= 1;
return float32;
}
float float_from_bytes(
uint32_t sign,
uint32_t exponent,
uint32_t fraction
) {
uint32_t bytes;
bytes = 0;
bytes |= sign;
bytes <<= 8;
bytes |= exponent;
bytes <<= 23;
bytes |= fraction;
return *(float*)&bytes;
}
int float32_equal(
float f,
uint32_t sign,
uint32_t exponent,
uint32_t fraction
) {
Float32 float32;
float32 = float32_from_float(f);
return
(float32.sign == sign) &&
(float32.exponent == exponent) &&
(float32.fraction == fraction)
;
}
void float32_print(float f) {
Float32 float32 = float32_from_float(f);
printf(
"%" PRIu32 " %" PRIu32 " %" PRIu32 "\n",
float32.sign, float32.exponent, float32.fraction
);
}
int main(void) {
/* Basic examples. */
assert(float32_equal(0.5f, 0, 126, 0));
assert(float32_equal(1.0f, 0, 127, 0));
assert(float32_equal(2.0f, 0, 128, 0));
assert(isnormal(0.5f));
assert(isnormal(1.0f));
assert(isnormal(2.0f));
/* Quick review of C hex floating point literals. */
assert(0.5f == 0x1.0p-1f);
assert(1.0f == 0x1.0p0f);
assert(2.0f == 0x1.0p1f);
/* Sign bit. */
assert(float32_equal(-0.5f, 1, 126, 0));
assert(float32_equal(-1.0f, 1, 127, 0));
assert(float32_equal(-2.0f, 1, 128, 0));
assert(isnormal(-0.5f));
assert(isnormal(-1.0f));
assert(isnormal(-2.0f));
/* The special case of 0.0 and -0.0. */
assert(float32_equal( 0.0f, 0, 0, 0));
assert(float32_equal(-0.0f, 1, 0, 0));
assert(!isnormal( 0.0f));
assert(!isnormal(-0.0f));
assert(0.0f == -0.0f);
/* ANSI C defines FLT_MIN as the smallest non-subnormal number. */
assert(FLT_MIN == 0x1.0p-126f);
assert(float32_equal(FLT_MIN, 0, 1, 0));
assert(isnormal(FLT_MIN));
/* The largest subnormal number. */
float largest_subnormal = float_from_bytes(0, 0, 0x7FFFFF);
assert(largest_subnormal == 0x0.FFFFFEp-126f);
assert(largest_subnormal < FLT_MIN);
assert(!isnormal(largest_subnormal));
/* The smallest non-zero subnormal number. */
float smallest_subnormal = float_from_bytes(0, 0, 1);
assert(smallest_subnormal == 0x0.000002p-126f);
assert(0.0f < smallest_subnormal);
assert(!isnormal(smallest_subnormal));
return EXIT_SUCCESS;
}
コンパイルして実行する:
gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c
./subnormal.out
C ++
CのすべてのAPIを公開することに加えて、C ++は、次のようなCではすぐに利用できないいくつかの特別な非正規関連機能も公開します<limits>
。
denorm_min
:タイプTの最小の正の非正規化数を返しますC ++では、API全体が浮動小数点型ごとにテンプレート化されており、はるかに優れています。
実装
x86_64およびARMv8は、IEEE754をハードウェアに直接実装します。これはCコードに変換されます。
特定の実装では、非正規化数は通常よりも遅いようです。0.1fを0に変更すると、パフォーマンスが10倍遅くなるのはなぜですか。これはARMマニュアルに記載されています。この回答の「ARMv8の詳細」セクションを参照してください。
ARMv8の詳細
ARMアーキテクチャリファレンスマニュアルARMv8DDI 0487C.aマニュアルA1.5.4「Flush-to-zero」では、パフォーマンスを向上させるために非正規化数をゼロに丸める構成可能なモードについて説明しています。
非正規化数とアンダーフロー例外を含む計算を行うと、浮動小数点処理のパフォーマンスが低下する可能性があります。多くのアルゴリズムでは、非正規化されたオペランドと中間結果をゼロに置き換えることで、最終結果の精度に大きな影響を与えることなく、このパフォーマンスを回復できます。この最適化を可能にするために、ARM浮動小数点実装では、次のようにさまざまな浮動小数点形式でゼロへのフラッシュモードを使用できます。
AArch64の場合:
の場合
FPCR.FZ==1
、Flush-to-Zeroモードは、すべての単精度および倍精度の入力とすべての命令の出力に使用されます。の場合
FPCR.FZ16==1
、Flush-to-Zeroモードは、以下を除くすべての半精度入力および浮動小数点命令の出力に使用されます。—半精度と単精度の間の変換。—半精度と倍精度の間の変換。数字。
A1.5.2「浮動小数点の標準と用語」表A1-3「浮動小数点の用語」は、非正規化数と非正規化数が同義語であることを確認します。
This manual IEEE 754-2008
------------------------- -------------
[...]
Denormal, or denormalized Subnormal
C5.2.7「FPCR、浮動小数点制御レジスタ」では、浮動小数点演算の入力が正常でない場合に、ARMv8がオプションで例外を発生させたりフラグビットを設定したりする方法について説明しています。
FPCR.IDE、ビット[15]入力非正規化浮動小数点例外トラップの有効化。可能な値は次のとおりです。
0b0トラップされていない例外処理が選択されました。浮動小数点例外が発生すると、FPSR.IDCビットが1に設定されます。
0b1トラップされた例外処理が選択されました。浮動小数点例外が発生した場合、PEはFPSR.IDCビットを更新しません。トラップ処理ソフトウェアは、FPSR.IDCビットを1に設定するかどうかを決定できます。
D12.2.88「MVFR1_EL1、AArch32メディアおよびVFP機能レジスタ1」は、非正規化サポートが実際には完全にオプションであることを示し、サポートがあるかどうかを検出するためのビットを提供します。
FPFtZ、ビット[3:0]
ゼロモードにフラッシュします。浮動小数点の実装がフラッシュからゼロへの操作モードのみをサポートするかどうかを示します。定義された値は次のとおりです。
0b0000実装されていないか、ハードウェアがフラッシュからゼロへの動作モードのみをサポートしています。
0b0001ハードウェアは完全な非正規化数演算をサポートしています。
他のすべての値は予約されています。
ARMv8-Aでは、許可される値は0b0000と0b0001です。
これは、非正規化数が実装されていない場合、実装がゼロにフラッシュするだけであることを示しています。
インフィニティとNaN
奇妙な?私はいくつかのことを書いています:
非正規化数が計算を改善する方法
TODO:そのジャンプが計算結果を悪化させる方法/非正規化数が計算結果を改善する方法をさらに正確に理解します。
実際の歴史
チャールズ・セブランスによる浮動小数点の老人へのインタビュー。(1998)は、ウィリアム・カハンとのインタビューの形での短い現実世界の歴史的概観であり、ジョン・コールマンによってコメントで提案されました。
http://blogs.oracle.com/d/entry/subnormal_numbersから:
一例として、小数点を使用して同じ番号を表す潜在的に複数の方法があり、数0.1は、1×10として表すことができる-1又は0.1×10 0の数字は、常にで格納されている標準おもむくを、あるいは0.01 * 10最初のビットを1つとして。1 * 10-1の例に対応する10進数。
ここで、表現できる最小の指数が-100であると仮定します。したがって、正規形で表すことができる最小の数は1 * 10-100です。ただし、先頭ビットが1であるという制約を緩和すると、実際には同じスペースでより小さな数を表すことができます。10進数の例をとると、0.1 * 10-100を表すことができます。これは非正規化数と呼ばれます。非正規化数を持つ目的は、最小の非正規化数とゼロの間のギャップを滑らかにすることです。
非正規化数は通常の数よりも精度が低いことを理解することが非常に重要です。実際、彼らはより小さなサイズのために精度を下げて取引しています。したがって、非正規数を使用する計算は、正規数の計算と同じ精度にはなりません。したがって、非正規化数で有意な計算を行うアプリケーションは、再スケーリング(つまり、数値に何らかのスケーリング係数を掛ける)によって非正規化数が少なくなり、より正確な結果が得られるかどうかを調べる価値があります。