Десятичное в IEEE с плавающей точкой одинарной точности

Мне интересно узнать, как преобразовать целочисленное значение в формат IEEE с плавающей запятой одинарной точности, используя только побитовые операторы. Однако я не совсем понимаю, что можно сделать, чтобы узнать, сколько осталось логических сдвигов при расчете для показателя степени.

Учитывая int, скажем 15, мы имеем:

Двоичный код: 1111

-> 1.111 x 2 ^ 3 => После размещения десятичной точки после первого бита мы находим, что значение ‘e’ будет равно трем.

E = Exp — смещение
Следовательно, Exp = 130 = 10000010

И значение будет: 111000000000000000000000

Однако я знал, что значение ‘e’ будет равно трем, потому что я смог увидеть, что после размещения десятичного разделителя после первого бита есть три бита. Есть ли более общий способ написания кода для этого общего случая?

Опять же, это для преобразования типа int в float, предполагая, что целое число неотрицательное, ненулевое и не больше максимального пространства, разрешенного для мантиссы.

Кроме того, кто-то может объяснить, почему округление необходимо для значений больше 23 бит?
Заранее спасибо!

1

Решение

Во-первых, вам следует почитать статью, если вы хотите лучше понять слабые места с плавающей запятой: «Что должен знать каждый специалист по вычислительной технике об арифметике с плавающей запятой». http://www.validlab.com/goldberg/paper.pdf

А теперь немного мяса.

Следующий код является пустым и пытается произвести IEEE-754 с плавающей точкой одинарной точности из unsigned int в диапазоне 0 < значение < 224. С этим форматом вы чаще всего сталкиваетесь на современном оборудовании, и это тот формат, на который вы, похоже, ссылаетесь в исходном вопросе.

IEEE-754 с плавающей точкой одинарной точности делятся на три поля: одиночный знаковый бит, 8 битов экспоненты и 23 бита значащих (иногда называемых мантиссой). IEEE-754 использует скрытый 1 значимое, означающее, что значимое на самом деле составляет всего 24 бита. Биты упакованы слева направо, со знаковым битом в бите 31, показателем степени в битах 30 … 23 и значением в битах 22 … 0. Следующая диаграмма из Википедии иллюстрирует:

формат с плавающей запятой

Экспонента имеет смещение 127, означающее, что фактический показатель степени, связанный с числом с плавающей запятой, на 127 меньше значения, сохраненного в поле показателя степени. Следовательно, показатель степени 0 будет закодирован как 127.

(Примечание: полная статья в Википедии может быть вам интересна. http://en.wikipedia.org/wiki/Single_precision_floating-point_format )

Поэтому номер IEEE-754 0x40000000 интерпретируется следующим образом:

  • Бит 31 = 0: положительное значение
  • Биты 30 .. 23 = 0x80: экспонента = 128 — 127 = 1 (иначе 21)
  • Биты 22 .. 0 — все 0: значимо = 1.00000000_00000000_0000000. (Обратите внимание, я восстановил скрытый 1).

Таким образом, значение 1,0 х 21 = 2,0

Чтобы преобразовать unsigned int в ограниченном диапазоне, указанном выше, затем в формате IEEE-754 вы можете использовать функцию, подобную приведенной ниже. Требуются следующие шаги:

  • Выравнивает 1 перед целым числом по положению скрытый 1 в представлении с плавающей точкой.
  • При выравнивании целого числа записывается общее количество сделанных смен.
  • Маскирует скрытое 1.
  • Используя количество сделанных смен, вычисляет показатель и добавляет его к числу.
  • С помощью reinterpret_cast, преобразует полученный битовый шаблон в float, Эта часть безобразна, потому что она использует указатель типа. Вы также можете сделать это, злоупотребив union, Некоторые платформы обеспечивают внутреннюю работу (например, _itofсделать эту реинтерпретацию менее уродливой.

Есть намного более быстрые способы сделать это; этот должен быть педагогически полезным, если не суперэффективным:

float uint_to_float(unsigned int significand)
{
// Only support 0 < significand < 1 << 24.
if (significand == 0 || significand >= 1 << 24)
return -1.0;  // or abort(); or whatever you'd like here.

int shifts = 0;

//  Align the leading 1 of the significand to the hidden-1
//  position.  Count the number of shifts required.
while ((significand & (1 << 23)) == 0)
{
significand <<= 1;
shifts++;
}

//  The number 1.0 has an exponent of 0, and would need to be
//  shifted left 23 times.  The number 2.0, however, has an
//  exponent of 1 and needs to be shifted left only 22 times.
//  Therefore, the exponent should be (23 - shifts).  IEEE-754
//  format requires a bias of 127, though, so the exponent field
//  is given by the following expression:
unsigned int exponent = 127 + 23 - shifts;

//  Now merge significand and exponent.  Be sure to strip away
//  the hidden 1 in the significand.
unsigned int merged = (exponent << 23) | (significand & 0x7FFFFF);//  Reinterpret as a float and return.  This is an evil hack.
return *reinterpret_cast< float* >( &merged );
}

Вы можете сделать этот процесс более эффективным, используя функции, которые определяют первую цифру 1. (Они иногда идут по именам, как clz для «считать ведущие нули», или norm для «нормализации».)

Вы также можете расширить это до чисел со знаком, записав знак, взяв абсолютное значение целого числа, выполнив шаги, описанные выше, и затем поместив знак в бит 31 числа.

Для целых чисел> = 224, все целое число не вписывается в поле значений и 32-разрядного формата с плавающей запятой. Вот почему вам нужно «округлить»: вы теряете младшие биты, чтобы соответствовать значению. Таким образом, несколько целых чисел в конечном итоге будут отображаться в один и тот же шаблон с плавающей точкой. Точное отображение зависит от режима округления (округление до -Inf, округление до + Inf, округление до нуля, округление до ближайшего четного). Но в том-то и дело, что вы не можете засунуть 24 бита в менее чем 24 бита без некоторой потери.

Вы можете увидеть это с помощью приведенного выше кода. Это работает, выравнивая ведущую 1 к скрытой 1 позиции. Если значение было> = 224, код должен быть сдвинут право, не оставил, и это обязательно сдвигает младшие биты. Режимы округления просто скажут вам, как обрабатывать сдвинутые биты.

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]