La manière la plus efficace de réinterpréter int en tant que flotteur

supposons que j'ai des garanties que float est IEEE 754 binary32. Étant donné un patron de bits qui correspond à un flotteur valide, stocké dans std::uint32_t , comment peut-on le réinterpréter comme un float d'une manière conforme standard la plus efficace?

float reinterpret_as_float(std::uint32_t ui) {
   return /* apply sorcery to ui */;
}

j'ai quelques moyens que je sais / suspecte/suppose ont quelques problèmes:

  1. Via reinterpret_cast ,

    float reinterpret_as_float(std::uint32_t ui) {
        return reinterpret_cast<float&>(ui);
    }
    

    ou l'équivalent

    float reinterpret_as_float(std::uint32_t ui) {
        return *reinterpret_cast<float*>(&ui);
    }
    

    qui souffre de problèmes d'alias.

  2. Via union ,

    float reinterpret_as_float(std::uint32_t ui) {
        union {
            std::uint32_t ui;
            float f;
        } u = {ui};
        return u.f;
    }
    

    qui n'est pas réellement légal, car il est seulement permis de lire à partir de la plus récente écrit à un membre. Pourtant, il semble que certains compilateurs (gcc).

  3. Via std::memcpy ,

    float reinterpret_as_float(std::uint32_t ui) {
        float f;
        std::memcpy(&f, &ui, 4);
        return f;
    }
    

    ce qui AFAIK est légal, mais un appel de fonction pour copier le mot simple semble inutile, bien qu'il puisse obtenir optimisé loin.

  4. Via reinterpret_cast ing char* et la copie,

    float reinterpret_as_float(std::uint32_t ui) {
        char* uip = reinterpret_cast<char*>(&ui);
        float f;
        char* fp = reinterpret_cast<char*>(&f);
        for (int i = 0; i < 4; ++i) {
            fp[i] = uip[i];
        }
        return f;
    }
    

    qui AFAIK est également légal, comme char pointeurs sont exemptés de problèmes d'aliasing et manuel byte copie boucle enregistre un appel de fonction possible. La boucle sera très certainement déroulée, mais 4 charges d'un octet éventuellement séparées / les magasins sont inquiétants, Je n'ai pas idée si cela est optimisable à quatre byte simple de charge / de stockage.

le 4 est le meilleur que j'ai pu trouver.

ai-je raison jusqu'à présent? Y a-t-il une meilleure façon de le faire, en particulier une façon qui garantira un seul chargement/stockage?

31
demandé sur yuri kilochek 2013-12-24 19:03:42

4 réponses

Afaik, il n'y a que deux approches qui sont conformes aux règles d'alias strictes: memcpy() et moulé à char* avec copie. Tous les autres lisent un float de mémoire qui appartient à un uint32_t , et le compilateur est autorisé à effectuer la lecture avant l'écriture à cet emplacement de mémoire. Il pourrait même optimiser l'écriture tout à fait car il peut prouver que la valeur stockée ne sera jamais utilisé selon des règles d'aliasing strictes, résultant en un retour de déchets valeur.

cela dépend vraiment du compilateur/optimise si la copie memcpy() ou char* est plus rapide. Dans les deux cas, un compilateur intelligent pourrait être en mesure de comprendre qu'il peut simplement charger et copier un uint32_t , mais je ne ferais confiance à aucun compilateur pour le faire avant que je l'ai vu dans le code assembleur résultant.

Edit:

Après quelques tests avec gcc 4.8.1, je peux dire que le memcpy() approche est la meilleure pour ce compilateur particulier, voir ci-dessous pour plus de détails.


Compilation

#include <stdint.h>

float foo(uint32_t a) {
    float b;
    char* aPointer = (char*)&a, *bPointer = (char*)&b;
    for( int i = sizeof(a); i--; ) bPointer[i] = aPointer[i];
    return b;
}

avec gcc -S -std=gnu11 -O3 foo.c donne ce code d'assemblage:

movl    %edi, %ecx
movl    %edi, %edx
movl    %edi, %eax
shrl    , %ecx
shrl    , %edx
shrw    , %ax
movb    %cl, -1(%rsp)
movb    %dl, -2(%rsp)
movb    %al, -3(%rsp)
movb    %dil, -4(%rsp)
movss   -4(%rsp), %xmm0
ret

ce n'est pas optimal.

Faire la même chose avec

#include <stdint.h>
#include <string.h>

float foo(uint32_t a) {
    float b;
    char* aPointer = (char*)&a, *bPointer = (char*)&b;
    memcpy(bPointer, aPointer, sizeof(a));
    return b;
}

rendements (avec tous les niveaux d'optimisation à l'exception de -O0 ):

movl    %edi, -4(%rsp)
movss   -4(%rsp), %xmm0
ret

c'est optimal.

14
répondu cmaster 2013-12-31 11:50:45

si le bitpattern dans la variable entière est la même qu'une valeur valide float , alors l'union est probablement la meilleure et la plus conforme façon de procéder. Et c'est en fait légal si vous lisez les spécifications (ne vous souvenez pas de la section pour le moment).

2
répondu Some programmer dude 2013-12-24 15:06:52

memcpy est toujours plus sûr, mais ne impliquent un copier

coulée peut conduire à des problèmes de

syndicat-semble être autorisé en C99 et C11, pas sûr de C++

regardez:

Quelle est la stricte règle d'alias?

et

est-ce que le poinçonnage par type à travers une union n'est pas spécifié dans C99, et est-il devenu spécifié dans C11?

2
répondu doron 2017-05-23 11:53:15
float reinterpret_as_float(std::uint32_t ui) {
   return *((float *)&ui);
}

comme simple fonction, son code est traduit en assemblée comme ceci (Pelles C Pour Windows):

fld [esp+4]
ret

Si défini par la fonction inline , alors un code comme celui-ci ( n non signé, x flottant):

x = reinterpret_as_float (n);

est traduit à assembleur comme ceci:

fld [ebp-4]  ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8]  ;LHS of asignment
-2
répondu mcleod_ideafix 2013-12-31 12:08:25