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.
Le nombre assemblé est 4019999A
en hexadécimal ou 01000000 00011001 10011001 10011010
en binary:
10000000
(ou 128) résultant en l’exposant 128 – 127 = 1 (la fraction est multipliée par 2 ^ 1 = 2). 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.