Sur .NET, un float
est représentée par un IEEE binaire32 nombre flottant de précision simple stocké en utilisant 32 bits. Apparemment, le code construit ce nombre en assemblant les bits dans un fichier de type int
et le convertit ensuite en un float
en utilisant unsafe
. Le cast est ce que l'on appelle en C++ un reinterpret_cast
où aucune conversion n'est effectuée lorsque le transfert est réalisé - les bits sont simplement réinterprétés comme un nouveau type.
![IEEE single precision floating number]()
Le numéro assemblé est 4019999A
en hexadécimal ou 01000000 00011001 10011001 10011010
en binaire :
- Le bit de signe est 0 (c'est un nombre positif).
- Les bits de l'exposant sont
10000000
(ou 128), ce qui donne l'exposant 128 - 127 = 1 (la fraction est multipliée par 2^1 = 2).
- Les bits de fraction sont
00110011001100110011010
qui, à défaut d'autre chose, ont presque un modèle reconnaissable de zéros et de uns.
Le flottant retourné a exactement les mêmes bits que 2.4 converti en virgule flottante et la fonction entière peut simplement être remplacée par le littéral 2.4f
.
Le zéro final qui "casse le schéma binaire" de la fraction est peut-être là pour faire correspondre le flottant à quelque chose qui peut être écrit en utilisant un littéral à virgule flottante ?
Alors quelle est la différence entre un plâtre normal et ce plâtre bizarre "non sécurisé" ?
Supposons le code suivant :
int result = 0x4019999A // 1075419546
float normalCast = (float) result;
float unsafeCast = *(float*) &result; // Only possible in an unsafe context
La première distribution prend l'entier 1075419546
et le convertit en sa représentation en virgule flottante, par exemple 1075419546f
. Cela implique de calculer les bits de signe, d'exposant et de fraction nécessaires pour représenter le nombre entier d'origine sous forme de nombre à virgule flottante. Il s'agit d'un calcul non trivial qui doit être effectué.
La deuxième distribution est plus sinistre (et ne peut être exécutée que dans un contexte non sécurisé). L'adresse &result
prend l'adresse de result
renvoyant un pointeur vers l'emplacement où l'entier 1075419546
est stocké. L'opérateur de déréférencement de pointeur *
peut alors être utilisé pour récupérer la valeur pointée par le pointeur. Utilisation de *&result
récupérera le nombre entier stocké à l'emplacement cependant en coulant d'abord le pointeur vers un fichier float*
(un pointeur vers un float
), un flotteur est plutôt extrait de l'emplacement mémoire, ce qui donne le flotteur 2.4f
étant affecté à unsafeCast
. Donc le récit de *(float*) &result
es donnez-moi un pointeur sur result
et supposer que le pointeur est un pointeur vers un float
et récupère la valeur pointée par le pointeur .
Contrairement au premier lancer, le deuxième lancer ne nécessite aucun calcul. Elle envoie simplement le 32 bits stocké dans result
en unsafeCast
(qui, heureusement, est aussi en 32 bits).
En général, l'exécution d'un tel lancer peut échouer de plusieurs façons mais en utilisant unsafe
vous dites au compilateur que vous savez ce que vous faites.