C++標準では、float型とdouble型の基本的なレイアウトについては説明されておらず、それらが表す必要のある値の範囲についてのみ説明されています。 (これは符号付き型にも当てはまります。2の補数か何か他のものですか)
私の質問は、doubleやfloatなどのPODタイプをポータブルな方法でシリアル化/逆シリアル化するために使用される手法は何ですか?現時点では、これを行う唯一の方法は、値を文字通りに表現することです( "123.456"のように)。doubleのieee754レイアウトは、すべてのアーキテクチャで標準ではありません。
Brian "Beej Jorgensen" Hallは、彼の ネットワークプログラミングガイドfloat
(またはdouble
)をuint32_t
(またはuint64_t
)両方がそれらの表現に同意しないかもしれない2台のマシン間のネットワークを介してそれを安全に送信できるようにするため。いくつかの制限があり、主にNaNと無限大をサポートしていません。
彼のパッキング機能は次のとおりです。
#define pack754_32(f) (pack754((f), 32, 8))
#define pack754_64(f) (pack754((f), 64, 11))
uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
long double fnorm;
int shift;
long long sign, exp, significand;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (f == 0.0) return 0; // get this special case out of the way
// check sign and begin normalization
if (f < 0) { sign = 1; fnorm = -f; }
else { sign = 0; fnorm = f; }
// get the normalized form of f and track the exponent
shift = 0;
while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
fnorm = fnorm - 1.0;
// calculate the binary form (non-float) of the significand data
significand = fnorm * ((1LL<<significandbits) + 0.5f);
// get the biased exponent
exp = shift + ((1<<(expbits-1)) - 1); // shift + bias
// return the final answer
return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}
人間が読める形式の何が問題になっていますか。
バイナリに比べていくつかの利点があります。
不利益:
htonl()
を参照)最高精度でdoubleを出力するには:
double v = 2.20;
std::cout << std::setprecision(std::numeric_limits<double>::digits) << v;
OK。私はそれが正確であるとは確信していません。精度が低下する場合があります。
Glib 2の(古い)gtypes.hファイルの実装を見てください。これには次のものが含まれます。
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
union _GFloatIEEE754
{
gfloat v_float;
struct {
guint mantissa : 23;
guint biased_exponent : 8;
guint sign : 1;
} mpn;
};
union _GDoubleIEEE754
{
gdouble v_double;
struct {
guint mantissa_low : 32;
guint mantissa_high : 20;
guint biased_exponent : 11;
guint sign : 1;
} mpn;
};
#Elif G_BYTE_ORDER == G_BIG_ENDIAN
union _GFloatIEEE754
{
gfloat v_float;
struct {
guint sign : 1;
guint biased_exponent : 8;
guint mantissa : 23;
} mpn;
};
union _GDoubleIEEE754
{
gdouble v_double;
struct {
guint sign : 1;
guint biased_exponent : 11;
guint mantissa_high : 20;
guint mantissa_low : 32;
} mpn;
};
#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
#error unknown ENDIAN type
#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
バイナリIEEE754表現をディスクに書き込み、これをストレージ形式として文書化します(エンディアンも一緒に)。次に、必要に応じてこれを内部表現に変換するのは実装次第です。
これを書き込む/読み取るための適切なシリアライザー/デシリアライザーインターフェイスを作成します。
その後、インターフェースにいくつかの実装を含めることができ、オプションをテストできます。
前に述べたように、明らかなオプションは次のとおりです。
覚えておいてください。このレイヤーを作成したら、この形式を内部で使用するプラットフォームのみをサポートしている場合は、いつでもIEEE754から始めることができます。このようにして、別のプラットフォームをサポートする必要がある場合にのみ、追加の作業が必要になります。あなたがする必要のない仕事をしないでください。
フロート/ダブルを再作成するために、常に使用できる形式に変換する必要があります。
これは文字列表現を使用するか、より少ないスペースが必要な場合は、ieee754(または選択した他の形式)で数値を表現してから、文字列の場合と同じようにparseそれを使用できます。
SQLite4は、新しい形式を使用してdoubleとfloatを格納します
- IEEE 754binary64浮動小数点数のサポートがないプラットフォームでも確実かつ一貫して動作します。
- 通貨の計算は通常、正確に丸めることなく実行できます。
- 符号付きまたは符号なしの64ビット整数は正確に表すことができます。
- 浮動小数点の範囲と精度は、IEEE 754binary64浮動小数点数のそれを超えています。
- 正と負の無限大とNaN(Not-a-Number)には、明確に定義された表現があります。
出典:
この古いスレッドが見つかりました。かなりの数のケースを解決する1つの解決策がありません。固定小数点を使用し、両端に組み込みのキャストを使用して既知のスケーリング係数で整数を渡します。したがって、基になる浮動小数点表現を気にする必要はまったくありません。
もちろん欠点もあります。このソリューションは、固定の倍率を持ちながら、特定のアプリケーションに必要な範囲と解像度の両方を取得できることを前提としています。さらに、シリアル化の最後で浮動小数点から固定小数点に変換し、逆シリアル化で元に戻すと、2つの丸め誤差が発生します。しかし、何年にもわたって、ほとんどすべての場合、固定小数点で十分であり、適度に高速であることがわかりました。
固定小数点の一般的なケースは、組み込みシステムまたはその他のデバイスの通信プロトコルです。
答えは、特定のアプリケーションとそのパフォーマンスプロファイルによって「異なる」と思います。
レイテンシの低い市場データ環境があるとしましょう。文字列の使用は率直に言って巧妙です。あなたが伝えている情報が価格である場合、double(およびそれらのバイナリ表現)は実際に扱うのが難しいです。一方、パフォーマンスをあまり気にせず、可視性(ストレージ、送信)が必要な場合は、文字列が理想的な候補です。
私は実際にfloat/doubleの整数仮数/指数表現を選択します-つまり、最も早い機会に、float/doubleを整数のペアに変換してから送信します。そうすれば、整数の移植性と、さまざまなルーチン(変換を処理するためのhton()
ルーチンなど)についてのみ心配する必要があります。また、最も普及しているプラットフォームのエンディアンにすべてを保存します(たとえば、Linuxのみを使用している場合、ビッグエンディアンにデータを保存する意味は何ですか?)