43

32ビットと16ビットの浮動小数点数の間で変換するクロスプラットフォームのライブラリ/アルゴリズムが必要です。16ビットの数値で数学を実行する必要はありません。32ビットフロートのサイズを小さくして、ネットワーク経由で送信できるようにする必要があります。私はC++で作業しています。

どれだけの精度が失われるかは理解していますが、それは私のアプリケーションでは問題ありません。

IEEE16ビット形式は素晴らしいでしょう。

4

14 に答える 14

58

単精度から半精度への完全な変換。これは私のSSEバージョンからの直接コピーなので、ブランチはありません。これは、ブランチレス選択を実行するという事実を利用しています-true == ~0(GCCifはステートメントを条件付きジャンプの不潔な混乱に変換しますが、Clangはそれらを条件付き移動に変換するだけです)。

更新(2019-11-04):完全に正しい丸めで単精度および倍精度の値をサポートするように作り直されました。また、わかりやすくするために、各ブランチレス選択の上に対応するifステートメントをコメントとして配置します。埋め込まれたNaNメッセージをフォーマット間で確実に変換する方法がないため、すべての着信NaNは、速度と健全性のためにベースクワイエットNaNに変換されます。

#include <cstdint> // uint32_t, uint64_t, etc.
#include <cstring> // memcpy
#include <climits> // CHAR_BIT
#include <limits>  // numeric_limits
#include <utility> // is_integral_v, is_floating_point_v, forward

namespace std
{
  template< typename T , typename U >
  T bit_cast( U&& u ) {
    static_assert( sizeof( T ) == sizeof( U ) );
    union { T t; }; // prevent construction
    std::memcpy( &t, &u, sizeof( t ) );
    return t;
  }
} // namespace std

template< typename T > struct native_float_bits;
template<> struct native_float_bits< float >{ using type = std::uint32_t; };
template<> struct native_float_bits< double >{ using type = std::uint64_t; };
template< typename T > using native_float_bits_t = typename native_float_bits< T >::type;

static_assert( sizeof( float ) == sizeof( native_float_bits_t< float > ) );
static_assert( sizeof( double ) == sizeof( native_float_bits_t< double > ) );

template< typename T, int SIG_BITS, int EXP_BITS >
struct raw_float_type_info {
  using raw_type = T;

  static constexpr int sig_bits = SIG_BITS;
  static constexpr int exp_bits = EXP_BITS;
  static constexpr int bits = sig_bits + exp_bits + 1;

  static_assert( std::is_integral_v< raw_type > );
  static_assert( sig_bits >= 0 );
  static_assert( exp_bits >= 0 );
  static_assert( bits <= sizeof( raw_type ) * CHAR_BIT );

  static constexpr int exp_max = ( 1 << exp_bits ) - 1;
  static constexpr int exp_bias = exp_max >> 1;

  static constexpr raw_type sign = raw_type( 1 ) << ( bits - 1 );
  static constexpr raw_type inf = raw_type( exp_max ) << sig_bits;
  static constexpr raw_type qnan = inf | ( inf >> 1 );

  static constexpr auto abs( raw_type v ) { return raw_type( v & ( sign - 1 ) ); }
  static constexpr bool is_nan( raw_type v ) { return abs( v ) > inf; }
  static constexpr bool is_inf( raw_type v ) { return abs( v ) == inf; }
  static constexpr bool is_zero( raw_type v ) { return abs( v ) == 0; }
};
using raw_flt16_type_info = raw_float_type_info< std::uint16_t, 10, 5 >;
using raw_flt32_type_info = raw_float_type_info< std::uint32_t, 23, 8 >;
using raw_flt64_type_info = raw_float_type_info< std::uint64_t, 52, 11 >;
//using raw_flt128_type_info = raw_float_type_info< uint128_t, 112, 15 >;

template< typename T, int SIG_BITS = std::numeric_limits< T >::digits - 1,
  int EXP_BITS = sizeof( T ) * CHAR_BIT - SIG_BITS - 1 >
struct float_type_info 
: raw_float_type_info< native_float_bits_t< T >, SIG_BITS, EXP_BITS > {
  using flt_type = T;
  static_assert( std::is_floating_point_v< flt_type > );
};

template< typename E >
struct raw_float_encoder
{
  using enc = E;
  using enc_type = typename enc::raw_type;

  template< bool DO_ROUNDING, typename F >
  static auto encode( F value )
  {
    using flt = float_type_info< F >;
    using raw_type = typename flt::raw_type;
    static constexpr auto sig_diff = flt::sig_bits - enc::sig_bits;
    static constexpr auto bit_diff = flt::bits - enc::bits;
    static constexpr auto do_rounding = DO_ROUNDING && sig_diff > 0;
    static constexpr auto bias_mul = raw_type( enc::exp_bias ) << flt::sig_bits;
    if constexpr( !do_rounding ) { // fix exp bias
      // when not rounding, fix exp first to avoid mixing float and binary ops
      value *= std::bit_cast< F >( bias_mul );
    }
    auto bits = std::bit_cast< raw_type >( value );
    auto sign = bits & flt::sign; // save sign
    bits ^= sign; // clear sign
    auto is_nan = flt::inf < bits; // compare before rounding!!
    if constexpr( do_rounding ) {
      static constexpr auto min_norm = raw_type( flt::exp_bias - enc::exp_bias + 1 ) << flt::sig_bits;
      static constexpr auto sub_rnd = enc::exp_bias < sig_diff
        ? raw_type( 1 ) << ( flt::sig_bits - 1 + enc::exp_bias - sig_diff )
        : raw_type( enc::exp_bias - sig_diff ) << flt::sig_bits;
      static constexpr auto sub_mul = raw_type( flt::exp_bias + sig_diff ) << flt::sig_bits;
      bool is_sub = bits < min_norm;
      auto norm = std::bit_cast< F >( bits );
      auto subn = norm;
      subn *= std::bit_cast< F >( sub_rnd ); // round subnormals
      subn *= std::bit_cast< F >( sub_mul ); // correct subnormal exp
      norm *= std::bit_cast< F >( bias_mul ); // fix exp bias
      bits = std::bit_cast< raw_type >( norm );
      bits += ( bits >> sig_diff ) & 1; // add tie breaking bias
      bits += ( raw_type( 1 ) << ( sig_diff - 1 ) ) - 1; // round up to half
      //if( is_sub ) bits = std::bit_cast< raw_type >( subn );
      bits ^= -is_sub & ( std::bit_cast< raw_type >( subn ) ^ bits );
    }
    bits >>= sig_diff; // truncate
    //if( enc::inf < bits ) bits = enc::inf; // fix overflow
    bits ^= -( enc::inf < bits ) & ( enc::inf ^ bits );
    //if( is_nan ) bits = enc::qnan;
    bits ^= -is_nan & ( enc::qnan ^ bits );
    bits |= sign >> bit_diff; // restore sign
    return enc_type( bits );
  }

  template< typename F >
  static F decode( enc_type value )
  {
    using flt = float_type_info< F >;
    using raw_type = typename flt::raw_type;
    static constexpr auto sig_diff = flt::sig_bits - enc::sig_bits;
    static constexpr auto bit_diff = flt::bits - enc::bits;
    static constexpr auto bias_mul = raw_type( 2 * flt::exp_bias - enc::exp_bias ) << flt::sig_bits;
    raw_type bits = value;
    auto sign = bits & enc::sign; // save sign
    bits ^= sign; // clear sign
    auto is_norm = bits < enc::inf;
    bits = ( sign << bit_diff ) | ( bits << sig_diff );
    auto val = std::bit_cast< F >( bits ) * std::bit_cast< F >( bias_mul );
    bits = std::bit_cast< raw_type >( val );
    //if( !is_norm ) bits |= flt::inf;
    bits |= -!is_norm & flt::inf;
    return std::bit_cast< F >( bits );
  }
};

using flt16_encoder = raw_float_encoder< raw_flt16_type_info >;

template< typename F >
auto quick_encode_flt16( F && value )
{ return flt16_encoder::encode< false >( std::forward< F >( value ) ); }

template< typename F >
auto encode_flt16( F && value )
{ return flt16_encoder::encode< true >( std::forward< F >( value ) ); }

template< typename F = float, typename X >
auto decode_flt16( X && value )
{ return flt16_encoder::decode< F >( std::forward< X >( value ) ); }

もちろん、完全なIEEEサポートは必ずしも必要ではありません。値がゼロに近づく対数分解能を必要としない場合は、すでに述べたように、値を固定小数点形式に線形化する方がはるかに高速です。

于 2010-08-22T19:23:38.860 に答える
22

半精度浮動小数点数:半精度浮動小数点数
float f = ((h&0x8000)<<16) | (((h&0x7c00)+0x1C000)<<13) | ((h&0x03FF)<<13);


uint32_t x = *((uint32_t*)&f);
uint16_t h = ((x>>16)&0x8000)|((((x&0x7f800000)-0x38000000)>>13)&0x7c00)|((x>>13)&0x03ff);

于 2014-11-06T12:09:06.480 に答える
20

std::frexp通常の浮動小数点数または倍精度浮動小数点数から仮数と指数を抽出します。次に、半精度浮動小数点数に収まらないほど大きい指数(飽和...?)をどうするかを決定し、それに応じて調整し、半分を置く必要があります-一緒に正確な数。 この記事には、変換の実行方法を示すCソースコードが含まれています。

于 2009-11-02T04:55:25.483 に答える
18

ニーズ(-1000、1000)を考えると、固定小数点表現を使用する方がよいでしょう。

//change to 20000 to SHORT_MAX if you don't mind whole numbers
//being turned into fractional ones
const int compact_range = 20000;

short compactFloat(double input) {
    return round(input * compact_range / 1000);
}
double expandToFloat(short input) {
    return ((double)input) * 1000 / compact_range;
}

これにより、0.05に最も近い精度が得られます。20000をSHORT_MAXに変更すると、精度は少し向上しますが、一部の整数は、もう一方の端で小数になります。

于 2009-11-02T05:35:29.210 に答える
10

なぜそんなに複雑なのですか?私の実装は追加のライブラリを必要とせず、IEEE-754 FP16形式に準拠し、正規化数と非正規化数の両方を管理し、ブランチがなく、前後の変換と溝NaN、またはInf拡張範囲に約40クロックサイクルかかります。これがビット演算の魔法の力です。

typedef unsigned short ushort;
typedef unsigned int uint;

uint as_uint(const float x) {
    return *(uint*)&x;
}
float as_float(const uint x) {
    return *(float*)&x;
}

float half_to_float(const ushort x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
    const uint e = (x&0x7C00)>>10; // exponent
    const uint m = (x&0x03FF)<<13; // mantissa
    const uint v = as_uint((float)m)>>23; // evil log2 bit hack to count leading zeros in denormalized format
    return as_float((x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000))); // sign : normalized : denormalized
}
ushort float_to_half(const float x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
    const uint b = as_uint(x)+0x00001000; // round-to-nearest-even: add last bit after truncated mantissa
    const uint e = (b&0x7F800000)>>23; // exponent
    const uint m = b&0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding
    return (b&0x80000000)>>16 | (e>112)*((((e-112)<<10)&0x7C00)|m>>13) | ((e<113)&(e>101))*((((0x007FF000+m)>>(125-e))+1)>>1) | (e>143)*0x7FFF; // sign : normalized : denormalized : saturate
}

使用方法と変換が正しいことを確認する方法の例:

#include <iostream>

void print_bits(const ushort x) {
    for(int i=15; i>=0; i--) {
        cout << ((x>>i)&1);
        if(i==15||i==10) cout << " ";
        if(i==10) cout << "      ";
    }
    cout << endl;
}
void print_bits(const float x) {
    uint b = *(uint*)&x;
    for(int i=31; i>=0; i--) {
        cout << ((b>>i)&1);
        if(i==31||i==23) cout << " ";
        if(i==23) cout << "   ";
    }
    cout << endl;
}

int main() {
    const float x = 1.0f;
    const ushort x_compressed = float_to_half(x);
    const float x_decompressed = half_to_float(x_compressed);
    print_bits(x);
    print_bits(x_compressed);
    print_bits(x_decompressed);
    return 0;
}

出力:

0 01111111    00000000000000000000000
0 01111       0000000000
0 01111111    00000000000000000000000
于 2020-02-03T21:19:30.253 に答える
5

情報のストリームを送信している場合、特にアプリケーションが持っているようにすべてが一貫した範囲内にある場合は、おそらくこれよりもうまくいく可能性があります。

float32の最小値と最大値だけで構成される小さなヘッダーを送信すると、2つの間の16ビット補間値として情報を送信できます。精度はそれほど問題ではないとおっしゃっていますが、一度に8ビットを送信することもできます。

再構築時の値は次のようになります。

float t = _t / numeric_limits<unsigned short>::max();  // With casting, naturally ;)
float val = h.min + t * (h.max - h.min);

お役に立てば幸いです。

-トム

于 2009-11-02T10:01:32.237 に答える
4

この質問はすでに少し古いですが、完全を期すために、半精度から浮動小数点への変換と半精度への変換についてこのペーパーを参照することもできます。

それらは、比較的小さなルックアップテーブルでブランチレステーブル駆動型アプローチを使用します。これは完全にIEEE準拠であり、PhernostのIEEE準拠のブランチレス変換ルーチンよりもパフォーマンスが優れています(少なくとも私のマシンでは)。しかしもちろん、彼のコードはSSEにはるかに適していて、メモリ遅延の影響を受けにくいです。

于 2012-02-23T17:08:53.140 に答える
4

この16ビットから32ビットの浮動小数点への変換は、無限大やNaNを考慮する必要がなく、非正規化数(DAZ)を受け入れることができる場合に非常に高速です。つまり、パフォーマンスに敏感な計算には適していますが、非正規化数が発生することが予想される場合は、ゼロによる除算に注意する必要があります。

これは、条件付き移動または同等の「setif」があるx86またはその他のプラットフォームに最も適していることに注意してください。

  1. 入力から符号ビットを取り除きます
  2. 仮数の最上位ビットを22番目のビットに揃えます
  3. 指数バイアスを調整する
  4. 入力指数がゼロの場合、ビットをすべてゼロに設定します
  5. 符号ビットを再挿入します

逆は、いくつかの追加を加えて、単精度から半精度に適用されます。

void float32(float* __restrict out, const uint16_t in) {
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;

    t1 = in & 0x7fff;                       // Non-sign bits
    t2 = in & 0x8000;                       // Sign bit
    t3 = in & 0x7c00;                       // Exponent

    t1 <<= 13;                              // Align mantissa on MSB
    t2 <<= 16;                              // Shift sign bit into position

    t1 += 0x38000000;                       // Adjust bias

    t1 = (t3 == 0 ? 0 : t1);                // Denormals-as-zero

    t1 |= t2;                               // Re-insert sign bit

    *((uint32_t*)out) = t1;
};

void float16(uint16_t* __restrict out, const float in) {
    uint32_t inu = *((uint32_t*)&in);
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;

    t1 = inu & 0x7fffffff;                 // Non-sign bits
    t2 = inu & 0x80000000;                 // Sign bit
    t3 = inu & 0x7f800000;                 // Exponent

    t1 >>= 13;                             // Align mantissa on MSB
    t2 >>= 16;                             // Shift sign bit into position

    t1 -= 0x1c000;                         // Adjust bias

    t1 = (t3 > 0x38800000) ? 0 : t1;       // Flush-to-zero
    t1 = (t3 < 0x8e000000) ? 0x7bff : t1;  // Clamp-to-max
    t1 = (t3 == 0 ? 0 : t1);               // Denormals-as-zero

    t1 |= t2;                              // Re-insert sign bit

    *((uint16_t*)out) = t1;
};

0x7bff定数を0x7c00に変更して、無限大にオーバーフローさせることができることに注意してください。

ソースコードについてはGitHubを参照してください。

于 2013-02-27T17:18:58.543 に答える
4

ここで他の回答で説明されているアプローチのほとんどは、浮動小数点から半精度への変換時に正しく丸められないか、2 **-14が最小の非ゼロ数になるため問題となる非正規化数を破棄するか、Inf/で不幸なことをします。 NaN。半分の最大の有限数は2^16より少し小さいため、Infも問題になります。OpenEXRは不必要に遅くて複雑でした、最後に私はそれを見ました。高速で正しいアプローチでは、FPUを使用して、直接命令として変換を実行するか、FPU丸めハードウェアを使用して正しい処理を実行します。半精度から浮動小数点への変換は、2^16要素のルックアップテーブルより遅くないはずです。

以下を打ち負かすのは難しいです。

OS X / iOSでは、vImageConvert_PlanarFtoPlanar16FおよびvImageConvert_Planar16FtoPlanarFを使用できます。Accelerate.frameworkを参照してください。

Intel ivybridgeは、このためのSSE命令を追加しました。f16cintrin.hを参照してください。同様の手順がネオン用のARMISAに追加されました。arm_neon.hのvcvt_f32_f16およびvcvt_f16_f32を参照してください。iOSでは、arm64またはarmv7sarchを使用してそれらにアクセスする必要があります。

于 2015-03-25T18:21:37.357 に答える
4

このコードは、32ビット浮動小数点数を16ビットに変換します。

#include <x86intrin.h>
#include <iostream>

int main()
{
    float f32;
    unsigned short f16;
    f32 = 3.14159265358979323846;
    f16 = _cvtss_sh(f32, 0);
    std::cout << f32 << std::endl;
    f32 = _cvtsh_ss(f16);
    std::cout << f32 << std::endl;
    return 0;
}

Intelicpc16.0.2でテストしました。

$ icpc a.cpp

g ++ 7.3.0:

$ g++ -march=native a.cpp

およびclang++6.0.0:

$ clang++ -march=native a.cpp

それは印刷します:

$ ./a.out
3.14159
3.14062

これらの組み込み関数に関するドキュメントは、次のURLで入手できます。

https://software.intel.com/en-us/node/524287

https://clang.llvm.org/doxygen/f16cintrin_8h.html

于 2017-03-28T23:32:17.070 に答える
1

質問は古く、すでに回答されていますが、16ビットIEEE準拠の半精度浮動小数点数を作成でき、組み込みの浮動小数点型とほぼ同じように動作するクラスを持つオープンソースのC++ライブラリについて言及する価値があると思いました。 32ビットではなく16ビット。これはOpenEXRライブラリの「half」クラスです。このコードは、パーミッシブBSDスタイルのライセンスに基づいています。標準ライブラリ以外の依存関係はないと思います。

于 2012-12-12T01:52:35.297 に答える
1

私はこれとまったく同じ問題を抱えていて、このリンクが非常に役立つことがわかりました。ファイル「ieeehalfprecision.c」をプロジェクトにインポートして、次のように使用するだけです。

float myFloat = 1.24;
uint16_t resultInHalf;
singles2halfp(&resultInHalf, &myFloat, 1); // it accepts a series of floats, so use 1 to input 1 float

// an example to revert the half float back
float resultInSingle;
halfp2singles(&resultInSingle, &resultInHalf, 1);

私もいくつかのコードを変更します(リンクの作者(James Tursa)によるコメントを参照してください):

#define INT16_TYPE int16_t 
#define UINT16_TYPE uint16_t 
#define INT32_TYPE int32_t 
#define UINT32_TYPE uint32_t
于 2014-09-09T04:50:05.430 に答える
1

ハーフフロート形式からシングルフロート形式への変換の実装を見つけ、 AVX2を使用して元に戻しました。これらのアルゴリズムのソフトウェア実装よりもはるかに高速です。お役に立てば幸いです。

32ビットfloatから16ビットfloatへの変換:

#include <immintrin.h"

inline void Float32ToFloat16(const float * src, uint16_t * dst)
{
    _mm_storeu_si128((__m128i*)dst, _mm256_cvtps_ph(_mm256_loadu_ps(src), 0));
}

void Float32ToFloat16(const float * src, size_t size, uint16_t * dst)
{
    assert(size >= 8);

    size_t fullAlignedSize = size&~(32-1);
    size_t partialAlignedSize = size&~(8-1);

    size_t i = 0;
    for (; i < fullAlignedSize; i += 32)
    {
        Float32ToFloat16(src + i + 0, dst + i + 0);
        Float32ToFloat16(src + i + 8, dst + i + 8);
        Float32ToFloat16(src + i + 16, dst + i + 16);
        Float32ToFloat16(src + i + 24, dst + i + 24);
    }
    for (; i < partialAlignedSize; i += 8)
        Float32ToFloat16(src + i, dst + i);
    if(partialAlignedSize != size)
        Float32ToFloat16(src + size - 8, dst + size - 8);
}

16ビットfloatから32ビットfloatへの変換:

#include <immintrin.h"

inline void Float16ToFloat32(const uint16_t * src, float * dst)
{
    _mm256_storeu_ps(dst, _mm256_cvtph_ps(_mm_loadu_si128((__m128i*)src)));
}

void Float16ToFloat32(const uint16_t * src, size_t size, float * dst)
{
    assert(size >= 8);

    size_t fullAlignedSize = size&~(32-1);
    size_t partialAlignedSize = size&~(8-1);

    size_t i = 0;
    for (; i < fullAlignedSize; i += 32)
    {
        Float16ToFloat32<align>(src + i + 0, dst + i + 0);
        Float16ToFloat32<align>(src + i + 8, dst + i + 8);
        Float16ToFloat32<align>(src + i + 16, dst + i + 16);
        Float16ToFloat32<align>(src + i + 24, dst + i + 24);
    }
    for (; i < partialAlignedSize; i += 8)
        Float16ToFloat32<align>(src + i, dst + i);
    if (partialAlignedSize != size)
        Float16ToFloat32<false>(src + size - 8, dst + size - 8);
}
于 2017-05-12T10:03:48.727 に答える
0

10進数から単精度までのコードに感謝

実際には、同じコードを半精度で編集することはできますが、gcc Cコンパイラでは不可能なので、次のようにします。

sudo apt install clang

次に、次のコードを試してください

// A C code to convert Decimal value to IEEE 16-bit floating point Half precision

#include <stdio.h>

void printBinary(int n, int i)
{
 

    int k;
    for (k = i - 1; k >= 0; k--) {
 
        if ((n >> k) & 1)
            printf("1");
        else
            printf("0");
    }
}
 
typedef union {
    
    __fp16 f;
    struct
    {
        unsigned int mantissa : 10;
        unsigned int exponent : 5;
        unsigned int sign : 1;
 
    } raw;
} myfloat;
 

// Driver Code
int main()
{
    myfloat var;
    var.f = 11;
    printf("%d | ", var.raw.sign);
    printBinary(var.raw.exponent, 5);
    printf(" | ");
    printBinary(var.raw.mantissa, 10);
    printf("\n");
    return 0;
}

ターミナルでコードをコンパイルします

clang code_name.c -o code_name
./code_name

ここ

__fp16

clangCコンパイラでサポートされている2バイトのfloatデータ型です

于 2022-02-17T17:50:11.700 に答える