Ответ 1
В .NET a float
представляется с помощью IEEE binary32 число с плавающей точкой с одиночной точностью, хранящееся с использованием 32 бит. По-видимому, код строит это число, собрав биты в int
, а затем переводит его в float
с помощью unsafe
. Листинг - это то, что в терминах С++ называется reinterpret_cast
, где преобразование не выполняется при выполнении трансляции - биты просто переинтерпретируются как новый тип.
Собранный номер 4019999A
в шестнадцатеричном виде или 01000000 00011001 10011001 10011010
в двоичном формате:
- Знаковый бит равен 0 (это положительное число).
- Биты экспоненты составляют
10000000
(или 128), что приводит к экспоненте 128 - 127 = 1 (доля умножается на 2 ^ 1 = 2). - Дробные разряды
00110011001100110011010
, которые, если ничего другого, почти не имеют узнаваемого шаблона нулей и единиц.
Возвращаемый float имеет те же самые биты, что и 2.4, преобразованные в плавающую точку, и вся функция просто может быть заменена литералом 2.4f
.
Последний нуль, который "разрывает бит-шаблон" фракции, возможно, может сделать поплавок совпадающим с тем, что может быть написано с использованием литерала с плавающей запятой?
Так в чем же разница между регулярным литом и этим странным "небезопасным актом"?
Предположим, что следующий код:
int result = 0x4019999A // 1075419546
float normalCast = (float) result;
float unsafeCast = *(float*) &result; // Only possible in an unsafe context
Первый актер принимает целое число 1075419546
и преобразует его в свое представление с плавающей запятой, например. 1075419546f
. Это включает в себя вычисление битов знака, экспоненты и дробей, необходимых для представления исходного целого числа в виде числа с плавающей запятой. Это нетривиальное вычисление, которое нужно сделать.
Второе приведение более зловещее (и может быть выполнено только в небезопасном контексте). &result
берет адрес result
, возвращая указатель на место, где хранится целое число 1075419546
. Оператор разыменования указателя *
может затем использоваться для извлечения значения, на которое указывает указатель. Использование *&result
будет извлекать целое число, сохраненное в местоположении, но, сначала наведя указатель на float*
(указатель на float
), вместо этого извлекается float из ячейки памяти, в результате чего назначается float 2.4f
до unsafeCast
. Таким образом, повествование *(float*) &result
дает мне указатель на result
и предполагает, что указатель является указателем на float
и извлекает значение, на которое указывает указатель.
В отличие от первого броска, второй бросок не требует никаких вычислений. Он просто перетаскивает 32 бит, хранящиеся в result
, в unsafeCast
(к счастью, также 32 бит).
В общем, выполнение подобного типа может потерпеть неудачу разными способами, но используя unsafe
, вы сообщаете компилятору, что знаете, что делаете.