web-dev-qa-db-fra.com

Comment effectuer une opération au niveau des bits sur des nombres à virgule flottante

J'ai essayé ceci:

float a = 1.4123;
a = a & (1 << 3);

Je reçois une erreur du compilateur en disant que l'opérande de & ne peut pas être de type float.

Quand je fais:

float a = 1.4123;
a = (int)a & (1 << 3);

Je fais fonctionner le programme. La seule chose à faire est que l'opération au niveau du bit est effectuée sur la représentation entière du nombre obtenu après arrondi.

Ce qui suit n'est pas non plus autorisé.

float a = 1.4123;
a = (void*)a & (1 << 3);

Je ne comprends pas pourquoi int peut être converti en void* mais pas float.

Je fais ceci pour résoudre le problème décrit dans la question Stack Overflow Comment résoudre des équations linéaires en utilisant un algorithme génétique?.

42
Rohit Banga

Au niveau de la langue, il n’existe pas d'opération "bit à bit sur les nombres à virgule flottante". Les opérations binaires en C/C++ fonctionnent sur la représentation de la valeur d'un nombre. Et la représentation en valeur des nombres en virgule flottante n'est pas définie en C/C++. Les nombres en virgule flottante n'ont pas de bits au niveau de la représentation des valeurs, raison pour laquelle vous ne pouvez pas leur appliquer d'opérations au niveau des bits.

Tout ce que vous pouvez faire est d'analyser le contenu en bits de la mémoire brute occupée par le nombre à virgule flottante. Pour cela, vous devez utiliser une union comme suggéré ci-dessous ou (de manière équivalente, et uniquement en C++) réinterpréter l'objet en virgule flottante comme un tableau d'objets unsigned char, comme dans

float f = 5;
unsigned char *c = reinterpret_cast<unsigned char *>(&f);
// inspect memory from c[0] to c[sizeof f - 1]

Et s'il vous plaît, n'essayez pas de réinterpréter un objet float en tant qu'objet int, comme le suggèrent d'autres réponses. Cela n'a pas beaucoup de sens, c'est illégal et il n'est pas garanti que cela fonctionne dans les compilateurs qui suivent les règles strictes en matière d'aliasing dans l'optimisation. Le seul moyen légal d'inspecter le contenu de la mémoire en C++ consiste à le réinterpréter sous la forme d'un tableau de [signed/unsigned] char.

Notez également que, techniquement, il n’est pas garanti que la représentation en virgule flottante sur votre système soit conforme à IEEE754 (bien qu’elle soit en pratique sauf si vous lui permettez explicitement de ne pas l’être, et uniquement en ce qui concerne -0.0, ± infinity et NaN).

68
AnT

Si vous essayez de changer les bits dans la représentation en virgule flottante, vous pouvez faire quelque chose comme ceci:

union fp_bit_twiddler {
    float f;
    int i;
} q;
q.f = a;
q.i &= (1 << 3);
a = q.f;

Comme le note AndreyT, accéder à une union comme celle-ci appelle un comportement indéfini, et le compilateur pourrait prendre les armes et vous étrangler. Faites ce qu'il suggère à la place.

18
mob
float a = 1.4123;
unsigned int* inta = reinterpret_cast<unsigned int*>(&a);
*inta = *inta & (1 << 3);
8
Chap

Regardez ce qui suit. Inspiré par la racine carrée rapide rapide:

#include <iostream>
using namespace std;

int main()
{
    float x, td = 2.0;
    int ti = *(int*) &td;
    cout << "Cast int: " << ti << endl;
    ti = ti>>4;
    x = *(float*) &ti;
    cout << "Recast float: " << x << endl;
    return 0; 
}
6
Justin

@loi de la populace:

Meilleur:

#include <stdint.h>
...
union fp_bit_twiddler {
    float f;
    uint32_t u;
} q;

/* mutatis mutandis ... */

Pour ces valeurs int seront probablement correctes, mais en général, vous devriez utiliser Des entettes non signées pour le transfert de bits afin d'éviter les effets des décalages arithmétiques. Et Le uint32_t fonctionnera même sur des systèmes dont l’intimité n’est pas de 32 bits.

4
Tim Schaeffer

L'implémentation Python dans Opérations au niveau des bits à virgule flottante (recette Python) d'opérations au niveau des bits à virgule flottante fonctionne en représentant des nombres en binaire qui s'étend à l'infini à gauche et à droite du point fractionnaire. Parce que les nombres à virgule flottante ont un zéro signé sur la plupart des architectures, il utilise le complément de chacun pour représenter les nombres négatifs (eh bien, en fait, il prétend simplement le faire et utilise quelques astuces pour obtenir l'apparence).

Je suis sûr qu'il peut être adapté pour fonctionner en C++, mais vous devez faire attention à ne pas laisser les bons décalages déborder lors de l'égalisation des exposants.

2
Pyry Pakkanen

Les opérateurs au niveau des bits NE DOIVENT PAS être utilisés sur des flottants, car les flottants sont spécifiques au matériel, quelle que soit la similarité de votre matériel. Sur quel projet/travail voulez-vous risquer "bien cela a fonctionné sur ma machine"? Au lieu de cela, pour C++, vous pouvez obtenir une "sensation" similaire pour les opérateurs de décalage de bits en surchargeant l'opérateur de flux sur un wrapper "object" pour un float:

// Simple object wrapper for float type as templates want classes.
class Float
{
float m_f;
public:
    Float( const float & f )
    : m_f( f )
    {
    }

    operator float() const
    {
        return m_f;
    }
};

float operator>>( const Float & left, int right )
{
    float temp = left;
    for( right; right > 0; --right )
    {
        temp /= 2.0f;
    }
    return temp;
}

float operator<<( const Float & left, int right )
{
    float temp = left;
    for( right; right > 0; --right )
    {
        temp *= 2.0f;
    }
    return temp;
}

int main( int argc, char ** argv )
{
    int a1 = 40 >> 2; 
    int a2 = 40 << 2;
    int a3 = 13 >> 2;
    int a4 = 256 >> 2;
    int a5 = 255 >> 2;

    float f1 = Float( 40.0f ) >> 2; 
    float f2 = Float( 40.0f ) << 2;
    float f3 = Float( 13.0f ) >> 2;
    float f4 = Float( 256.0f ) >> 2;
    float f5 = Float( 255.0f ) >> 2;
}

Vous aurez un reste que vous pourrez jeter en fonction de votre implémentation souhaitée.

1
Kit10
float a = 1.4123;
int *b = (int *)&a;
*b = *b & (1 << 3);
// a is now the IEEE floating-point value caused by the manipulation of *b
// equals 1.121039e-44 (tested on my system)

Cela ressemble à la réponse de Justin, sauf que cela ne crée qu'une vue des bits des mêmes registres que a. Ainsi, lorsque vous manipulez *b, la valeur de a change en conséquence.

1
Patrick Roberts

FWIW, il existe un cas d'utilisation réel pour les opérations bit à bit sur virgule flottante (je viens juste de le rencontrer récemment) - les shaders écrits pour les GPU qui ne prennent en charge que les anciennes versions de GLSL (les versions 1.2 et antérieures ne prenaient pas en charge les opérateurs binaire) et où il y aurait perte de précision si les flotteurs étaient convertis en ints.

Les opérations binaires peuvent être implémentées sur des nombres à virgule flottante à l'aide de contrôles de reste (modulo) et d'inégalité. Par exemple:

float A = 0.625; //value to check; ie, 160/256
float mask = 0.25; //bit to check; ie, 1/4
bool result = (mod(A, 2.0 * mask) >= mask); //non-zero if bit 0.25 is on in A

Ce qui précède suppose que A est compris entre [0..1) et qu’il n’ya qu’un seul "bit" dans le masque à vérifier, mais il peut être généralisé pour des cas plus complexes.

Cette idée est basée sur certaines des informations trouvées dans est-ce-qu'il est possible d'implémenter-opérateurs-au-bit-à-l'aide de l'arithmétique de nombres entiers

S'il n'y a même pas de fonction mod intégrée, cela peut aussi être implémenté assez facilement. Par exemple:

float mod(float num, float den)
{
    return num - den * floor(num / den);
}
0
djulien