Во-первых, документ следует рассматривать чтение, если вы хотите понять, слабостей с плавающей точкой лучше: «Что каждый ученый должен знать о арифметики с плавающей точкой» http://www.validlab.com/goldberg/paper.pdf
А теперь к некоторым мяса.
Следующий код является голые кости, а попытки произвести IEEE-754 одинарной точности с плавающей точкой из unsigned int
в диапазоне от 0 < < значение 2 . Это формат, с которым вы, скорее всего, столкнетесь на современном оборудовании, и это формат, который вы, похоже, ссылаетесь в своем первоначальном вопросе.
Одноточечные поплавки 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 = 0х80: экспонент = 128 - 127 = 1 (aka. 2)
- Биты 22 .. 0 все 0: Знак = 1.00000000_00000000_0000000. (Примечание: я восстановил скрытый 1).
Таким образом, значение равно 1.0 x 2 = 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 номера.
Для целых чисел> = 2 целое целое не вписывается в значение поля 32-битного поплавка. Вот почему вам нужно «обходить»: вы теряете LSB, чтобы сделать значение подходящим. Таким образом, множественные целые числа будут отображаться в один и тот же шаблон с плавающей точкой. Точное отображение зависит от режима округления (округляется к -Inf, округляется до + Inf, округляется к нулю, округляется до ближайшего четного). Но дело в том, что вы не можете засунуть 24 бита в менее чем 24 бита без какой-либо потери.
Вы можете видеть это с точки зрения приведенного выше кода. Он работает путем выравнивания ведущей 1 к скрытой позиции 1. Если значение было> = 2 , код должен был бы сдвинуть вправо, а не осталось, и это обязательно сдвигает LSB. Режимы округления просто говорят вам, как обращаться с битами, смещенными.
Надеюсь, там будет достаточно информации, чтобы помочь вам также изменить процесс. :-) –
Там определенно был :) Особенно в нашем чате по другому вопросу. Ты мне очень помог, снова спасибо Джо :) –
Эй, Джо, у меня был еще один вопрос для тебя. Плюс сторона, я верю, что все до такой степени, когда значение имеет побитовое и работает с 0x7FFFFF! Так что большое спасибо за вашу помощь до сих пор :) Тем не менее, я получаю это сообщение, когда пытаюсь, и значение с 0x7FFFFF "Immediate 0x007FFFFF не может быть представлен 0-255, сдвинутым влево на 0-23 или дублируются во всех, нечетных или четных байтах » Как вы думаете, возможно ли, что я могу удалить 23-й бит другим способом? –