1

ビットごとの演算子のみを使用して、整数値を IEEE 単精度浮動小数点形式に変換する方法を学ぶことに興味があります。ただし、指数を計算するときに必要な論理シフトの数を知るために何ができるかについて、私は混乱しています。

15 などの int を指定すると、次のようになります。

バイナリ: 1111

-> 1.111 x 2^3 => 最初のビットの後に小数点を置くと、「e」の値が 3 になることがわかります。

E = Exp - バイアス したがって、Exp = 130 = 10000010

仮数は次のようになります: 111000000000000000000000

ただし、最初のビットの後に 10 進数を配置すると 3 ビットあることがわかったので、「e」の値が 3 になることはわかっていました。一般的なケースとして、これをコーディングするより一般的な方法はありますか?

繰り返しますが、これは int から float への変換です。整数が負でもゼロでもなく、仮数部に許可されている最大スペースよりも大きくないことを前提としています。

また、23ビットを超える値に丸めが必要な理由を誰かが説明できますか? 前もって感謝します!

4

1 に答える 1

5

まず、浮動小数点の弱点をよりよく理解したい場合は、読むことを検討すべき論文: "What Every Computer Scientist Should Know About Floating Point Arithmetic" http://www.validlab.com/goldberg/paper.pdf

そして今、いくつかの肉に。

unsigned int次のコードは基本的なもので、範囲 0 < 値 < 2 24から IEEE-754 単精度浮動小数点数を生成しようとします。これは、最新のハードウェアで最も遭遇する可能性が高い形式であり、元の質問で参照しているように見える形式です。

IEEE-754 単精度浮動小数点数は、1 つの符号ビット、8 ビットの指数、および 23 ビットの仮数 (仮数とも呼ばれる) の 3 つのフィールドに分割されます。IEEE-754 は隠し 1仮数部を使用します。これは仮数部が実際には合計 24 ビットであることを意味します。ビットは左から右にパックされ、ビット 31 に符号ビット、ビット 30 .. 23 に指数、ビット 22 .. 0 に仮数が含まれます。ウィキペディアの次の図は、次のことを示しています。

浮動小数点形式

指数には 127 のバイアスがあります。つまり、浮動小数点数に関連付けられた実際の指数は、指数フィールドに格納されている値よりも 127 小さいことを意味します。したがって、0 の指数は 127 としてエンコードされます。

(注: ウィキペディアの完全な記事は興味深いかもしれません。参照: http://en.wikipedia.org/wiki/Single_precision_floating-point_format )

したがって、IEEE-754 番号 0x40000000 は次のように解釈されます。

  • ビット 31 = 0: 正の値
  • ビット 30 .. 23 = 0x80: 指数 = 128 - 127 = 1 (aka. 2 1 )
  • ビット 22 .. 0 はすべて 0 です: 仮数 = 1.00000000_00000000_0000000. (非表示の1を復元したことに注意してください)。

したがって、値は 1.0 x 2 1 = 2.0 です。

上記の限られた範囲の を IEEE-754 形式に変換するunsigned intには、次のような関数を使用できます。次の手順を実行します。

  • 整数の先頭の 1 を浮動小数点表現の非表示の 1 の位置に揃えます。
  • 整数を整列させながら、行われたシフトの総数を記録します。
  • 非表示の 1 をマスクします。
  • 行われたシフトの数を使用して、指数を計算し、それを数値に追加します。
  • を使用しreinterpret_castて、結果のビットパターンを に変換しfloatます。この部分は、型打ちされたポインターを使用しているため、醜いハックです。を悪用してこれを行うこともできますunion_itof一部のプラットフォームでは、この再解釈の見苦しさを軽減するための組み込み操作 ( など) が提供されています。

これを行うには、はるかに高速な方法があります。これは、非常に効率的ではないにしても、教育的に役立つことを意図しています。

float uint_to_float(unsigned int significand)
{
    // Only support 0 < significand < 1 << 24.
    if (significand == 0 || significand >= 1 << 24)
        return -1.0;  // or abort(); or whatever you'd like here.

    int shifts = 0;

    //  Align the leading 1 of the significand to the hidden-1 
    //  position.  Count the number of shifts required.
    while ((significand & (1 << 23)) == 0)
    {
        significand <<= 1;
        shifts++;
    }

    //  The number 1.0 has an exponent of 0, and would need to be
    //  shifted left 23 times.  The number 2.0, however, has an
    //  exponent of 1 and needs to be shifted left only 22 times.
    //  Therefore, the exponent should be (23 - shifts).  IEEE-754
    //  format requires a bias of 127, though, so the exponent field
    //  is given by the following expression:
    unsigned int exponent = 127 + 23 - shifts;

    //  Now merge significand and exponent.  Be sure to strip away
    //  the hidden 1 in the significand.
    unsigned int merged = (exponent << 23) | (significand & 0x7FFFFF);


    //  Reinterpret as a float and return.  This is an evil hack.
    return *reinterpret_cast< float* >( &merged );
}

数値の先頭の 1 を検出する関数を使用すると、このプロセスをより効率的に行うことができます。clz(これらは、「先行ゼロのカウント」や「正規化」などの名前で呼ばれることがありますnorm。)

符号を記録し、整数の絶対値を取得し、上記の手順を実行してから、符号を数値のビット 31 に挿入することで、これを符号付き数値に拡張することもできます。

整数 >= 2 24の場合、整数全体が 32 ビット浮動小数点形式の仮数フィールドに収まりません。これが、「丸める」必要がある理由です。値を適合させるために、LSB を失います。したがって、複数の整数が同じ浮動小数点パターンにマッピングされることになります。正確なマッピングは、丸めモード (-Inf 方向への丸め、+Inf 方向への丸め、ゼロ方向への丸め、最も近い偶数方向への丸め) によって異なります。しかし、問題は、24 ビットを 24 ビット未満に押し込むことはできず、損失が発生するということです。

これは、上記のコードで確認できます。先頭の 1 を非表示の 1 の位置に揃えることで機能します。値が >= 2 24の場合、コードはleftではなくrightにシフトする必要があり、必然的に LSB が離れてシフトされます。丸めモードは、シフトされたビットを処理する方法を示しているだけです。

于 2013-12-01T02:11:38.943 に答える