32ビットと16ビットの浮動小数点数の間で変換するクロスプラットフォームのライブラリ/アルゴリズムが必要です。 16ビットの数値を使用して数学を実行する必要はありません。ネットワーク経由で送信できるように、32ビットのfloatのサイズを小さくする必要があります。私はC++で働いています。
精度がどれだけ失われるかは理解していますが、アプリケーションにはそれで問題ありません。
IEEE 16ビット形式は素晴らしいでしょう。
std::frexp
は、通常の浮動小数点数または倍精度数から仮数と指数を抽出します。次に、大きすぎて半精度浮動小数点数に収まらない(飽和...?)指数をどうするかを決定する必要があります。半精度数をまとめます。 この記事 変換の実行方法を示すCソースコードがあります。
単精度から半精度への完全な変換。これはmy SSEバージョンからの直接コピーなので、ブランチレスです。GCCでは(-true ==〜0)、VisualStudioにも当てはまる可能性があるという事実を利用していますが、 、私はコピーを持っていません。
class Float16Compressor
{
union Bits
{
float f;
int32_t si;
uint32_t ui;
};
static int const shift = 13;
static int const shiftSign = 16;
static int32_t const infN = 0x7F800000; // flt32 infinity
static int32_t const maxN = 0x477FE000; // max flt16 normal as a flt32
static int32_t const minN = 0x38800000; // min flt16 normal as a flt32
static int32_t const signN = 0x80000000; // flt32 sign bit
static int32_t const infC = infN >> shift;
static int32_t const nanN = (infC + 1) << shift; // minimum flt16 nan as a flt32
static int32_t const maxC = maxN >> shift;
static int32_t const minC = minN >> shift;
static int32_t const signC = signN >> shiftSign; // flt16 sign bit
static int32_t const mulN = 0x52000000; // (1 << 23) / minN
static int32_t const mulC = 0x33800000; // minN / (1 << (23 - shift))
static int32_t const subC = 0x003FF; // max flt32 subnormal down shifted
static int32_t const norC = 0x00400; // min flt32 normal down shifted
static int32_t const maxD = infC - maxC - 1;
static int32_t const minD = minC - subC - 1;
public:
static uint16_t compress(float value)
{
Bits v, s;
v.f = value;
uint32_t sign = v.si & signN;
v.si ^= sign;
sign >>= shiftSign; // logical shift
s.si = mulN;
s.si = s.f * v.f; // correct subnormals
v.si ^= (s.si ^ v.si) & -(minN > v.si);
v.si ^= (infN ^ v.si) & -((infN > v.si) & (v.si > maxN));
v.si ^= (nanN ^ v.si) & -((nanN > v.si) & (v.si > infN));
v.ui >>= shift; // logical shift
v.si ^= ((v.si - maxD) ^ v.si) & -(v.si > maxC);
v.si ^= ((v.si - minD) ^ v.si) & -(v.si > subC);
return v.ui | sign;
}
static float decompress(uint16_t value)
{
Bits v;
v.ui = value;
int32_t sign = v.si & signC;
v.si ^= sign;
sign <<= shiftSign;
v.si ^= ((v.si + minD) ^ v.si) & -(v.si > subC);
v.si ^= ((v.si + maxD) ^ v.si) & -(v.si > maxC);
Bits s;
s.si = mulC;
s.f *= v.si;
int32_t mask = -(norC > v.si);
v.si <<= shift;
v.si ^= (s.si ^ v.si) & mask;
v.si |= sign;
return v.f;
}
};
したがって、それは多くのことを取り入れる必要がありますが、無限、静かなNaN、シグナルNaN、負のゼロの両方のすべての非正規値を処理します。もちろん、IEEEの完全なサポートが常に必要なわけではありません。したがって、汎用フロートの圧縮:
class FloatCompressor
{
union Bits
{
float f;
int32_t si;
uint32_t ui;
};
bool hasNegatives;
bool noLoss;
int32_t _maxF;
int32_t _minF;
int32_t _epsF;
int32_t _maxC;
int32_t _zeroC;
int32_t _pDelta;
int32_t _nDelta;
int _shift;
static int32_t const signF = 0x80000000;
static int32_t const absF = ~signF;
public:
FloatCompressor(float min, float epsilon, float max, int precision)
{
// legal values
// min <= 0 < epsilon < max
// 0 <= precision <= 23
_shift = 23 - precision;
Bits v;
v.f = min;
_minF = v.si;
v.f = epsilon;
_epsF = v.si;
v.f = max;
_maxF = v.si;
hasNegatives = _minF < 0;
noLoss = _shift == 0;
int32_t pepsU, nepsU;
if(noLoss) {
nepsU = _epsF;
pepsU = _epsF ^ signF;
_maxC = _maxF ^ signF;
_zeroC = signF;
} else {
nepsU = uint32_t(_epsF ^ signF) >> _shift;
pepsU = uint32_t(_epsF) >> _shift;
_maxC = uint32_t(_maxF) >> _shift;
_zeroC = 0;
}
_pDelta = pepsU - _zeroC - 1;
_nDelta = nepsU - _maxC - 1;
}
float clamp(float value)
{
Bits v;
v.f = value;
int32_t max = _maxF;
if(hasNegatives)
max ^= (_minF ^ _maxF) & -(0 > v.si);
v.si ^= (max ^ v.si) & -(v.si > max);
v.si &= -(_epsF <= (v.si & absF));
return v.f;
}
uint32_t compress(float value)
{
Bits v;
v.f = clamp(value);
if(noLoss)
v.si ^= signF;
else
v.ui >>= _shift;
if(hasNegatives)
v.si ^= ((v.si - _nDelta) ^ v.si) & -(v.si > _maxC);
v.si ^= ((v.si - _pDelta) ^ v.si) & -(v.si > _zeroC);
if(noLoss)
v.si ^= signF;
return v.ui;
}
float decompress(uint32_t value)
{
Bits v;
v.ui = value;
if(noLoss)
v.si ^= signF;
v.si ^= ((v.si + _pDelta) ^ v.si) & -(v.si > _zeroC);
if(hasNegatives)
v.si ^= ((v.si + _nDelta) ^ v.si) & -(v.si > _maxC);
if(noLoss)
v.si ^= signF;
else
v.si <<= _shift;
return v.f;
}
};
これにより、すべての値が許容範囲内に強制され、NaN、無限大、または負のゼロはサポートされません。イプシロンは、範囲内の最小許容値です。精度は、フロートから保持する精度のビット数です。上記には多くのブランチがありますが、それらはすべて静的であり、CPUのブランチプレディクタによってキャッシュされます。
もちろん、値がゼロに近い対数分解能を必要としない場合。既に述べたように、それらを固定小数点形式に線形化することははるかに高速です。
メモリ内の線形フロートカラー値のサイズを小さくするために、グラフィックライブラリのFloatCompressor(SSEバージョン)を使用します。圧縮されたフロートには、ガンマ補正や超越関数などの時間のかかる機能用の小さなルックアップテーブルを作成できるという利点があります。線形sRGB値を圧縮すると、最大12ビットまたは最大値3011に減少します。これは、sRGBとの間のルックアップテーブルサイズに最適です。
ニーズ(-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に変更すると、もう少し正確になりますが、一部の整数はもう一方の端で小数になります。
半分が浮く: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);
情報のストリームを送信する場合、特にすべてが一定の範囲内にある場合、アプリケーションが持っているように見えるため、これよりもうまくいく可能性があります。
Float32の最小値と最大値だけで構成される小さなヘッダーを送信すると、2つの間の16ビットの補間値として情報を送信できます。また、精度はさほど問題ではないと言うように、一度に8ビットを送信することさえできます。
再構築時の値は次のようになります。
float t = _t / numeric_limits<unsigned short>::max(); // With casting, naturally ;)
float val = h.min + t * (h.max - h.min);
お役に立てば幸いです。
-トム
ここの他の回答で説明されているアプローチのほとんどは、floatからhalfへの変換時に正しく丸められないか、2 **-14がゼロ以外の最小数になるため問題である非正規数を捨てるか、Inf /で不幸なことをしますナンInfも問題です。なぜなら、半分の最大有限数は2 ^ 16より少し小さいからです。 OpenEXRは不必要に遅く複雑でした。最後に私はそれを見ました。高速で正しいアプローチでは、FPUを使用して直接命令として、またはFPU丸めハードウェアを使用して変換を行い、正しいことを実現します。半分から浮動小数点への変換は、2 ^ 16要素のルックアップテーブルより遅くなることはありません。
以下は打ちにくいです:
OS X/iOSでは、vImageConvert_PlanarFtoPlanar16FおよびvImageConvert_Planar16FtoPlanarFを使用できます。 Accelerate.frameworkを参照してください。
Intel ivybridgeがSSEの説明を追加しました。f16cintrin.hを参照してください。同様の指示がARM ISA Neonに対して追加されました。 arm_neon.hのvcvt_f32_f16およびvcvt_f16_f32を参照してくださいiOSでは、それらにアクセスするにはarm64またはarmv7s Archを使用する必要があります。
このコードは、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;
}
Intel icpc 16.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
これらの組み込み関数に関するドキュメントは、次の場所にあります。
この質問はもう少し古いですが、完全を期すために、half-to-floatおよびfloat-to-half変換について このペーパー を参照することもできます。
比較的小さなルックアップテーブルを使用したブランチレステーブル駆動アプローチを使用します。 IEEEに完全に準拠しており、PhernostのIEEEに準拠したブランチレス変換ルーチンのパフォーマンス(少なくとも私のマシンでは)に勝っています。しかしもちろん、彼のコードはSSEにより適しています。また、メモリレイテンシの影響を受けやすいというわけではありません。
16-32ビット浮動小数点のこの変換は、無限大やNaNを考慮する必要がない場合に非常に高速であり、非正規化(DAZ)を受け入れることができます。つまりパフォーマンスに敏感な計算に適していますが、非正規化が発生することが予想される場合は、ゼロによる除算に注意する必要があります。
これは、x86または条件付きの移動または「set if」同等物を持つ他のプラットフォームに最も適していることに注意してください。
単精度から半精度への逆の場合は、いくつかの追加があります。
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 をご覧ください。
私は 実装 が半浮動小数点形式から単一浮動小数点形式に変換され、AVX2を使用して戻ることを発見しました。これらのアルゴリズムのソフトウェア実装よりもはるかに高速です。役に立つことを願っています。
32ビット浮動小数点から16ビット浮動小数点への変換:
#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ビット浮動小数点から32ビット浮動小数点への変換:
#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);
}
質問は古く、すでに回答されていますが、16ビットIEEE準拠の半精度浮動小数点数を作成でき、組み込みの浮動小数点型とほとんど同じように動作するクラスを持つオープンソースC++ライブラリに言及する価値があると考えました32ビットではなく16ビット。これは OpenEXRライブラリの「半分」クラス です。コードは寛容なBSDスタイルのライセンスの下にあります。標準ライブラリ以外の依存関係があるとは思わない。
私はこれとまったく同じ問題を抱えていましたが、 このリンク は非常に役立ちました。ファイル「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