Fonction C # ‘unsafe’ – * (float *) (& result) vs (float) (résultat)

Quelqu’un peut-il expliquer de manière simple les codes ci-dessous:

public unsafe static float sample(){ int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); return *(float*)(&result); //don't know what for... please explain } 

Remarque: le code ci-dessus utilise une fonction non sécurisée

Pour le code ci-dessus, j’ai du mal à comprendre car je ne comprends pas la différence entre sa valeur de retour et la valeur de retour ci-dessous:

 return (float)(result); 

Est-il nécessaire d’utiliser une fonction non sécurisée si vous retournez *(float*)(&result) ?

Sur .NET, un float est représenté en utilisant un nombre flottant simple précision IEEE binary32 stocké en 32 bits. Apparemment, le code construit ce nombre en assemblant les bits dans un int et le jette ensuite dans un float utilisant unsafe . La dissortingbution est ce que les termes C ++ sont appelés un reinterpret_cast où aucune conversion n’est effectuée lorsque la dissortingbution est effectuée – les bits sont simplement réinterprétés comme un nouveau type.

Nombre flottant simple précision IEEE

Le nombre assemblé est 4019999A en hexadécimal ou 01000000 00011001 10011001 10011010 en binary:

  • Le bit de signe est 0 (c’est un nombre positif).
  • Les bits d’exposant sont 10000000 (ou 128) résultant en l’exposant 128 – 127 = 1 (la fraction est multipliée par 2 ^ 1 = 2).
  • Les fractions de bits sont 00110011001100110011010 qui, pour ne rien dire, ont presque un motif reconnaissable de zéros et de uns.

Le flottant renvoyé a exactement les mêmes bits que 2.4 convertis en virgule flottante et la fonction entière peut simplement être remplacée par le littéral 2.4f .

Le dernier zéro qui “brise le bit pattern” de la fraction est peut-être là pour faire correspondre le float à quelque chose qui peut être écrit en utilisant un littéral à virgule flottante?


Alors, quelle est la différence entre une dissortingbution régulière et cette “dissortingbution dangereuse” étrange?

Supposons le code suivant:

 int result = 0x4019999A // 1075419546 float normalCast = (float) result; float unsafeCast = *(float*) &result; // Only possible in an unsafe context 

Le premier cast prend l’entier 1075419546 et le convertit en sa représentation en virgule flottante, par exemple 1075419546f . Cela implique le calcul des bits de signe, d’exposant et de fraction nécessaires pour représenter le nombre entier d’origine sous forme de nombre à virgule flottante. C’est un calcul non sortingvial à faire.

Le deuxième casting est plus sinistre (et ne peut être effectué que dans un contexte dangereux). Le &result prend l’adresse du result renvoyant un pointeur vers l’emplacement où l’entier 1075419546 est stocké. L’opérateur de déréférencement de pointeur * peut ensuite être utilisé pour récupérer la valeur indiquée par le pointeur. Utiliser *&result récupère le nombre entier stocké à l’emplacement mais en déplaçant d’abord le pointeur sur un float* (un pointeur sur un float ), un float est récupéré à partir de l’emplacement mémoire, le float 2.4f étant affecté à unsafeCast . Ainsi, le récit de *(float*) &result est -il un pointeur sur le result et suppose que le pointeur est un pointeur sur un float et récupère la valeur pointée par le pointeur .

Contrairement à la première dissortingbution, la seconde dissortingbution ne nécessite aucun calcul. Il place simplement le 32 bits stocké dans result dans unsafeCast (ce qui est heureusement aussi 32 bits).

En général, effectuer un casting comme celui-ci peut échouer de plusieurs manières, mais en utilisant unsafe vous dites au compilateur que vous savez ce que vous faites.

Si j’interprète ce que la méthode fait correctement, c’est un équivalent sûr:

 public static float sample() { int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); byte[] data = BitConverter.GetBytes(result); return BitConverter.ToSingle(data, 0); } 

Comme on l’a déjà dit, il réinterprète la valeur int comme un float .

Cela ressemble à une tentative d’optimisation. Au lieu d’effectuer des calculs en virgule flottante, vous effectuez des calculs sur la représentation Integer d’un nombre à virgule flottante.

Rappelez-vous que les flottants sont stockés comme des valeurs binarys, tout comme ints.

Une fois le calcul effectué, vous utilisez des pointeurs et des transtypages pour convertir l’entier en valeur flottante.

Ce n’est pas la même chose que de convertir la valeur en flottant. Cela va transformer la valeur int 1 en float 1.0. Dans ce cas, vous transformez la valeur int en nombre à virgule flottante décrit par la valeur binary stockée dans l’int.

C’est assez difficile à expliquer correctement. Je vais chercher un exemple. 🙂

Edit: Regardez ici: http://en.wikipedia.org/wiki/Fast_inverse_square_root

Votre code fait essentiellement la même chose que décrit dans cet article.

Re: Que fait-il?

Il prend la valeur des octets stockés int et interprète ces octets comme un flottant (sans conversion).

Heureusement, les flottants et les ints ont la même taille de données de 4 octets.

Parce que Sarge Borsch a demandé, voici l’équivalent «Union»:

 [StructLayout(LayoutKind.Explicit)] struct ByteFloatUnion { [FieldOffset(0)] internal byte byte0; [FieldOffset(1)] internal byte byte1; [FieldOffset(2)] internal byte byte2; [FieldOffset(3)] internal byte byte3; [FieldOffset(0)] internal float single; } public static float sample() { ByteFloatUnion result; result.single = 0f; result.byte0 = 154; result.byte1 = 153; result.byte2 = 25; result.byte3 = 64; return result.single; } 

Comme d’autres l’ont déjà décrit, il traite les octets d’un int comme s’ils étaient un flottant.

Vous pourriez obtenir le même résultat sans utiliser un code dangereux comme celui-ci:

 public static float sample() { int result = 154 + (153 << 8) + (25 << 16) + (64 << 24); return BitConverter.ToSingle(BitConverter.GetBytes(result), 0); } 

Mais alors ce ne sera plus très rapide et vous pourriez aussi bien utiliser les fonctions floats / doubles que les fonctions mathématiques.