Question

Voici les objectifs que je suis en train de réaliser:

  • Je dois emballer 32 bits IEEE flotte en 30 bits.
  • Je veux faire en diminuant la taille de mantisse par 2 bits.
  • L'opération elle-même doit être aussi rapide que possible.
  • Je suis conscient du fait que une certaine précision sera perdu, ce qui est acceptable.
  • Ce serait un avantage, si cette opération ne ruinerait pas des cas particuliers comme SNAN, QNAN, infinités, etc. Mais je suis prêt à sacrifier cette survitesse.

Je suppose que cette question se compose de deux parties:

1) Puis-je simplement effacer les bits les moins significatifs de mantisse? Je l'ai essayé, et jusqu'à présent il fonctionne, mais peut-être que je demande des ennuis ... Quelque chose comme:

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;

2) S'il y a des cas où 1) échouera, quel serait le meilleur moyen d'y parvenir?

Merci à l'avance

Était-ce utile?

La solution 4

Je ne peux pas sélectionner l'une des réponses que celui défini, parce que la plupart d'entre eux ont des informations valides, mais pas tout à fait ce que je cherchais. Je vais donc résumer mes conclusions.

La méthode de conversion que j'ai posté dans ma partie 1) de la question est manifestement erronée par la norme C, pour que d'autres méthodes pour les bits de l'extrait flotteur doit être utilisé.

Et le plus important ... pour autant que je comprends à la lecture des réponses et d'autres sources au sujet IEEE754 flotteurs, il est autorisé à déposer les bits les moins significatifs de mantisse. Il touchera surtout que la précision, à une exception près: SNAN. Depuis SNAN est représenté par ensemble d'exposants à 255, et mantisse! = 0, il peut y avoir la situation où mantisse serait <= 3, et laissant tomber deux derniers bits convertirait SNAN à +/- Infinity. Mais depuis SNAN ne sont pas générés au cours d'opérations en virgule flottante sur CPU, son coffre-fort dans un environnement contrôlé.

Autres conseils

Vous viole effectivement les règles strictes d'aliasing (section 3.10 de la norme C ++) avec ces moulages de Réinterpréter. Cela va sans doute exploser dans votre visage lorsque vous activez les optimisations du compilateur.

La norme C de la section 3.10 du paragraphe 15 dit:

  

Si une tentative de programme pour accéder à la valeur stockée d'un objet à travers une lvalue autre que celle de l'un des types suivants le comportement est indéfini

     
      
  • le type de dynamique de l'objet,
  •   
  • une version cv-qualifié du type dynamique de l'objet,
  •   
  • un type similaire au type dynamique de l'objet,
  •   
  • un type qui est le type signé ou non signé correspondant au type dynamique de l'objet,
  •   
  • un type qui est la signature ou le type non signé correspondant à une version cv-qualifié du type dynamique de l'objet,
  •   
  • un type agrégat ou de l'union qui comprend l'un des types précités, parmi ses membres (y compris, de façon récursive, un membre d'un sous-agrégat ou union contenu),
  •   
  • un type qui est une classe de base (éventuellement cv-qualifié) Type de type dynamique de l'objet,
  •   
  • un type de char ou unsigned char.
  •   

Plus précisément, 3.10 / 15 ne nous permet pas d'accéder à un objet flottant via une lvalue de type unsigned int. En fait, je me suis mordu par cela. Le programme que j'ai écrit a cessé de fonctionner après la mise optimisations. Apparemment, GCC ne s'attendait pas à une lvalue de type float à alias une lvalue de type int qui est une hypothèse équitable 3.10 / 15. Les instructions se sont bousculés par l'optimiseur sous la règle que, si l'exploitation 3.10 / 15 et il ne fonctionne plus.

Dans ce qui suit hypothèses

  • float correspond vraiment à un 32bit IEEE-float,
  • sizeof (float) == sizeof (int)
  • unsigned int n'a pas de bits de remplissage ou des représentations de piégeage

vous devriez être en mesure de le faire comme ceci:

/// returns a 30 bit number
unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return r >> 2;
}

float unpack_float(unsigned int x) {
    x <<= 2;
    float r;
    std::memcpy(&r,&x,sizeof r);
    return r;
}

Cela ne souffre pas de la « 3.10-violation » et est généralement très rapide. Au moins friandises GCC memcpy comme une fonction intrinsèque. Si vous ne avez pas besoin des fonctions de travail avec Nans, infinités ou des nombres avec une amplitude extrêmement élevée, vous pouvez même améliorer la précision en remplaçant « r >> 2 » avec « (r + 1) >> 2 »:

unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return (r+1) >> 2;
}

Cela fonctionne même si elle change l'exposant en raison d'un débordement de mantisse parce que le IEEE-754 de codage cartes valeurs à virgule flottante consécutives à des nombres entiers consécutifs (ignorant +/- zéro). Cette cartographie se rapproche en fait un logarithme assez bien.

chute Aveuglément les 2 RLSP du flotteur peut échouer pour petit nombre d'encodages peu courants NaN.

NaN est codé comme exposant = 255, mantisse! = 0, mais IEEE-754 ne dit rien au sujet de laquelle les valeurs de mantiassa doivent être utilisées. Si la valeur de mantisse est <= 3, vous pouvez transformer un NaN en une infinité!

Vous devriez l'encapsuler dans un struct, de sorte que vous ne mélangez pas accidentellement l'utilisation du flotteur tagguées avec « unsigned int » régulier:

#include <iostream>
using namespace std;

struct TypedFloat {
    private:
        union {
            unsigned int raw : 32;
            struct {
                unsigned int num  : 30;  
                unsigned int type : 2;  
            };
        };
    public:

        TypedFloat(unsigned int type=0) : num(0), type(type) {}

        operator float() const {
            unsigned int tmp = num << 2;
            return reinterpret_cast<float&>(tmp);
        }
        void operator=(float newnum) {
            num = reinterpret_cast<int&>(newnum) >> 2;
        }
        unsigned int getType() const {
            return type;
        }
        void setType(unsigned int type) {
            this->type = type;
        }
};

int main() { 
    const unsigned int TYPE_A = 1;
    TypedFloat a(TYPE_A);

    a = 3.4;
    cout << a + 5.4 << endl;
    float b = a;
    cout << a << endl;
    cout << b << endl;
    cout << a.getType() << endl;
    return 0;
}

Je ne peux pas garantir sa portabilité cependant.

Combien de précision avez-vous besoin? Si le flotteur 16 bits est suffisant (suffisant pour certains types de graphiques), puis le flotteur 16 bits de ILM ( « moitié »), une partie de OpenEXR est grande, obéit toutes sortes de règles (http://www.openexr.com/ ), et vous aurez beaucoup d'espace laissé après vous emballez dans un struct.

Par contre, si vous connaissez la gamme approximative des valeurs qu'ils vont prendre, vous devriez envisager point fixe. Ils sont plus utiles que la plupart des gens se rendent compte.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top