web-dev-qa-db-ja.com

非正規浮動小数点数とは何ですか?

isnormal()リファレンスページ

指定された浮動小数点数argが正常かどうか、つまりゼロ、非正規、無限、NaNのいずれでもないかを決定します。

数値がゼロ、無限、またはNaNの場合、その意味は明らかです。しかし、それはまた、非正規を言います。数値が非正規になるのはいつですか?

61
BЈовић

IEEE754標準では、浮動小数点数は2進科学表記法で表されますx=[〜#〜] m [〜#〜]×2e。ここで、[〜#〜] m [〜#〜]仮数およびe指数です。数学的には、1≤[〜#〜] m [〜#〜]<2. *となるように常に指数を選択できます。ただし、コンピューター表現では指数のみ範囲が有限であり、ゼロより大きく、1.0×2より小さい数があるe。それらの数値は、非正規数または非正規数です。

実際には、仮数は先行1なしで格納されます。これは、非正規数(およびゼロ)に対して常に先行1exceptがあるためです。したがって、解釈は、指数が最小でない場合、暗黙の先行1があり、指数が最小の場合、存在せず、数は非正規であるというものです。

*)より一般的には、1≤[〜#〜] m [〜#〜]<[〜#〜] b [〜#〜]任意のベース-[〜#〜] b [〜#〜]科学表記法。

61
Kerrek SB

IEEE 754の基本

まず、IEEE 754番号の基本が整理されていることを確認しましょう。

単精度(32ビット)に焦点を当てますが、すべてをすぐに他の精度に一般化できます。

形式は次のとおりです。

  • 1ビット:符号
  • 8ビット:指数
  • 23ビット:小数

または、写真が好きな場合:

enter image description here

ソース

記号は単純です: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です
  • 数字はプラスまたはマイナス0.0を表します

バイト00 00 00 000.0を表します。

これらのルールのみを考慮した場合、表現できるゼロ以外の最小数は次のようになります。

  • 指数:0
  • 分数:1

これは、先頭のビット規則により、16進法で次のようになります。

1.000002 * 2 ^ (-127)

.000002は22個のゼロで、最後に1が付きます。

fraction = 0を取ることはできません。そうしないと、その番号は0.0になります。

しかし、その後、鋭い芸術的感覚も持っていたエンジニアは、次のように考えました。まっすぐ0.0から、2の適切な累乗でさえない何かにジャンプするということですか?もっと小さな数字をどうにか表せませんか?

非正規数

エンジニアはしばらく頭をかき、いつものように別の良いアイデアで戻ってきました。新しいルールを作成した場合:

指数が0の場合:

  • 先行ビットは0になります
  • 指数は-126に固定されています(この例外がなかった場合の-127ではありません)

このような数値は、非正規数(または同義語である非正規数)と呼ばれます。

このルールは、次のような数であることをすぐに意味します。

  • 指数:0
  • 端数:0

0.0です。これは、追跡するルールが1つ少ないことを意味するため、エレガントです。

したがって、0.0は、実際の定義では非正規数です。

この新しいルールでは、最小の非非正規数は次のとおりです。

  • 指数:1(0は非正規です)
  • 端数:0

以下を表します:

1.0 * 2 ^ (-126)

次に、最大の非正規数は次のとおりです。

  • 指数:0
  • 小数部:0x7FFFFF(23ビット1)

等しい:

0.FFFFFE * 2 ^ (-126)

ここで、.FFFFFEは、ドットの右側にある23ビットです。

これは、正常であるように聞こえる最小の非非正規数にかなり近いです。

そして、最小の非ゼロの非正規数は次のとおりです。

  • 指数:0
  • 分数:1

等しい:

0.000002 * 2 ^ (-126)

0.0にもかなり近いようです!

それより小さな数字を表現する賢明な方法を見つけることができなかったため、エンジニアは喜んで、オンラインで猫の写真を見ることに戻りました。

ご覧のとおり、非正規数は精度と表現の長さのトレードオフを行います。

最も極端な例として、最小の非ゼロの非正規:

0.000002 * 2 ^ (-126)

基本的に、32ビットではなく1ビットの精度です。たとえば、2で割った場合:

0.000002 * 2 ^ (-126) / 2

実際に0.0に到達します!

視覚化

私たちが学んだことについて幾何学的な直観を持っていることは常に良い考えなので、ここに行きます。

IEEE 754浮動小数点数を特定の指数ごとに1行にプロットすると、次のようになります。

          +---+-------+---------------+-------------------------------+
exponent  |126|  127  |      128      |              129              |
          +---+-------+---------------+-------------------------------+
          |   |       |               |                               |
          v   v       v               v                               v
          -------------------------------------------------------------
floats    ***** * * * *   *   *   *   *       *       *       *       *
          -------------------------------------------------------------
          ^   ^       ^               ^                               ^
          |   |       |               |                               |
          0.5 1.0     2.0             4.0                             8.0

それから、各指数について次のことがわかります。

  • 指数ごとに、表現された数値間にオーバーラップはありません
  • 各指数に対して、同じ数の2 ^ 32の数字があります(ここでは4 *で表されます)
  • 与えられた指数に対して点は等間隔です
  • より大きな指数はより広い範囲をカバーしますが、より多くのポイントが広がります

さて、それを指数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つのグラフを比較すると、次のことがわかります。

  • 非正規数は、0から[2^-127, 2^-126)の範囲の指数[0, 2^-126)の範囲の長さを2倍にします。

    非正規範囲のフロート間のスペースは、[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)の半分のポイントを持っています
    • 等々

    これは、非正規数がサイズと精度のトレードオフであると言うときの意味です。

Runnable Cの例

それでは、実際のコードを試して、理論を検証しましょう。

現在のほとんどすべてのデスクトップマシンでは、C floatは単精度IEEE 754浮動小数点数を表します。

これは特に私のUbuntu 18.04 AMD64 Lenovo P51ラップトップの場合です。

その前提で、すべてのアサーションは次のプログラムを渡します。

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;
}

GitHubアップストリーム

コンパイルして実行:

gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c
./subnormal.out

C++

CのすべてのAPIを公開することに加えて、C++は <limits> のようにCでは容易に利用できない追加の非正規関連機能も公開します。

  • denorm_min :タイプTの正の最小非正規値を返します

C++では、ホールAPIは各浮動小数点型に対してテンプレート化されており、はるかに優れています。

実装

x86_64およびARMv8は、ハードウェア上で直接IEEE 754を実装しており、Cコードはこれを変換します。

サブノーマルは、特定の実装ではノーマルよりも高速ではないようです: なぜ0.1fから0に変更するとパフォーマンスが10倍遅くなるのですか? これはARMこの回答の「ARMv8の詳細」セクション。

ARMv8の詳細

ARMアーキテクチャリファレンスマニュアルARMv8 DDI 0487C.aマニュアル A1.5.4「ゼロにフラッシュ」は、パフォーマンスを改善するために非正規化がゼロに丸められる構成可能なモードについて説明しています。

非正規化数とアンダーフロー例外を含む計算を実行すると、浮動小数点処理のパフォーマンスが低下する可能性があります。多くのアルゴリズムでは、非正規化オペランドと中間結果をゼロに置き換えることにより、最終結果の精度に大きな影響を与えることなく、このパフォーマンスを回復できます。この最適化を許可するために、ARM浮動小数点の実装により、次のように異なる浮動小数点形式でFlush-to-Zeroモードを使用できます。

  • 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]

ゼロモードにフラッシュします。浮動小数点実装がFlush-to-Zeroモードの操作のみをサポートするかどうかを示します。定義されている値は次のとおりです。

  • 0b0000実装されていないか、ハードウェアがFlush-to-Zeroモードのみをサポートしています。

  • 0b0001ハードウェアは完全な非正規化数演算をサポートしています。

他のすべての値は予約されています。

ARMv8-Aでは、許可される値は0b0000および0b0001です。

これは、サブノーマルが実装されていない場合、実装はゼロへのフラッシュに戻ることを示唆しています。

InfinityおよびNaN

奇妙な?私はいくつかのことを書いた:

http://blogs.Oracle.com/d/entry/subnormal_numbers から:

例として10進数を使用して、同じ数値を表す複数の方法が潜在的にあり、数値0.1は1 * 10として表すことができます。-1 または0.1 * 10 または、さらに0.01 *10。標準では、数値は常に最初のビットを1として格納されることが規定されています。 1 * 10-1の例に対応する10進数。

ここで、表現可能な最低指数が-100であるとします。したがって、通常の形式で表現できる最小数は1 * 10です-100。ただし、先頭ビットが1であるという制約を緩和すると、実際には同じスペースでより小さい数を表すことができます。 10進数の例をとると、0.1 * 10を表すことができます-100。これは非正規数と呼ばれます。非正規数を持つ目的は、最小正規数とゼロの間のギャップを滑らかにすることです。

非正規数は正規数よりも低い精度で表されることを理解することが非常に重要です。実際、彼らはサイズを小さくするために精度を下げて取引しています。したがって、非正規数を使用する計算は、正規数の計算と同じ精度にはなりません。したがって、非正規数で重要な計算を行うアプリケーションは、再スケーリング(つまり、スケーリング係数で数値を乗算する)が非正規数を減らし、より正確な結果をもたらすかどうかを調べる価値があります。

25
allwyn.menezes