Подтвердить что ты не робот

С# 'небезопасная' функция - * (float *) (& result) vs. (float) (результат)

Может ли кто-нибудь объяснить простым способом следующие коды:

public unsafe static float sample(){    
      int result = 154 + (153 << 8) + (25 << 16) + (64 << 24);

      return *(float*)(&result); //don't know what for... please explain
}

Примечание: приведенный выше код использует небезопасную функцию

Для приведенного выше кода у меня тяжелое время, потому что я не понимаю, какая разница между его возвращаемым значением сравнивается с возвращаемым значением ниже:

return (float)(result);

Нужно ли использовать небезопасную функцию, если вы возвращаете *(float*)(&result)?

4b9b3361

Ответ 1

В .NET a float представляется с помощью IEEE binary32 число с плавающей точкой с одиночной точностью, хранящееся с использованием 32 бит. По-видимому, код строит это число, собрав биты в int, а затем переводит его в float с помощью unsafe. Листинг - это то, что в терминах С++ называется reinterpret_cast, где преобразование не выполняется при выполнении трансляции - биты просто переинтерпретируются как новый тип.

IEEE single precision floating number

Собранный номер 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, вы сообщаете компилятору, что знаете, что делаете.

Ответ 2

Если я интерпретирую, что метод делает правильно, это безопасный эквивалент:

public static float sample() {    
   int result = 154 + (153 << 8) + (25 << 16) + (64 << 24);

   byte[] data = BitConverter.GetBytes(result);
   return BitConverter.ToSingle(data, 0);
}

Как уже было сказано, он повторно интерпретирует значение int как float.

Ответ 3

Это выглядит как попытка оптимизации. Вместо вычислений с плавающей запятой вы выполняете вычисления целых чисел в представлении Integer числа с плавающей запятой.

Помните, что float хранится как двоичные значения, как и int.

После выполнения расчета вы используете указатели и кастинг для преобразования целого числа в значение float.

Это не то же самое, что отличать значение от float. Это превратит значение int 1 в float 1.0. В этом случае вы превращаете значение int в число с плавающей запятой, описываемое двоичным значением, хранящимся в int.

Трудно объяснить правильно. Я буду искать пример.: -)

Изменить: Смотрите здесь: http://en.wikipedia.org/wiki/Fast_inverse_square_root

Ваш код в основном делает то же, что описано в этой статье.

Ответ 4

Re: Что он делает?

Он принимает значение байтов, хранящихся в int, и вместо этого интерпретирует эти байты как float (без преобразования).

К счастью, float и ints имеют тот же размер данных в 4 байта.

Ответ 5

Потому что Сардж Борш спросил, здесь эквивалент "Союза":

[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;
}

Ответ 6

Как уже описывали другие, он обрабатывает байты int, как если бы они были float.

Вы можете получить тот же результат, не используя небезопасный код, например:

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

Но тогда это будет не очень быстро, и вы также можете использовать float/doubles и математические функции.