Comment convertir entre les valeurs big-endian et little-endian en C ++?

196

Comment convertir entre les valeurs big-endian et little-endian en C ++?

EDIT: pour plus de clarté, je dois traduire des données binaires (valeurs à virgule flottante double précision et entiers 32 bits et 64 bits) d'une architecture CPU à une autre. Cela n'implique pas la mise en réseau, donc ntoh () et les fonctions similaires ne fonctionneront pas ici.

EDIT # 2: La réponse que j'ai acceptée s'applique directement aux compilateurs que je cible (c'est pourquoi je l'ai choisi). Cependant, il existe d'autres très bonnes réponses, plus portables ici.

Uhall
la source
21
ntoh hton fonctionnera bien, même si cela n'a rien à voir avec la mise en réseau.
Ben Collins
2
La meilleure façon de gérer l'endianité en général est de s'assurer que le code s'exécute sur les machines hôtes à la fois petites et grandes. Si cela fonctionne, vous l'avez probablement bien fait. Supposer que vous utilisez x86 / be est dangereux en tant que pratique.
jakobengblom2
10
hton ntoh ne fonctionnera pas si la machine est big-endian, car le demandeur de question veut explicitement effectuer la conversion.
fabspro
6
@ jakobengblom2 est la seule personne à le mentionner. Presque tous les exemples de cette page utilisent des concepts comme "swap" octets au lieu de le faire indépendamment de l'endianité sous-jacente. Si vous avez affaire à des formats de fichiers externes (qui ont une endianité bien définie), la chose la plus portable à faire est de traiter les données externes comme un flux d'octets et de convertir le flux d'octets vers et depuis les entiers natifs. Je grince des dents à chaque fois que je vois du short swap(short x)code, car il se cassera si vous passez à une plate-forme avec une endianité différente. Matthieu M a la seule bonne réponse ci-dessous.
Mark Lakata
3
Vous pensez que le problème est complètement faux. La tâche n'est pas "comment convertir des valeurs big-endian et little-endian". La tâche est "comment convertir des valeurs à virgule flottante et entières dans un format particulier au format natif de ma plate-forme". Si vous le faites correctement, le format natif peut être big endian, little endian, mixed endian ou ternary pour tous vos soucis de code.
David Schwartz

Réponses:

166

Si vous utilisez Visual C ++, procédez comme suit: Vous incluez intrin.h et appelez les fonctions suivantes:

Pour les nombres à 16 bits:

unsigned short _byteswap_ushort(unsigned short value);

Pour les nombres 32 bits:

unsigned long _byteswap_ulong(unsigned long value);

Pour les nombres 64 bits:

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

Les nombres à 8 bits (caractères) n'ont pas besoin d'être convertis.

De plus, ceux-ci ne sont définis que pour les valeurs non signées, ils fonctionnent également pour les entiers signés.

Pour les flottants et les doubles, c'est plus difficile que pour les entiers simples, car ceux-ci peuvent ou non être dans l'ordre des octets des machines hôtes. Vous pouvez obtenir des flotteurs little-endian sur des machines big-endian et vice versa.

D'autres compilateurs ont également des caractéristiques intrinsèques similaires.

Dans GCC, par exemple, vous pouvez appeler directement certains programmes intégrés comme indiqué ici :

uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)

(pas besoin d'inclure quelque chose). Afaik bits.h déclare également la même fonction de manière non gcc-centrique.

Swap 16 bits c'est juste une rotation de bits.

Appeler les intrinsèques au lieu de lancer les vôtres vous offre les meilleures performances et densité de code entre les deux.

Nils Pipenbrinck
la source
11
Avec GCC, je pourrais utiliser: #include <byteswap.h> int32_t bswap_32 (int32_t x) int64_t bswap_64 (int64_t x)
jmanning2k
5
__builtin_bswapXest uniquement disponible à partir de GCC-4.3
Matt Joiner
20
Il est également intéressant de noter que ces valeurs intrinsèques / toujours / octets swap, ils ne sont pas comme htonl, htons, etc. Vous devez savoir à partir du contexte de votre situation quand échanger réellement les octets.
Brian Vandenberg
8
@Jason parce que les nombres à 8 bits sont les mêmes en gros et petit boutian. :-)
Nils Pipenbrinck
2
@BrianVandenberg Right; utiliser htonlet ntohlsans se soucier du contexte fonctionnerait lors de l'écriture de code portable puisque la plate-forme définissant ces fonctions le permuterait si c'est petit / moyen-endian et sur gros-boutien ce serait un no-op. Cependant, lors du décodage d'un type de fichier standard qui est défini comme petit-boutien (par exemple BMP), il faut toujours connaître le contexte et ne peut pas simplement compter sur htonlet ntohl.
legends2k
86

Tout simplement:

#include <climits>

template <typename T>
T swap_endian(T u)
{
    static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");

    union
    {
        T u;
        unsigned char u8[sizeof(T)];
    } source, dest;

    source.u = u;

    for (size_t k = 0; k < sizeof(T); k++)
        dest.u8[k] = source.u8[sizeof(T) - k - 1];

    return dest.u;
}

utilisation: swap_endian<uint32_t>(42).

Alexandre C.
la source
3
Ayez un vote positif. Je viens d'utiliser des uchars et de leur attribuer 4 à 1, 3 à 2, 2 à 3 et 1 à 4, mais c'est plus flexible si vous avez des tailles différentes. 6 horloges sur un Pentium IIRC de 1re génération. BSWAP est d'une horloge, mais est spécifique à la plate-forme.
2
@RocketRoy: Oui, et si la vitesse s'avère être un problème, il est très simple d'écrire des surcharges avec des intrisiques spécifiques à la plate-forme et au type.
Alexandre C.
3
@MihaiTodor: Cette utilisation des unions pour le transtypage via un tableau de caractères est explicitement autorisée par la norme. Voir par exemple. cette question .
Alexandre C.
4
@AlexandreC. Pas dans la norme C ++ - seulement en C. En C ++ (qui est ce code), ce code est un comportement non défini.
Rapptz
4
@Rapptz: 3.10 semble clair: "Si un programme tente d'accéder à la valeur stockée d'un objet via une valeur gl autre que l'un des types suivants, le comportement n'est pas défini: [...] un type char ou unsigned char type. ". Peut-être que je manque quelque chose ici, mais il était assez clair pour moi que l'accès à n'importe quel type via des pointeurs char était explicitement autorisé.
Alexandre C.
75

Extrait de The Byte Order Fallacy de Rob Pike:

Supposons que votre flux de données comporte un entier 32 bits codé en petit-boutien. Voici comment l'extraire (en supposant des octets non signés):

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

Si c'est du big-endian, voici comment l'extraire:

i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);

TL; DR: ne vous inquiétez pas de l'ordre natif de votre plate-forme, tout ce qui compte est l'ordre des octets du flux à partir duquel vous lisez, et vous feriez mieux d'espérer qu'il soit bien défini.

Remarque: il a été remarqué dans le commentaire qu'en l'absence de conversion de type explicite, il était important que ce datasoit un tableau de unsigned charou uint8_t. L'utilisation de signed charou char(si signé) entraînera la data[x]promotion en un entier et data[x] << 24éventuellement le décalage d'un 1 dans le bit de signe qui est UB.

Matthieu M.
la source
6
C'est cool, mais il me semble que cela ne s'applique qu'aux entiers et aux variantes. Que faire des flotteurs / doubles?
Brett
1
@ v.oddou: oui et non, les fichiers mappés en mémoire sont exactement les mêmes que les trames réseau; si vous acceptez de ne pas les lire directement, tout ce qui compte c'est leur endianité: si petit-endian, utilisez la première formule, si c'est big-endian, utilisez la seconde. Tout compilateur digne de ce nom optimisera les transformations inutiles si l'adéquation correspond.
Matthieu M.
2
@meowsqueak: Oui, je m'attendrais à ce que cela fonctionne, car seul l'ordre des octets change, pas l'ordre des bits dans chaque octet.
Matthieu M.
3
Sur une note vaguement liée, le message lié est une lecture désagréable ... Le gars semble valoriser la brièveté, mais il a préféré écrire une longue diatribe sur tous ces mauvais programmeurs qui ne sont pas aussi éclairés que sur l'endianité, au lieu d'être réellement expliquer la situation et POURQUOI sa solution fonctionne toujours.
Annonce N
1
Si vous utilisez cette méthode, assurez-vous de convertir vos données en (caractère non signé *)
joseph
51

Si vous faites cela à des fins de compatibilité réseau / hôte, vous devez utiliser:

ntohl() //Network to Host byte order (Long)
htonl() //Host to Network byte order (Long)

ntohs() //Network to Host byte order (Short)
htons() //Host to Network byte order (Short)

Si vous faites cela pour une autre raison, l'une des solutions byte_swap présentées ici fonctionnerait très bien.

Glacial
la source
2
l'ordre des octets réseau est un gros endian je crois. Ces fonctions peuvent être utilisées dans cet esprit même si vous n'utilisez pas de code réseau. Cependant il n'y a pas de versions flottantes ntohf ou htonf
Matt
2
Matt H., ce n'est généralement correct. Tous les systèmes informatiques n'ont pas d'ordre d'octets en petit bout. Si vous travailliez sur, disons, un Motorolla 68k, un PowerPC ou une autre architecture big-endian, ces fonctions n'échangeront pas d'octets du tout car elles sont déjà dans l'ordre des octets du réseau.
Frosty
2
Malheureusement, htonlet ntohlne peut pas aller au petit endian sur une plate-forme big-endian.
Brian Vandenberg
2
@celtschk, compris; cependant, l'OP veut un moyen de changer l'endianisme, même dans un environnement big-endian.
Brian Vandenberg
4
Pour éviter l'inévitable question: il y a plusieurs raisons d'avoir besoin de LE pour une plateforme BE; un certain nombre de formats de fichiers (bmp, fli, pcx, qtm, rtf, tga pour n'en nommer que quelques-uns) utilisent de petites valeurs endiennes ... ou du moins, une version du format l'a fait à un moment donné de toute façon.
Brian Vandenberg
26

J'ai pris quelques suggestions de ce post et les ai rassemblées pour former ceci:

#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>

enum endianness
{
    little_endian,
    big_endian,
    network_endian = big_endian,

    #if defined(BOOST_LITTLE_ENDIAN)
        host_endian = little_endian
    #elif defined(BOOST_BIG_ENDIAN)
        host_endian = big_endian
    #else
        #error "unable to determine system endianness"
    #endif
};

namespace detail {

template<typename T, size_t sz>
struct swap_bytes
{
    inline T operator()(T val)
    {
        throw std::out_of_range("data size");
    }
};

template<typename T>
struct swap_bytes<T, 1>
{
    inline T operator()(T val)
    {
        return val;
    }
};

template<typename T>
struct swap_bytes<T, 2>
{
    inline T operator()(T val)
    {
        return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
    }
};

template<typename T>
struct swap_bytes<T, 4>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff000000) >> 24) |
                (((val) & 0x00ff0000) >>  8) |
                (((val) & 0x0000ff00) <<  8) |
                (((val) & 0x000000ff) << 24));
    }
};

template<>
struct swap_bytes<float, 4>
{
    inline float operator()(float val)
    {
        uint32_t mem =swap_bytes<uint32_t, sizeof(uint32_t)>()(*(uint32_t*)&val);
        return *(float*)&mem;
    }
};

template<typename T>
struct swap_bytes<T, 8>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff00000000000000ull) >> 56) |
                (((val) & 0x00ff000000000000ull) >> 40) |
                (((val) & 0x0000ff0000000000ull) >> 24) |
                (((val) & 0x000000ff00000000ull) >> 8 ) |
                (((val) & 0x00000000ff000000ull) << 8 ) |
                (((val) & 0x0000000000ff0000ull) << 24) |
                (((val) & 0x000000000000ff00ull) << 40) |
                (((val) & 0x00000000000000ffull) << 56));
    }
};

template<>
struct swap_bytes<double, 8>
{
    inline double operator()(double val)
    {
        uint64_t mem =swap_bytes<uint64_t, sizeof(uint64_t)>()(*(uint64_t*)&val);
        return *(double*)&mem;
    }
};

template<endianness from, endianness to, class T>
struct do_byte_swap
{
    inline T operator()(T value)
    {
        return swap_bytes<T, sizeof(T)>()(value);
    }
};
// specialisations when attempting to swap to the same endianess
template<class T> struct do_byte_swap<little_endian, little_endian, T> { inline T operator()(T value) { return value; } };
template<class T> struct do_byte_swap<big_endian,    big_endian,    T> { inline T operator()(T value) { return value; } };

} // namespace detail

template<endianness from, endianness to, class T>
inline T byte_swap(T value)
{
    // ensure the data is only 1, 2, 4 or 8 bytes
    BOOST_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
    // ensure we're only swapping arithmetic types
    BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

    return detail::do_byte_swap<from, to, T>()(value);
}
Steve Lorimer
la source
vous devez également inclure <cstdint> ou <stdint.h>, par exemple, pour uint32_t
ady
17

La procédure pour passer du big-endian au little-endian est la même que pour passer du petit-endian au big-endian.

Voici un exemple de code:

void swapByteOrder(unsigned short& us)
{
    us = (us >> 8) |
         (us << 8);
}

void swapByteOrder(unsigned int& ui)
{
    ui = (ui >> 24) |
         ((ui<<8) & 0x00FF0000) |
         ((ui>>8) & 0x0000FF00) |
         (ui << 24);
}

void swapByteOrder(unsigned long long& ull)
{
    ull = (ull >> 56) |
          ((ull<<40) & 0x00FF000000000000) |
          ((ull<<24) & 0x0000FF0000000000) |
          ((ull<<8) & 0x000000FF00000000) |
          ((ull>>8) & 0x00000000FF000000) |
          ((ull>>24) & 0x0000000000FF0000) |
          ((ull>>40) & 0x000000000000FF00) |
          (ull << 56);
}
Kevin
la source
2
La dernière fonction publiée ici est incorrecte et doit être modifiée comme suit: void swapByteOrder (unsigned long long & ull) {ull = (ull >> 56) | ... (ull << 56); }
Eric Burnett
14
Je ne pense pas qu'il soit correct d'utiliser logique et (&&) par opposition à bit à bit et (&). Selon la spécification C ++, les deux opérandes sont implicitement convertis en bool, ce qui n'est pas ce que vous voulez.
Trevor Robinson
16

Il existe une instruction d'assemblage appelée BSWAP qui fera l'échange pour vous, extrêmement rapidement . Vous pouvez en lire plus ici .

Visual Studio, ou plus précisément la bibliothèque d'exécution Visual C ++, a pour cela des intrinsèques de plate-forme, appelés _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64(). Des similaires devraient exister pour d'autres plateformes, mais je ne sais pas comment elles s'appelleraient.

anon6439
la source
Voilà un excellent lien. Cela a ravivé mon intérêt pour l'assembleur x86.
PP.
1
Les résultats de synchronisation pour BSWAP sont présentés ici. gmplib.org/~tege/x86-timing.pdf ... et ici ... agner.org/optimize/instruction_tables.pdf
12

Nous l'avons fait avec des modèles. Vous pouvez faire quelque chose comme ça:

// Specialization for 2-byte types.
template<>
inline void endian_byte_swapper< 2 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    ushort* p_dest = reinterpret_cast< ushort* >(dest);
    ushort const* const p_src = reinterpret_cast< ushort const* >(src);
    *p_dest = (*p_src >> 8) | (*p_src << 8);
}

// Specialization for 4-byte types.
template<>
inline void endian_byte_swapper< 4 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    uint* p_dest = reinterpret_cast< uint* >(dest);
    uint const* const p_src = reinterpret_cast< uint const* >(src);
    *p_dest = (*p_src >> 24) | ((*p_src & 0x00ff0000) >> 8) | ((*p_src & 0x0000ff00) << 8) | (*p_src << 24);
}
marque
la source
8

Si vous faites cela pour transférer des données entre différentes plates-formes, regardez les fonctions ntoh et hton.

Andrew
la source
7

De la même manière que vous le faites en C:

short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));

Vous pouvez également déclarer un vecteur de caractères non signés, y mémoriser la valeur d'entrée, inverser les octets dans un autre vecteur et mémoriser les octets, mais cela prendra des ordres de grandeur plus longtemps que le twiddling de bits, en particulier avec les valeurs 64 bits.

Ben Straub
la source
7

Sur la plupart des systèmes POSIX (ce n'est pas dans la norme POSIX), il y a le fichier endian.h, qui peut être utilisé pour déterminer le codage utilisé par votre système. De là, c'est quelque chose comme ça:

unsigned int change_endian(unsigned int x)
{
    unsigned char *ptr = (unsigned char *)&x;
    return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}

Cela permute l'ordre (du gros endian au petit endian):

Si vous avez le numéro 0xDEADBEEF (sur un petit système endian stocké en tant que 0xEFBEADDE), ptr [0] sera 0xEF, ptr [1] est 0xBE, etc.

Mais si vous souhaitez l'utiliser pour la mise en réseau, alors htons, htonl et htonll (et leurs inverses ntohs, ntohl et ntohll) seront utiles pour la conversion de l'ordre de l'hôte à l'ordre du réseau.

terminus
la source
6
C'est drôle - le standard POSIX sur opengroup.org/onlinepubs/9699919799/toc.htm ne mentionne pas d'en-tête '<endian.h>'.
Jonathan Leffler
1
Vous pouvez utiliser htonlet amis, que le cas d'utilisation ait quelque chose à voir avec la mise en réseau. L'ordre des octets réseau est big-endian, il suffit donc de traiter ces fonctions comme host_to_be et be_to_host. (Cela n'aide pas si vous avez besoin de host_to_le, cependant.)
Peter Cordes
5

Notez que, au moins pour Windows, htonl () est beaucoup plus lent que son homologue intrinsèque _byteswap_ulong (). Le premier est un appel de bibliothèque DLL dans ws2_32.dll, le second est une instruction d'assemblage BSWAP. Par conséquent, si vous écrivez du code dépendant de la plate-forme, préférez utiliser les intrinsèques pour la vitesse:

#define htonl(x) _byteswap_ulong(x)

Cela peut être particulièrement important pour le traitement d'image .PNG où tous les entiers sont enregistrés dans Big Endian avec l'explication "On peut utiliser htonl () ..." {pour ralentir les programmes Windows typiques, si vous n'êtes pas préparé}.

user2699548
la source
4

La plupart des plates-formes ont un fichier d'en-tête système qui fournit des fonctions d'octetswap efficaces. Sous Linux, c'est dans <endian.h>. Vous pouvez bien l'envelopper en C ++:

#include <iostream>

#include <endian.h>

template<size_t N> struct SizeT {};

#define BYTESWAPS(bits) \
template<class T> inline T htobe(T t, SizeT<bits / 8>) { return htobe ## bits(t); } \
template<class T> inline T htole(T t, SizeT<bits / 8>) { return htole ## bits(t); } \
template<class T> inline T betoh(T t, SizeT<bits / 8>) { return be ## bits ## toh(t); } \
template<class T> inline T letoh(T t, SizeT<bits / 8>) { return le ## bits ## toh(t); }

BYTESWAPS(16)
BYTESWAPS(32)
BYTESWAPS(64)

#undef BYTESWAPS

template<class T> inline T htobe(T t) { return htobe(t, SizeT<sizeof t>()); }
template<class T> inline T htole(T t) { return htole(t, SizeT<sizeof t>()); }
template<class T> inline T betoh(T t) { return betoh(t, SizeT<sizeof t>()); }
template<class T> inline T letoh(T t) { return letoh(t, SizeT<sizeof t>()); }

int main()
{
    std::cout << std::hex;
    std::cout << htobe(static_cast<unsigned short>(0xfeca)) << '\n';
    std::cout << htobe(0xafbeadde) << '\n';

    // Use ULL suffix to specify integer constant as unsigned long long 
    std::cout << htobe(0xfecaefbeafdeedfeULL) << '\n';
}

Production:

cafe
deadbeaf
feeddeafbeefcafe
Maxim Egorushkin
la source
Changement: #define BYTESWAPS (bits) \ template <class T> inline T htobe (T t, SizeT <bits / 8>) {return htobe ## bits (t); } \ template <class T> inline T htole (T t, SizeT <bits / 8>) {return htole ## bits (t); } \ template <class T> inline T betoh (T t, SizeT <bits / 8>) {return be ## bits ## toh (t); } \ template <classe T> inline T letoh (T t, SizeT <bits / 8>) {return le ## bits ## toh (t); }
ldav1s
Merci, j'ai oublié de tester betoh () et letoh ().
Maxim Egorushkin
4

j'aime celui-ci, juste pour le style :-)

long swap(long i) {
    char *c = (char *) &i;
    return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
}
friedemann
la source
Je reçois une erreur en char[]disant "Erreur: le type incomplet n'est pas autorisé"
Portland Runner
4

Sérieusement ... je ne comprends pas pourquoi toutes les solutions sont si compliquées ! Que diriez-vous de la fonction de modèle la plus simple et la plus générale qui permute tout type de n'importe quelle taille en toutes circonstances dans n'importe quel système d'exploitation ????

template <typename T>
void SwapEnd(T& var)
{
    static_assert(std::is_pod<T>::value, "Type must be POD type for safety");
    std::array<char, sizeof(T)> varArray;
    std::memcpy(varArray.data(), &var, sizeof(T));
    for(int i = 0; i < static_cast<int>(sizeof(var)/2); i++)
        std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
    std::memcpy(&var, varArray.data(), sizeof(T));
}

C'est le pouvoir magique du C et du C ++ ensemble! Échangez simplement la variable d'origine caractère par caractère.

Point 1 : Aucun opérateur: N'oubliez pas que je n'ai pas utilisé l'opérateur d'affectation simple "=" car certains objets seront gâchés lorsque l'endianisme sera inversé et le constructeur de copie (ou l'opérateur d'affectation) ne fonctionnera pas. Par conséquent, il est plus fiable de les copier caractère par caractère.

Point 2 : Soyez conscient des problèmes d'alignement: notez que nous copions vers et depuis un tableau, ce qui est la bonne chose à faire car le compilateur C ++ ne garantit pas que nous pouvons accéder à la mémoire non alignée (cette réponse a été mise à jour à partir de son original formulaire pour cela). Par exemple, si vous allouez uint64_t, votre compilateur ne peut pas garantir que vous pouvez accéder au 3e octet de cela en tant que uint8_t. Par conséquent, la bonne chose à faire est de le copier dans un tableau de caractères, de l'échanger, puis de le recopier (donc non reinterpret_cast). Notez que les compilateurs sont généralement assez intelligents pour convertir ce que vous avez fait en reinterpret_casts'ils sont capables d'accéder à des octets individuels indépendamment de l'alignement.

Pour utiliser cette fonction :

double x = 5;
SwapEnd(x);

et xest maintenant différent dans l'endianisme.

Le physicien quantique
la source
2
Cela fonctionnera n'importe où, mais l'assemblage produit sera souvent sous-optimal: voir ma question stackoverflow.com/questions/36657895/…
j_kubik
Vous utilisez new/ deletepour allouer un tampon pour cela?!? sizeof(var)est une constante de compilation, vous pouvez donc le faire char varSwapped[sizeof(var)]. Ou vous pourriez faire char *p = reinterpret_cast<char*>(&var)et échanger sur place.
Peter Cordes
@Peter cette réponse est rapide et sale pour prouver un point. Je mettrai en œuvre vos suggestions. Cependant, vous n'avez pas besoin d'être un méga SO AH et de voter contre la solution à 5 lignes par rapport aux solutions à 50 lignes qui y sont abandonnées. Je ne vais pas en dire plus.
The Quantum Physicist
Cette réponse fait quelques remarques utiles sur la prudence avec les constructeurs et les opérateurs surchargés sur les données erronées, donc je serais heureux de supprimer mon downvote une fois que le code n'est pas horrible, et c'est quelque chose qu'un bon compilateur pourrait compiler dans un bswap instruction. Aussi, je suggère d'utiliser for(size_t i = 0 ; i < sizeof(var) ; i++)au lieu d'un static_cast<long>. (Ou en fait, le swap sur place utilisera un ordre croissant et décroissant char*pour que cela disparaisse quand même).
Peter Cordes
par exemple, voir la réponse de Mark Ransom en utilisant std :: swap pour inverser sur place.
Peter Cordes
3

J'ai ce code qui me permet de convertir de HOST_ENDIAN_ORDER (quel qu'il soit) en LITTLE_ENDIAN_ORDER ou BIG_ENDIAN_ORDER. J'utilise un modèle, donc si j'essaie de convertir de HOST_ENDIAN_ORDER en LITTLE_ENDIAN_ORDER et qu'ils se trouvent être les mêmes pour la machine pour laquelle je compile, aucun code ne sera généré.

Voici le code avec quelques commentaires:

// We define some constant for little, big and host endianess. Here I use 
// BOOST_LITTLE_ENDIAN/BOOST_BIG_ENDIAN to check the host indianess. If you
// don't want to use boost you will have to modify this part a bit.
enum EEndian
{
  LITTLE_ENDIAN_ORDER,
  BIG_ENDIAN_ORDER,
#if defined(BOOST_LITTLE_ENDIAN)
  HOST_ENDIAN_ORDER = LITTLE_ENDIAN_ORDER
#elif defined(BOOST_BIG_ENDIAN)
  HOST_ENDIAN_ORDER = BIG_ENDIAN_ORDER
#else
#error "Impossible de determiner l'indianness du systeme cible."
#endif
};

// this function swap the bytes of values given it's size as a template
// parameter (could sizeof be used?).
template <class T, unsigned int size>
inline T SwapBytes(T value)
{
  union
  {
     T value;
     char bytes[size];
  } in, out;

  in.value = value;

  for (unsigned int i = 0; i < size / 2; ++i)
  {
     out.bytes[i] = in.bytes[size - 1 - i];
     out.bytes[size - 1 - i] = in.bytes[i];
  }

  return out.value;
}

// Here is the function you will use. Again there is two compile-time assertion
// that use the boost librarie. You could probably comment them out, but if you
// do be cautious not to use this function for anything else than integers
// types. This function need to be calles like this :
//
//     int x = someValue;
//     int i = EndianSwapBytes<HOST_ENDIAN_ORDER, BIG_ENDIAN_ORDER>(x);
//
template<EEndian from, EEndian to, class T>
inline T EndianSwapBytes(T value)
{
  // A : La donnée à swapper à une taille de 2, 4 ou 8 octets
  BOOST_STATIC_ASSERT(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);

  // A : La donnée à swapper est d'un type arithmetic
  BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

  // Si from et to sont du même type on ne swap pas.
  if (from == to)
     return value;

  return SwapBytes<T, sizeof(T)>(value);
}
Mathieu Pagé
la source
3

Si un entier non signé big-endian 32 bits ressemble à 0xAABBCCDD qui est égal à 2864434397, alors ce même entier non signé 32 bits ressemble à 0xDDCCBBAA sur un processeur little-endian qui est également égal à 2864434397.

Si un court-circuit non signé 16 bits big-endian ressemble à 0xAABB qui est égal à 43707, alors ce même short non-signé 16 bits ressemble à 0xBBAA sur un processeur little-endian qui est également égal à 43707.

Voici quelques fonctions #define pratiques pour échanger des octets de little-endian à big-endian et vice-versa ->

// can be used for short, unsigned short, word, unsigned word (2-byte types)
#define BYTESWAP16(n) (((n&0xFF00)>>8)|((n&0x00FF)<<8))

// can be used for int or unsigned int or float (4-byte types)
#define BYTESWAP32(n) ((BYTESWAP16((n&0xFFFF0000)>>16))|((BYTESWAP16(n&0x0000FFFF))<<16))

// can be used for unsigned long long or double (8-byte types)
#define BYTESWAP64(n) ((BYTESWAP32((n&0xFFFFFFFF00000000)>>32))|((BYTESWAP32(n&0x00000000FFFFFFFF))<<32))
Adam Freeman
la source
2

Voici une version généralisée que j'ai inventée du haut de ma tête, pour échanger une valeur en place. Les autres suggestions seraient meilleures si les performances sont un problème.

 template<typename T>
    void ByteSwap(T * p)
    {
        for (int i = 0;  i < sizeof(T)/2;  ++i)
            std::swap(((char *)p)[i], ((char *)p)[sizeof(T)-1-i]);
    }

Avertissement: je n'ai pas encore essayé de le compiler ou de le tester.

Mark Ransom
la source
2

Si vous prenez le modèle commun pour inverser l'ordre des bits dans un mot et supprimez la partie qui inverse les bits dans chaque octet, vous vous retrouvez avec quelque chose qui inverse uniquement les octets dans un mot. Pour 64 bits:

x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
x = ((x & 0x00ff00ff00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff00ff00ff);

Le compilateur devrait nettoyer les opérations de masquage de bits superflues (je les ai laissées pour mettre en surbrillance le modèle), mais si ce n'est pas le cas, vous pouvez réécrire la première ligne de cette façon:

x = ( x                       << 32) ^  (x >> 32);

Cela devrait normalement se réduire à une seule instruction de rotation sur la plupart des architectures (sans tenir compte du fait que toute l'opération est probablement une instruction).

Sur un processeur RISC, les grandes constantes compliquées peuvent entraîner des difficultés de compilation. Cependant, vous pouvez calculer trivialement chacune des constantes de la précédente. Ainsi:

uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
x = ((x & k) << 32) ^ ((x >> 32) & k);
k ^= k << 16;
x = ((x & k) << 16) ^ ((x >> 16) & k);
k ^= k << 8;
x = ((x & k) <<  8) ^ ((x >>  8) & k);

Si vous le souhaitez, vous pouvez l'écrire en boucle. Ce ne sera pas efficace, mais juste pour le plaisir:

int i = sizeof(x) * CHAR_BIT / 2;
uintmax_t k = (1 << i) - 1;
while (i >= 8)
{
    x = ((x & k) << i) ^ ((x >> i) & k);
    i >>= 1;
    k ^= k << i;
}

Et pour être complet, voici la version 32 bits simplifiée du premier formulaire:

x = ( x               << 16) ^  (x >> 16);
x = ((x & 0x00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff);
sh1
la source
2

Je pensais juste avoir ajouté ma propre solution ici car je ne l'ai vue nulle part. C'est une petite fonction portable et basée sur un modèle C ++ et portable qui utilise uniquement des opérations de bits.

template<typename T> inline static T swapByteOrder(const T& val) {
    int totalBytes = sizeof(val);
    T swapped = (T) 0;
    for (int i = 0; i < totalBytes; ++i) {
        swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
    }
    return swapped;
}
Joao
la source
2

Je suis vraiment surpris que personne n'ait mentionné les fonctions htobeXX et betohXX. Ils sont définis dans endian.h et sont très similaires aux fonctions réseau htonXX.

pseudo
la source
2

En utilisant les codes ci-dessous, vous pouvez facilement basculer entre BigEndian et LittleEndian

#define uint32_t unsigned 
#define uint16_t unsigned short

#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))

#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))
pz64_
la source
1

J'ai récemment écrit une macro pour le faire en C, mais elle est également valable en C ++:

#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)

Il accepte n'importe quel type et inverse les octets dans l'argument passé. Exemples d'utilisations:

int main(){
    unsigned long long x = 0xABCDEF0123456789;
    printf("Before: %llX\n",x);
    REVERSE_BYTES(x);
    printf("After : %llX\n",x);

    char c[7]="nametag";
    printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
    REVERSE_BYTES(c);
    printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}

Qui imprime:

Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman

Ce qui précède est parfaitement copiable / collable, mais il se passe beaucoup de choses ici, donc je vais vous expliquer comment cela fonctionne pièce par pièce:

La première chose notable est que la macro entière est enfermée dans un do while(0)bloc. Ceci est un idiome commun permettant l'utilisation normale des points-virgules après la macro.

Ensuite, l'utilisation d'une variable nommée REVERSE_BYTEScomme forcompteur de boucle. Le nom de la macro lui-même est utilisé comme nom de variable pour garantir qu'il n'entre pas en conflit avec d'autres symboles qui peuvent être dans le champ d'application partout où la macro est utilisée. Étant donné que le nom est utilisé dans l'expansion de la macro, il ne sera pas développé à nouveau lorsqu'il est utilisé comme nom de variable ici.

Dans la forboucle, deux octets sont référencés et XOR échangés (un nom de variable temporaire n'est donc pas requis):

((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]

__VA_ARGS__représente tout ce qui a été donné à la macro et est utilisé pour augmenter la flexibilité de ce qui peut être transmis (quoique pas beaucoup). L'adresse de cet argument est ensuite prise et transtypée en un unsigned charpointeur pour permettre l'échange de ses octets via un []indice de tableau .

Le dernier point particulier est le manque d' {}appareils orthopédiques. Ils ne sont pas nécessaires car toutes les étapes de chaque échange sont jointes à l' opérateur virgule , ce qui en fait une instruction.

Enfin, il convient de noter que ce n'est pas l'approche idéale si la vitesse est une priorité absolue. S'il s'agit d'un facteur important, certaines des macros spécifiques au type ou des directives spécifiques à la plate-forme référencées dans d'autres réponses sont probablement une meilleure option. Cette approche, cependant, est portable pour tous les types, toutes les principales plates-formes et les langages C et C ++.

Ryan Hilbert
la source
trouvé cela quelque part dans un code. confus le diable hors de moi. Merci pour l'explication. Mais pourquoi l'utilisation de __VA_ARGS__?
asr9
0

Wow, je ne pouvais pas croire certaines des réponses que j'ai lues ici. Il y a en fait une instruction d'assemblage qui le fait plus rapidement qu'autre chose. bswap. Vous pouvez simplement écrire une fonction comme celle-ci ...

__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

C'est BEAUCOUP plus rapide que les intrinsèques qui ont été suggérées. Je les ai démontés et j'ai regardé. La fonction ci-dessus n'a pas de prologue / épilogue et n'a donc pratiquement aucun frais généraux.

unsigned long _byteswap_ulong(unsigned long value);

Faire 16 bits est tout aussi simple, à l'exception que vous utiliseriez xchg al, ah. bswap ne fonctionne que sur les registres 32 bits.

64 bits est un peu plus délicat, mais pas trop. Beaucoup mieux que tous les exemples ci-dessus avec des boucles et des modèles, etc.

Il y a quelques mises en garde ici ... Tout d'abord, bswap n'est disponible que sur les processeurs 80x486 et supérieurs. Est-ce que quelqu'un envisage de le faire fonctionner sur un 386?!? Si c'est le cas, vous pouvez toujours remplacer bswap par ...

mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

L'assemblage en ligne n'est également disponible qu'en code x86 dans Visual Studio. Une fonction nue ne peut pas être doublée et n'est pas non plus disponible dans les versions x64. Dans ce cas, vous allez devoir utiliser les caractéristiques intrinsèques du compilateur.

Le soudeur
la source
1
_byteswap_ulonget _uint64(par exemple dans la réponse acceptée) les deux compilent pour utiliser l' bswapinstruction. Je serais surpris mais intéressé de savoir si cet asm est beaucoup plus rapide car il n'omet que le prologue / l'épilogue - l'avez-vous évalué?
ZachB
@stdcall La question ne demandait pas de solution portable ou ne mentionnait même rien d'une plateforme. Comme ma réponse l'a dit, ce qui précède est le moyen le plus rapide de swap endian. Bien sûr, si vous écrivez ceci sur une plate-forme non X86, cela ne fonctionnera pas, mais comme je l'ai également mentionné, vous êtes alors limité aux intrinsèques du compilateur, si votre compilateur les prend même en charge.
The Welder
@ZachB Dans ce cas particulier, je pense que l'omission du prologue et de l'épilogue va vous donner une économie décente parce que vous n'exécutez essentiellement qu'une instruction. Le prologue devra pousser sur la pile, faire une soustraction, définir le pointeur de base, puis similaire à la fin. Je ne l'ai pas évalué, mais ce qui précède a une chaîne de dépendance 0 que vous n'obtiendrez tout simplement pas sans qu'elle soit nue. Peut-être qu'un bon compilateur l'intégrerait, mais alors vous êtes dans un parc de balle différent.
The Welder
2
Peut-être. Mais notez que dans le cas commun de l'échange d'un tableau de nombres, les caractéristiques intrinsèques du compilateur discutées dans d'autres réponses utiliseront des extensions SSE / AVX et émettront PSHUFB, qui surpasse BSWAP. Voir wm.ite.pl/articles/reverse-array-of-bytes.html
ZachB
C'est une mauvaise forme à mon humble avis de publier une solution spécifique à la plate-forme, lorsque l'OP n'a pas spécifié qu'ils n'avaient besoin que d'une solution pour x86. Et pour dénigrer les autres solutions, lorsque la vôtre est inutilisable sur de nombreux systèmes d'exploitation très répandus tels que iOS et Android (qui utilisent des processeurs ARM ou MIPS.)
Jens Alfke
0

Technique portable pour implémenter des accesseurs endiens non alignés non alignés et optimisés. Ils fonctionnent sur chaque compilateur, chaque alignement de frontière et chaque ordre d'octets. Ces routines non alignées sont complétées ou évoquées, selon l'endian natif et l'alignement. Liste partielle mais vous avez l'idée. BO * sont des valeurs constantes basées sur l'ordre des octets natifs.

uint32_t sw_get_uint32_1234(pu32)
uint32_1234 *pu32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32_1234[0] = (*pu32)[BO32_0];
  bou32.u32_1234[1] = (*pu32)[BO32_1];
  bou32.u32_1234[2] = (*pu32)[BO32_2];
  bou32.u32_1234[3] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_1234(pu32, u32)
uint32_1234 *pu32;
uint32_t u32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_1234[0];
  (*pu32)[BO32_1] = bou32.u32_1234[1];
  (*pu32)[BO32_2] = bou32.u32_1234[2];
  (*pu32)[BO32_3] = bou32.u32_1234[3];
}

#if HAS_SW_INT64
int64 sw_get_int64_12345678(pi64)
int64_12345678 *pi64;
{
  union {
    int64_12345678 i64_12345678;
    int64 i64;
  } boi64;
  boi64.i64_12345678[0] = (*pi64)[BO64_0];
  boi64.i64_12345678[1] = (*pi64)[BO64_1];
  boi64.i64_12345678[2] = (*pi64)[BO64_2];
  boi64.i64_12345678[3] = (*pi64)[BO64_3];
  boi64.i64_12345678[4] = (*pi64)[BO64_4];
  boi64.i64_12345678[5] = (*pi64)[BO64_5];
  boi64.i64_12345678[6] = (*pi64)[BO64_6];
  boi64.i64_12345678[7] = (*pi64)[BO64_7];
  return(boi64.i64);
}
#endif

int32_t sw_get_int32_3412(pi32)
int32_3412 *pi32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32_3412[2] = (*pi32)[BO32_0];
  boi32.i32_3412[3] = (*pi32)[BO32_1];
  boi32.i32_3412[0] = (*pi32)[BO32_2];
  boi32.i32_3412[1] = (*pi32)[BO32_3];
  return(boi32.i32);
}

void sw_set_int32_3412(pi32, i32)
int32_3412 *pi32;
int32_t i32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32 = i32;
  (*pi32)[BO32_0] = boi32.i32_3412[2];
  (*pi32)[BO32_1] = boi32.i32_3412[3];
  (*pi32)[BO32_2] = boi32.i32_3412[0];
  (*pi32)[BO32_3] = boi32.i32_3412[1];
}

uint32_t sw_get_uint32_3412(pu32)
uint32_3412 *pu32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32_3412[2] = (*pu32)[BO32_0];
  bou32.u32_3412[3] = (*pu32)[BO32_1];
  bou32.u32_3412[0] = (*pu32)[BO32_2];
  bou32.u32_3412[1] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_3412(pu32, u32)
uint32_3412 *pu32;
uint32_t u32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_3412[2];
  (*pu32)[BO32_1] = bou32.u32_3412[3];
  (*pu32)[BO32_2] = bou32.u32_3412[0];
  (*pu32)[BO32_3] = bou32.u32_3412[1];
}

float sw_get_float_1234(pf)
float_1234 *pf;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f_1234[0] = (*pf)[BO32_0];
  bof.f_1234[1] = (*pf)[BO32_1];
  bof.f_1234[2] = (*pf)[BO32_2];
  bof.f_1234[3] = (*pf)[BO32_3];
  return(bof.f);
}

void sw_set_float_1234(pf, f)
float_1234 *pf;
float f;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f = (float)f;
  (*pf)[BO32_0] = bof.f_1234[0];
  (*pf)[BO32_1] = bof.f_1234[1];
  (*pf)[BO32_2] = bof.f_1234[2];
  (*pf)[BO32_3] = bof.f_1234[3];
}

double sw_get_double_12345678(pd)
double_12345678 *pd;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d_12345678[0] = (*pd)[BO64_0];
  bod.d_12345678[1] = (*pd)[BO64_1];
  bod.d_12345678[2] = (*pd)[BO64_2];
  bod.d_12345678[3] = (*pd)[BO64_3];
  bod.d_12345678[4] = (*pd)[BO64_4];
  bod.d_12345678[5] = (*pd)[BO64_5];
  bod.d_12345678[6] = (*pd)[BO64_6];
  bod.d_12345678[7] = (*pd)[BO64_7];
  return(bod.d);
}

void sw_set_double_12345678(pd, d)
double_12345678 *pd;
double d;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d = d;
  (*pd)[BO64_0] = bod.d_12345678[0];
  (*pd)[BO64_1] = bod.d_12345678[1];
  (*pd)[BO64_2] = bod.d_12345678[2];
  (*pd)[BO64_3] = bod.d_12345678[3];
  (*pd)[BO64_4] = bod.d_12345678[4];
  (*pd)[BO64_5] = bod.d_12345678[5];
  (*pd)[BO64_6] = bod.d_12345678[6];
  (*pd)[BO64_7] = bod.d_12345678[7];
}

Ces typedefs ont l'avantage de générer des erreurs de compilation s'ils ne sont pas utilisés avec des accesseurs, atténuant ainsi les bogues oubliés des accesseurs.

typedef char int8_1[1], uint8_1[1];

typedef char int16_12[2], uint16_12[2]; /* little endian */
typedef char int16_21[2], uint16_21[2]; /* big endian */

typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */

typedef char int32_1234[4], uint32_1234[4]; /* little endian */
typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
typedef char int32_4321[4], uint32_4321[4]; /* big endian */

typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */

typedef char float_1234[4]; /* little endian */
typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
typedef char float_4321[4]; /* big endian */

typedef char double_12345678[8]; /* little endian */
typedef char double_78563412[8]; /* Alpha Micro? */
typedef char double_87654321[8]; /* big endian */
BSalita
la source
2
Pour cette question, la balise C ++ fait une différence. Il y a beaucoup de comportements indéfinis en raison de C ++ et de l'union.
2017
0

Voici comment lire un double stocké au format IEEE 754 64 bits, même si votre ordinateur hôte utilise un système différent.

/*
* read a double from a stream in ieee754 format regardless of host
*  encoding.
*  fp - the stream
*  bigendian - set to if big bytes first, clear for little bytes
*              first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
    unsigned char buff[8];
    int i;
    double fnorm = 0.0;
    unsigned char temp;
    int sign;
    int exponent;
    double bitval;
    int maski, mask;
    int expbits = 11;
    int significandbits = 52;
    int shift;
    double answer;

    /* read the data */
    for (i = 0; i < 8; i++)
        buff[i] = fgetc(fp);
    /* just reverse if not big-endian*/
    if (!bigendian)
    {
        for (i = 0; i < 4; i++)
        {
            temp = buff[i];
            buff[i] = buff[8 - i - 1];
            buff[8 - i - 1] = temp;
        }
    }
    sign = buff[0] & 0x80 ? -1 : 1;
    /* exponet in raw format*/
    exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);

    /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
    bitval = 0.5;
    maski = 1;
    mask = 0x08;
    for (i = 0; i < significandbits; i++)
    {
        if (buff[maski] & mask)
            fnorm += bitval;

        bitval /= 2.0;
        mask >>= 1;
        if (mask == 0)
        {
            mask = 0x80;
            maski++;
        }
    }
    /* handle zero specially */
    if (exponent == 0 && fnorm == 0)
        return 0.0;

    shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
    /* nans have exp 1024 and non-zero mantissa */
    if (shift == 1024 && fnorm != 0)
        return sqrt(-1.0);
    /*infinity*/
    if (shift == 1024 && fnorm == 0)
    {

#ifdef INFINITY
        return sign == 1 ? INFINITY : -INFINITY;
#endif
        return  (sign * 1.0) / 0.0;
    }
    if (shift > -1023)
    {
        answer = ldexp(fnorm + 1.0, shift);
        return answer * sign;
    }
    else
    {
        /* denormalised numbers */
        if (fnorm == 0.0)
            return 0.0;
        shift = -1022;
        while (fnorm < 1.0)
        {
            fnorm *= 2;
            shift--;
        }
        answer = ldexp(fnorm, shift);
        return answer * sign;
    }
}

Pour le reste de la suite de fonctions, y compris les routines d'écriture et d'entier, voir mon projet github

https://github.com/MalcolmMcLean/ieee754

Malcolm McLean
la source
0

L'échange d'octets avec une astuce en trois étapes autour d'un pivot dans une fonction de modèle donne une solution O (ln2) flexible et rapide qui ne nécessite pas de bibliothèque, le style ici rejette également les types à 1 octet:

template<typename T>void swap(T &t){
    for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
        *((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
    }
}
Quinn Carver
la source
0

Il semble que le moyen le plus sûr serait d'utiliser des htons sur chaque mot. Donc, si vous avez ...

std::vector<uint16_t> storage(n);  // where n is the number to be converted

// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });

Ce qui précède serait un no-op si vous étiez sur un système big-endian, donc je chercherais tout ce que votre plate-forme utilise comme condition de compilation pour décider si htons est un no-op. C'est O (n) après tout. Sur un Mac, ce serait quelque chose comme ...

#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });
#endif
cycollins
la source
0

Si vous avez C ++ 17, ajoutez cet en-tête

#include <algorithm>

Utilisez cette fonction de modèle pour échanger les octets:

template <typename T>
void swapEndian(T& buffer)
{
    static_assert(std::is_pod<T>::value, "swapEndian support POD type only");
    char* startIndex = static_cast<char*>((void*)buffer.data());
    char* endIndex = startIndex + sizeof(buffer);
    std::reverse(startIndex, endIndex);
}

appelez-le comme:

swapEndian (stlContainer);
Thinkal VB
la source
-4

Recherchez le décalage de bits, car c'est essentiellement tout ce que vous devez faire pour échanger de petit -> gros endian. Ensuite, en fonction de la taille du bit, vous modifiez la façon dont vous effectuez le décalage des bits.

Baron Rouge
la source