4 votes

Sauvegarde correcte (indépendante de 32bit/64bit) d'un float en binaire ofstream

Donc apparemment sur ma machine, le flotteur, le double et le double long ont chacun des tailles différentes. Il ne semble pas non plus y avoir de norme stricte imposant le nombre d'octets que doit contenir chacun de ces types.

Comment peut-on alors sauvegarder une valeur à virgule flottante dans un fichier binaire, et la faire lire correctement sur un autre système si les tailles diffèrent ? Par exemple, ma machine a des doubles de 8 octets, alors que celle de Joe a des doubles de 12 octets.

Sans avoir à l'exporter sous forme de texte (par exemple "0,3232"). Cela semble beaucoup moins compact que la représentation binaire.

6voto

James Kanze Points 96599

Vous devez définir un format, et le mettre en œuvre. Typiquement, la plupart des protocoles réseau que je connais utilisent les flottants et les doubles IEEE, la sortie big-endian (mais d'autres formats sont possibles). L'avantage d'utiliser les formats IEEE est que c'est ce qu'utilisent la plupart des machines actuelles de tous les jours internes ; si vous êtes sur l'une de ces machines (et que la portabilité de votre code vers d'autres machines, comme les mainframes, n'est pas un problème), vous pouvez vous "convertir" au format simplement en effectuant une conversion de type vers un int non signé de la de la même taille, et en le sortant. Ainsi, par exemple, vous pourriez avoir :

obstream&
operator<<( obstream& dest, uint64_t value )
{
    dest.put((value >> 56) & 0xFF);
    dest.put((value >> 48) & 0xFF);
    dest.put((value >> 40) & 0xFF);
    dest.put((value >> 32) & 0xFF);
    dest.put((value >> 24) & 0xFF);
    dest.put((value >> 16) & 0xFF);
    dest.put((value >>  8) & 0xFF);
    dest.put((value      ) & 0xFF);
    return dest;
}

obstream&
operator<<( obstream& dest, double value )
{
    return dest << reinterpret_cast<uint64_t const&>( value );
}

Si vous devez être portable sur une machine ne supportant pas IEEE (par exemple, l'un des les mainframes modernes), vous aurez besoin de quelque chose d'un peu plus compliqué :

obstream&
obstream::operator<<( obstream& dest, double value )
{
    bool                isNeg = value < 0;
    if ( isNeg ) {
        value = - value;
    }
    int                 exp;
    if ( value == 0.0 ) {
        exp = 0;
    } else {
        value = ldexp( frexp( value, &exp ), 53 );
        exp += 1022;
    }
    uint64_t mant = static_cast< uint64_t >( value );
    dest.put( (isNeg ? 0x80 : 0x00) | exp >> 4 );
    dest.put( ((exp << 4) & 0xF0) | ((mant >> 48) & 0x0F) );
    dest.put( mant >> 40 );
    dest.put( mant >> 32 );
    dest.put( mant >> 24 );
    dest.put( mant >> 16 );
    dest.put( mant >>  8 );
    dest.put( mant       );
    return dest;
}

(Notez que cela ne gère pas correctement les NaN et les infinis. Personnellement, je les bannirais du format, car toutes les représentations en virgule flottante ne conviennent pas. pas toutes les représentations en virgule flottante les supportent. Mais alors, il n'y a pas de format en virgule flottante flottante sur un mainframe IBM qui supportera 1E306, bien que vous pouvez l'encoder dans le format double IEEE ci-dessus).

La lecture est, bien sûr, le contraire. Soit :

ibstream&
operator>>( ibstream& source, uint64_t& results )
{
    uint64_t value = (source.get() & 0xFF) << 56;
    value |= (source.get() & 0xFF) << 48;
    value |= (source.get() & 0xFF) << 40;
    value |= (source.get() & 0xFF) << 32;
    value |= (source.get() & 0xFF) << 24;
    value |= (source.get() & 0xFF) << 16;
    value |= (source.get() & 0xFF) <<  8;
    value |= (source.get() & 0xFF)      ;
    if ( source )
        results = value;
    return source;
}

ibstream&
operator>>( ibstream& source, double& results)
{
    uint64_t tmp;
    source >> tmp;
    if ( source )
        results = reinterpret_cast<double const&>( tmp );
    return source;
}

ou si vous ne pouvez pas compter sur l'IEEE :

ibstream&
ibstream::operator>>( ibstream& source, double& results )
{
    uint64_t tmp;
    source >> tmp;
    if ( source ) {
        double f = 0.0;
        if ( (tmp & 0x7FFFFFFFFFFFFFFF) != 0 ) {
            f = ldexp( ((tmp & 0x000FFFFFFFFFFFFF) | 0x0010000000000000),
                       static_cast<int>( (tmp & 0x7FF0000000000000) >> 52 )
                                - 1022 - 53 );
        }
        if ( (tmp & 0x8000000000000000) != 0 ) {
            f = -f;
        }
        dest = f;
    }
    return source;
}

(Cela suppose que l'entrée n'est pas un NaN ou un infini).

2voto

Roland Illig Points 15357

Vous pourriez les stocker dans un format indépendant de la machine. Pour cela, vous devez d'abord les convertir de votre représentation à l'autre.

Un type de conversion simple consiste à analyser le nombre en un triple (signe, exposant, significande), et à les enregistrer comme des nombres entiers. De cette façon, vous pouvez conserver la précision totale.

0voto

Markus Pilman Points 1438

Vous pouvez utiliser la sérialisation Boost http://www.boost.org/doc/libs/1_47_0/libs/serialization/doc/index.html qui peut être sérialisé en texte, xml ou binaire.

Sinon, vous pouvez le faire manuellement, par exemple comme ceci :

  • vous acceptez le little endian (et vérifiez si la machine est little ou big endian, puis effectuez un nouveau calcul).
  • vous écrivez un octet, combien d'octets de long le nombre à virgule flottante fait-il ?
  • puis vous écrivez le flottant sur le flux - en petit endian

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X