2013-02-19 2 views
8

私は自分のアプリケーションのファイル形式を工夫しています。ビッグエンディアンとリトルエンディアンの両方のシステムで動作することは明らかです。私は既にhtonlntohlを使用して整数型を管理するための作業ソリューションを見つけましたが、floatdoubleの値で同じことをしようとすると少し固まってしまいます。Cで浮動小数点型を読み書きするときに、バイトオーダーの違いを処理するにはどうすればよいですか?

浮動小数点表現がどのように機能するかを考えると、標準のバイトオーダー関数はこれらの値では機能しないと考えられます。同様に、伝統的な意味でのエンディアンがこれらのタイプのバイトオーダーを支配するものであるかどうかは完全にはわかりません。

私が必要とするのは一貫性です。 doubleを書いて、それを読み込んだときに同じ値を得られるようにする方法。Cでこれを行うにはどうすればいいですか?

+2

テキストとして保存するオプションはありませんか? – qPCR4vir

+0

@ qPCR4virそれは多くのパフォーマンスを殺す可能性があります。 – fuz

+0

HDF5(http://www.hdfgroup.org/HDF5/)のようなライブラリは、あなたのためにそれをすべて処理します。私はHDF5があなたのニーズに少し重いかもしれないと思う。 –

答えて

11

(C99)のdouble frexp(double value, int *exp);を使用して、浮動小数点値を正規化された小数部([0.5,1]の範囲内)と2のべき乗に分解することもできます。範囲の整数を取得するFLT_RADIXDBL_MANT_DIGによって分率[FLT_RADIXDBL_MANT_DIG/2、FLT_RADIXDBL_MANT_DIG)。次に、あなたのフォーマットでどちらを選択しても、ビッグエンディアンまたはリトルエンディアンの両方の整数を保存します。

保存した数値をロードするときは、逆の操作を行い、double ldexp(double x, int exp);を使用して、再構成された割合に2の累乗を掛けます。

これは、FLT_RADIX = 2(事実上すべてのシステムと思われますか?)とDBL_MANT_DIG < = 64の場合に最適です。

オーバーフローを避けるため、注意が必要です。 doublesため

サンプルコード:

#include <limits.h> 
#include <float.h> 
#include <math.h> 
#include <string.h> 
#include <stdio.h> 

#if CHAR_BIT != 8 
#error currently supported only CHAR_BIT = 8 
#endif 

#if FLT_RADIX != 2 
#error currently supported only FLT_RADIX = 2 
#endif 

#ifndef M_PI 
#define M_PI 3.14159265358979324 
#endif 

typedef unsigned char uint8; 

/* 
    10-byte little-endian serialized format for double: 
    - normalized mantissa stored as 64-bit (8-byte) signed integer: 
     negative range: (-2^53, -2^52] 
     zero: 0 
     positive range: [+2^52, +2^53) 
    - 16-bit (2-byte) signed exponent: 
     range: [-0x7FFE, +0x7FFE] 

    Represented value = mantissa * 2^(exponent - 53) 

    Special cases: 
    - +infinity: mantissa = 0x7FFFFFFFFFFFFFFF, exp = 0x7FFF 
    - -infinity: mantissa = 0x8000000000000000, exp = 0x7FFF 
    - NaN:  mantissa = 0x0000000000000000, exp = 0x7FFF 
    - +/-0:  only one zero supported 
*/ 

void Double2Bytes(uint8 buf[10], double x) 
{ 
    double m; 
    long long im; // at least 64 bits 
    int ie; 
    int i; 

    if (isnan(x)) 
    { 
    // NaN 
    memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\xFF\x7F", 10); 
    return; 
    } 
    else if (isinf(x)) 
    { 
    if (signbit(x)) 
     // -inf 
     memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10); 
    else 
     // +inf 
     memcpy(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10); 
    return; 
    } 

    // Split double into normalized mantissa (range: (-1, -0.5], 0, [+0.5, +1)) 
    // and base-2 exponent 
    m = frexp(x, &ie); // x = m * 2^ie exactly for FLT_RADIX=2 
        // frexp() can't fail 
    // Extract most significant 53 bits of mantissa as integer 
    m = ldexp(m, 53); // can't overflow because 
        // DBL_MAX_10_EXP >= 37 equivalent to DBL_MAX_2_EXP >= 122 
    im = trunc(m); // exact unless DBL_MANT_DIG > 53 

    // If the exponent is too small or too big, reduce the number to 0 or 
    // +/- infinity 
    if (ie > 0x7FFE) 
    { 
    if (im < 0) 
     // -inf 
     memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10); 
    else 
     // +inf 
     memcpy(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10); 
    return; 
    } 
    else if (ie < -0x7FFE) 
    { 
    // 0 
    memcpy(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00", 10); 
    return; 
    } 

    // Store im as signed 64-bit little-endian integer 
    for (i = 0; i < 8; i++, im >>= 8) 
    buf[i] = (uint8)im; 

    // Store ie as signed 16-bit little-endian integer 
    for (i = 8; i < 10; i++, ie >>= 8) 
    buf[i] = (uint8)ie; 
} 

void Bytes2Double(double* x, const uint8 buf[10]) 
{ 
    unsigned long long uim; // at least 64 bits 
    long long im; // ditto 
    unsigned uie; 
    int ie; 
    double m; 
    int i; 
    int negative = 0; 
    int maxe; 

    if (!memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\x00" "\xFF\x7F", 10)) 
    { 
#ifdef NAN 
    *x = NAN; 
#else 
    *x = 0; // NaN is not supported, use 0 instead (we could return an error) 
#endif 
    return; 
    } 

    if (!memcmp(buf, "\x00\x00\x00\x00\x00\x00\x00\x80" "\xFF\x7F", 10)) 
    { 
    *x = -INFINITY; 
    return; 
    } 
    else if (!memcmp(buf, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F" "\xFF\x7F", 10)) 
    { 
    *x = INFINITY; 
    return; 
    } 

    // Load im as signed 64-bit little-endian integer 
    uim = 0; 
    for (i = 0; i < 8; i++) 
    { 
    uim >>= 8; 
    uim |= (unsigned long long)buf[i] << (64 - 8); 
    } 
    if (uim <= 0x7FFFFFFFFFFFFFFFLL) 
    im = uim; 
    else 
    im = (long long)(uim - 0x7FFFFFFFFFFFFFFFLL - 1) - 0x7FFFFFFFFFFFFFFFLL - 1; 

    // Obtain the absolute value of the mantissa, make sure it's 
    // normalized and fits into 53 bits, else the input is invalid 
    if (im > 0) 
    { 
    if (im < (1LL << 52) || im >= (1LL << 53)) 
    { 
#ifdef NAN 
     *x = NAN; 
#else 
     *x = 0; // NaN is not supported, use 0 instead (we could return an error) 
#endif 
     return; 
    } 
    } 
    else if (im < 0) 
    { 
    if (im > -(1LL << 52) || im <= -(1LL << 53)) 
    { 
#ifdef NAN 
     *x = NAN; 
#else 
     *x = 0; // NaN is not supported, use 0 instead (we could return an error) 
#endif 
     return; 
    } 
    negative = 1; 
    im = -im; 
    } 

    // Load ie as signed 16-bit little-endian integer 
    uie = 0; 
    for (i = 8; i < 10; i++) 
    { 
    uie >>= 8; 
    uie |= (unsigned)buf[i] << (16 - 8); 
    } 
    if (uie <= 0x7FFF) 
    ie = uie; 
    else 
    ie = (int)(uie - 0x7FFF - 1) - 0x7FFF - 1; 

    // If DBL_MANT_DIG < 53, truncate the mantissa 
    im >>= (53 > DBL_MANT_DIG) ? (53 - DBL_MANT_DIG) : 0; 

    m = im; 
    m = ldexp(m, (53 > DBL_MANT_DIG) ? -DBL_MANT_DIG : -53); // can't overflow 
      // because DBL_MAX_10_EXP >= 37 equivalent to DBL_MAX_2_EXP >= 122 

    // Find out the maximum base-2 exponent and 
    // if ours is greater, return +/- infinity 
    frexp(DBL_MAX, &maxe); 
    if (ie > maxe) 
    m = INFINITY; 
    else 
    m = ldexp(m, ie); // underflow may cause a floating-point exception 

    *x = negative ? -m : m; 
} 

int test(double x, const char* name) 
{ 
    uint8 buf[10], buf2[10]; 
    double x2; 
    int error1, error2; 

    Double2Bytes(buf, x); 
    Bytes2Double(&x2, buf); 
    Double2Bytes(buf2, x2); 

    printf("%+.15E '%s' -> %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", 
     x, 
     name, 
     buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9]); 

    if ((error1 = memcmp(&x, &x2, sizeof(x))) != 0) 
    puts("Bytes2Double(Double2Bytes(x)) != x"); 

    if ((error2 = memcmp(buf, buf2, sizeof(buf))) != 0) 
    puts("Double2Bytes(Bytes2Double(Double2Bytes(x))) != Double2Bytes(x)"); 

    puts(""); 

    return error1 || error2; 
} 

int testInf(void) 
{ 
    uint8 buf[10]; 
    double x, x2; 
    int error; 

    x = DBL_MAX; 
    Double2Bytes(buf, x); 
    if (!++buf[8]) 
    ++buf[9]; // increment the exponent beyond the maximum 
    Bytes2Double(&x2, buf); 

    printf("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X -> %+.15E\n", 
     buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9], 
     x2); 

    if ((error = !isinf(x2)) != 0) 
    puts("Bytes2Double(Double2Bytes(DBL_MAX) * 2) != INF"); 

    puts(""); 

    return error; 
} 

#define VALUE_AND_NAME(V) { V, #V } 

const struct 
{ 
    double value; 
    const char* name; 
} testData[] = 
{ 
#ifdef NAN 
    VALUE_AND_NAME(NAN), 
#endif 
    VALUE_AND_NAME(0.0), 
    VALUE_AND_NAME(+DBL_MIN), 
    VALUE_AND_NAME(-DBL_MIN), 
    VALUE_AND_NAME(+1.0), 
    VALUE_AND_NAME(-1.0), 
    VALUE_AND_NAME(+M_PI), 
    VALUE_AND_NAME(-M_PI), 
    VALUE_AND_NAME(+DBL_MAX), 
    VALUE_AND_NAME(-DBL_MAX), 
    VALUE_AND_NAME(+INFINITY), 
    VALUE_AND_NAME(-INFINITY), 
}; 

int main(void) 
{ 
    unsigned i; 
    int errors = 0; 

    for (i = 0; i < sizeof(testData)/sizeof(testData[0]); i++) 
    errors += test(testData[i].value, testData[i].name); 

    errors += testInf(); 

    // Test subnormal values. A floating-point exception may be raised. 
    errors += test(+DBL_MIN/2, "+DBL_MIN/2"); 
    errors += test(-DBL_MIN/2, "-DBL_MIN/2"); 

    printf("%d error(s)\n", errors); 

    return 0; 
} 

出力(ideone):

+NAN 'NAN' -> 00 00 00 00 00 00 00 00 FF 7F 

+0.000000000000000E+00 '0.0' -> 00 00 00 00 00 00 00 00 00 00 

+2.225073858507201E-308 '+DBL_MIN' -> 00 00 00 00 00 00 10 00 03 FC 

-2.225073858507201E-308 '-DBL_MIN' -> 00 00 00 00 00 00 F0 FF 03 FC 

+1.000000000000000E+00 '+1.0' -> 00 00 00 00 00 00 10 00 01 00 

-1.000000000000000E+00 '-1.0' -> 00 00 00 00 00 00 F0 FF 01 00 

+3.141592653589793E+00 '+M_PI' -> 18 2D 44 54 FB 21 19 00 02 00 

-3.141592653589793E+00 '-M_PI' -> E8 D2 BB AB 04 DE E6 FF 02 00 

+1.797693134862316E+308 '+DBL_MAX' -> FF FF FF FF FF FF 1F 00 00 04 

-1.797693134862316E+308 '-DBL_MAX' -> 01 00 00 00 00 00 E0 FF 00 04 

+INF '+INFINITY' -> FF FF FF FF FF FF FF 7F FF 7F 

-INF '-INFINITY' -> 00 00 00 00 00 00 00 80 FF 7F 

FF FF FF FF FF FF 1F 00 01 04 -> +INF 

+1.112536929253601E-308 '+DBL_MIN/2' -> 00 00 00 00 00 00 10 00 02 FC 

-1.112536929253601E-308 '-DBL_MIN/2' -> 00 00 00 00 00 00 F0 FF 02 FC 

0 error(s) 
+0

よかった、ありがとう。私は唯一の懸念が残っていると思う。どのようにそれらを処理することをお勧めしますか?また、この方法は正確ですか? –

+0

浮動小数点値が2の場合、これは正確でなければなりません。実行可能なオーバーフローは、CPUの書式の方が、書式よりも指数の範囲が広いか、逆の場合です。これをチェックし、無限大がサポートされていない場合は、無限大または最大値に特殊値を使用する必要があります。仮数の桁数/ビット数に多少似た問題があるかもしれません。 CPUまたはファイルスロットに適合しない場合は、切り捨てまたは丸めする必要があります。 –

+0

IEEE-754のコードを最適化することで、CPUがIEEE-754をサポートしている場合は何も特別なことはしません。 –

2

アプリケーションによっては、プレーンテキストのデータ形式(XMLである可能性があります)を使用することをお勧めします。ディスクスペースを無駄にしたくない場合は、圧縮することができます。

+0

浮動小数点値をテキストとして書き出すときは、 '%a'は'%f'/'%e' /'%g'よりも良い選択です。読み込み可能ではありませんが、小数点の切り捨てやあまりにも多くの文字数の切り捨ては避けてください。 –

3

浮動小数点値は整数値imhoと同じバイトオーダーを使用します。

float htonf(float x) { 
    union foo { 
    float f; 
    uint32_t i; 
    } foo = { .f = x }; 

    foo.i = htonl(foo.i); 
    return foo.f; 
} 
+0

intsとfloatのエンディアンが異なるプラットフォームがありました。しかし、私たちはもはやそれらをサポートする気にはならないかもしれません。 –

0

XMLは、おそらくそれを行うには、最もポータブルな方法です。それぞれの整数相手とそれらをオーバーラップし、共通hton機能を使用するために労働組合を使用してください。

しかし、すでにパーサーのほとんどがビルドされていますが、float/doubleの問題には固執しているようです。

すべてのターゲットプラットフォームでIEEE-754浮動小数点数(および倍精度)を使用していない限り、バイトスワッピングのトリックは有効ではありません。君は。

+0

待ってください...今日、IEEE 754浮動小数点を使用しないプラットフォームがありますか? – fuz

+0

私は、RAMのIEEE-754 float/doubleのビットの順序については何の保証もないと思います。それは何でもかまいませんし、あなたはその内容を直接操作してはなりません。 –

+0

これは、プラットフォームのdoubleの実装がIEEE-754に準拠していることを確認する方法に関する興味深い記事です。http://stackoverflow.com/a/753018/1384030 –

0

実装では、シリアル化された浮動小数点表現が常に指定された形式で扱われることが保証されていれば、あなたは大丈夫です(IEEE 754が一般的です)。

はい、アーキテクチャによっては、浮動小数点数の順序が異なる場合があります(たとえば、ビッグエンディアンまたはリトルエンディアンの場合)。したがって、何とかエンディアンを指定したいと思うでしょう。これは、フォーマットの仕様または変数であり、ファイルのデータに記録されます。

最後の大きな落とし穴は、組み込みのアライメントが異なることがあることです。ハードウェア/プロセッサが誤整列されたデータをどのように処理するかは、実装定義です。したがって、データ/バイトをスワップしてから宛先に移動する必要があります。float/double

0

HDF5たりはNetCDFのようなライブラリを使っていない限り、おそらくマークは言った、このような高パフォーマンスのためのビットのヘビー級でありますそれらのライブラリで利用可能な他の機能も必要です。

シリアル化のみを扱う軽量の代替品は、たとえば、次のようになります。 XDRwikipedia descriptionも参照のこと)。多くのOSは、自立していないXDRライブラリがあれば、XDRルーチンをすぐに提供します。

関連する問題