В .NET float
представляется с помощью бинарного32 IEEE числа одинарной точности с плавающей запятой, хранящегося с использованием 32 биты. Очевидно, код создает это число, собирая биты в int
, а затем преобразуя его в float
, используя unsafe
. Приведение - это то, что в терминах C++ называется reinterpret_cast
, где при выполнении приведения не выполняется преобразование - биты просто переинтерпретируются как новый тип.
Собранное число равно 4019999A
в шестнадцатеричном формате или 01000000 00011001 10011001 10011010
в двоичном:
- Знаковый бит равен 0 (это положительное число).
- Биты показателя степени равны
10000000
(или 128), в результате чего показатель степени 128 - 127 = 1 (дробь умножается на 2^1 = 2).
- Биты дроби — это
00110011001100110011010
, которые, по крайней мере, имеют почти узнаваемую структуру нулей и единиц.
Возвращаемое число с плавающей запятой имеет те же самые биты, что и 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
) вместо этого из ячейки памяти извлекается число с плавающей запятой, в результате чего число с плавающей запятой 2.4f
назначается unsafeCast
. Таким образом, описание *(float*) &result
таково: дайте мне указатель на result
и предположите, что указатель является указателем на float
, и извлеките значение, на которое указывает указатель.
В отличие от первого приведения, второе приведение не требует никаких вычислений. Он просто помещает 32-битное значение, хранящееся в result
, в unsafeCast
(которое, к счастью, также является 32-битным).
В целом выполнение подобного приведения может привести к сбою во многих отношениях, но, используя unsafe
, вы сообщаете компилятору, что знаете, что делаете.
person
Martin Liversage
schedule
04.10.2012
result
не является указателем.. я думаю, вы не можете преобразовать его адрес в указатель с плавающей запятой - person Anirudha   schedule 04.10.2012*
и&
, только если у вас есть модификаторunsafe
. - person Marc Gravell   schedule 04.10.2012return
идентичен - это простоfloat
. Вы можете представить себе лишнюю строку:float tmp = *(float*)(&result); return tmp;
. Любая разница не имеет никакого отношения кreturn
. - person Marc Gravell   schedule 04.10.2012return (float)result;
- это преобразование - оно преобразует целое число123
в число с плавающей запятой123.0F
- но это не повторная интерпретация; байты для123
и123.0F
совершенно разные. Приведение типа re-interpretive просто говорит, что здесь 4 байта; теперь относитесь к ним как кfloat
- person Marc Gravell   schedule 04.10.2012