2016-11-04 6 views
6

CおよびC++で32ビット浮動小数点変数をシリアル化してマイクロコントローラとの間でやりとりするポータブルな方法を見つけることに苦労しています。あまりにも多くの労力を費やすことなく、シリアライゼーション/デシリアライゼーションを他の言語からも行うことができるように、フォーマットを十分に定義しておきたい。関連した質問は以下のとおりです。floatを32ビット整数としてシリアル化する移植可能な方法

Portability of binary serialization of double/float type in C++

Serialize double and float with C

c++ portable conversion of long to double

私はほとんどの場合、はフロート表現が同じであるため、組合/ memcpyのはうまく動作します型キャストが、私ことを知っていますもう少しコントロールと心の部分を持っていることを好むだろう。私は今のところ思い付いたことは以下の通りです:

void serialize_float32(uint8_t* buffer, float number, int32_t *index) { 
    int e = 0; 
    float sig = frexpf(number, &e); 
    float sig_abs = fabsf(sig); 
    uint32_t sig_i = 0; 

    if (sig_abs >= 0.5) { 
     sig_i = (uint32_t)((sig_abs - 0.5f) * 2.0f * 8388608.0f); 
     e += 126; 
    } 

    uint32_t res = ((e & 0xFF) << 23) | (sig_i & 0x7FFFFF); 
    if (sig < 0) { 
     res |= 1 << 31; 
    } 

    buffer[(*index)++] = (res >> 24) & 0xFF; 
    buffer[(*index)++] = (res >> 16) & 0xFF; 
    buffer[(*index)++] = (res >> 8) & 0xFF; 
    buffer[(*index)++] = res & 0xFF; 
} 

float deserialize_float32(const uint8_t *buffer, int32_t *index) { 
    uint32_t res = ((uint32_t) buffer[*index]) << 24 | 
       ((uint32_t) buffer[*index + 1]) << 16 | 
       ((uint32_t) buffer[*index + 2]) << 8 | 
       ((uint32_t) buffer[*index + 3]); 
    *index += 4; 

    int e = (res >> 23) & 0xFF; 
    uint32_t sig_i = res & 0x7FFFFF; 
    bool neg = res & (1 << 31); 

    float sig = 0.0; 
    if (e != 0 || sig_i != 0) { 
     sig = (float)sig_i/(8388608.0 * 2.0) + 0.5; 
     e -= 126; 
    } 

    if (neg) { 
     sig = -sig; 
    } 

    return ldexpf(sig, e); 
} 

私がしようとしたfrexpldexp機能は、この目的のために作られているように見えるが、場合には、それらは使用できません。

float frexpf_slow(float f, int *e) { 
    if (f == 0.0) { 
     *e = 0; 
     return 0.0; 
    } 

    *e = ceil(log2f(fabsf(f))); 
    float res = f/powf(2.0, (float)*e); 

    // Make sure that the magnitude stays below 1 so that no overflow occurs 
    // during serialization. This seems to be required after doing some manual 
    // testing. 

    if (res >= 1.0) { 
     res -= 0.5; 
     *e += 1; 
    } 

    if (res <= -1.0) { 
     res += 0.5; 
     *e += 1; 
    } 

    return res; 
} 

:共通している機能を使用して手動でだけでなく、それらを実装します
float ldexpf_slow(float f, int e) { 
    return f * powf(2.0, (float)e); 
} 

私が検討してきたことの1つは、8388608(2^23)か8388607(2^23-1)を乗算器として使用するかどうかです。 frexpは1より小さい値を返し、いくつかの実験の後には実際の浮動小数点でビット精度の結果が得られ、このオーバーフローが発生するコーナーケースを見つけることができなかったようです。しかしそれは異なるコンパイラ/システムでは当てはまらないかもしれません。これが問題になる可能性がある場合は、精度を落とす小さな乗算器も私にとっては問題ありません。私はこれがInfやNaNを処理しないことを知っていますが、今のところこれは必須条件ではありません。

最後に、私の質問です:これは合理的なアプローチのようですか、それとも移植性の問題がある複雑なソリューションを作成していますか?フロートを仮定

+0

のいずれかを使用する必要があります。短い答え:例えばgoogle protobufのようなデシリアライズライブラリ/ツールを使用しない限り、移植可能な方法では実際にはできません。 –

+0

私が提示したアプローチの問題点は何ですか?私が普段読んでいるのは、浮動小数点表現がすべてのシステムで同じであることを保証できないということです。そのため、浮動小数点の内部表現が何であるかにかかわらず常に同じものを生成しようとします。 –

+0

_Endianess_のいずれかの問題の名前を指定します。 –

答えて

4

あなたはserialize_floatのバグを持っているようだ。最後4行は次のようになります。

buffer[(*index)++] = (res >> 24) & 0xFF; 
buffer[(*index)++] = (res >> 16) & 0xFF; 
buffer[(*index)++] = (res >> 8) & 0xFF; 
buffer[(*index)++] = res & 0xFF; 

あなたの方法は126の代わり128によって相殺ための無限大および/またはNaNのために正しく動作しない可能性があります。広範なテストによって検証することができます:40億の値しかありませんが、すべての可能性をあまり長く取らないようにしてください。

floatの実際のメモリ表現は、アーキテクチャによって異なる場合がありますが、今日ではIEEE 854(またはより正確にはIEC 60559)が広く普及しています。 __STDC_IEC_559__が定義されているかどうかをチェックすることで、特定のターゲットが準拠しているかどうかを確認できます。ただし、IEEE 854の場合でも、システム間の潜在的に異なるエンディアンを処理する必要があります。 floatのエンディアンは、同じプラットフォームの整数のエンディアンと同じであると想定できません。

単純なキャストは正しくないことにも注意してください。uint32_t res = *(uint32_t *)&number;は厳密なエイリアシング規則に違反します。 unionを使用するか、memcpy(&res, &number, sizeof(res));

+0

ありがとう!コピーペーストエラーです。私は実際にそれを行うための余分な機能を持っていますが、質問のために私は同じことをして何が起こっているのかをより簡単に見えるようにします。 –

+0

ループについての良い点!私は実際にInfとNaN以外のすべてを使ってその夜を過ごしました。そして、私のラップトップでは、すべての値に対して正常に動作するようです。私は他のシステムについてはわかりません。 –

+0

シフトの全体的なポイントは、エンドエンティティを完全に避けることです。 – 2501

4

は、仮数、指数および符号を抽出する、IEEE 754形式であり、完全にポータブルである:

uint32_t internal; 
float value = //...some value 
memcpy(&internal , &value , sizeof(value)); 

const uint32_t sign =  (internal >> 31u) & 0x1u; 
const uint32_t mantissa = (internal >> 0u ) & 0x7FFFFFu; 
const uint32_t exponent = (internal >> 23u) & 0xFFu; 

反転フロートを構築する手順。

フロート全体のみを送信する場合は、バッファにコピーしてください。これは、フロートが754 IEEEされていない場合でも動作しますが、それは32ビットでなければならず、整数と浮動小数点型の両方のエンディアンが同じでなければなりません:

buffer[0] = (internal >> 0u ) & 0xFFu; 
buffer[1] = (internal >> 8u ) & 0xFFu; 
buffer[2] = (internal >> 16u) & 0xFFu; 
buffer[3] = (internal >> 24u) & 0xFFu; 
+0

私はそれらをまったく抽出する必要がないと仮定しているなら、すぐに型キャストを行うことができます。 –

+0

@BenjaminVedderどういう意味ですか? – 2501

+0

それから、私はそれをすることができました: uint32_t internal; 浮動小数点値= //...some値 memcpy(&internal、&value、sizeof(value)); バッファ[(* index)++] =(内部>> 24)& 0xFF; バッファ[(*インデックス)++] =(内部>> 16)& 0xFF; バッファ[ >> 8)& 0xFF; バッファ[(* index)++] =内部& 0xFF; これを複雑にすることのポイントは、非標準の浮動小数点表現の場合に対処できるようにしたいということです。実際には2016年の問題ではありません。 (編集:フォーマットについてごめん、コメントは改行をサポートしていないようです) –

関連する問題