2013-11-30 5 views
6

Мне было интересно, можете ли вы объяснить процесс преобразования целого числа в float или float в целое число. Для моего класса мы должны делать это, используя только побитовые операторы, но я думаю, что твердое понимание приведения от типа к типу поможет мне больше на этом этапе.Преобразование Int для Float/Float to Int с использованием побитового

Из того, что я знаю до сих пор, для int, чтобы плавать, вам нужно будет преобразовать целое число в двоичное, нормализовать значение целого числа, найдя значение, экспоненту и дробью, а затем вывести значение в float из там?

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

Я пытался следовать инструкциям из этого вопроса: Casting float to int (bitwise) in C

Но я не был в состоянии понять это.

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

Заранее спасибо

ответ

12

Во-первых, документ следует рассматривать чтение, если вы хотите понять, слабостей с плавающей точкой лучше: «Что каждый ученый должен знать о арифметики с плавающей точкой» 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. Следующая диаграмма иллюстрирует из Википедии:

floating point format

Показатель имеет смещение 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. Режимы округления просто говорят вам, как обращаться с битами, смещенными.

+0

Надеюсь, там будет достаточно информации, чтобы помочь вам также изменить процесс. :-) –

+0

Там определенно был :) Особенно в нашем чате по другому вопросу. Ты мне очень помог, снова спасибо Джо :) –

+0

Эй, Джо, у меня был еще один вопрос для тебя. Плюс сторона, я верю, что все до такой степени, когда значение имеет побитовое и работает с 0x7FFFFF! Так что большое спасибо за вашу помощь до сих пор :) Тем не менее, я получаю это сообщение, когда пытаюсь, и значение с 0x7FFFFF "Immediate 0x007FFFFF не может быть представлен 0-255, сдвинутым влево на 0-23 или дублируются во всех, нечетных или четных байтах » Как вы думаете, возможно ли, что я могу удалить 23-й бит другим способом? –

2

Вы проверили с плавающей точкой представление IEEE 754?

В 32-битной нормированной форме он имеет битовый знак (мантиссы), 8-разрядный показатель (избыток-127, я думаю) и 23-битную мантисса в «десятичном», за исключением того, что «0.» (всегда в этой форме), а радиус равен 2, а не 10. То есть: значение MSB равно 1/2, следующий бит 1/4 и так далее.

1

Ответ Джо З. элегантный, но диапазон входных значений очень ограничен. 32 бит с плавающей точкой может хранить все целые значения из следующего диапазона:

[-2 ... + 2 ] = [-16777216 ... + 16777216]

и некоторые другие значения вне этого диапазона.

Весь диапазон будет покрыт этим:

float int2float(int value) 
{ 
    // handles all values from [-2^24...2^24] 
    // outside this range only some integers may be represented exactly 
    // this method will use truncation 'rounding mode' during conversion 

    // we can safely reinterpret it as 0.0 
    if (value == 0) return 0.0; 

    if (value == (1U<<31)) // ie -2^31 
    { 
     // -(-2^31) = -2^31 so we'll not be able to handle it below - use const 
     value = 0xCF000000; 
     return *((float*)&value); 
    } 

    int sign = 0; 

    // handle negative values 
    if (value < 0) 
    { 
     sign = 1U << 31; 
     value = -value; 
    } 

    // although right shift of signed is undefined - all compilers (that I know) do 
    // arithmetic shift (copies sign into MSB) is what I prefer here 
    // hence using unsigned abs_value_copy for shift 
    unsigned int abs_value_copy = value; 

    // find leading one 
    int bit_num = 31; 
    int shift_count = 0; 

    for(; bit_num > 0; bit_num--) 
    { 
     if (abs_value_copy & (1U<<bit_num)) 
     { 
      if (bit_num >= 23) 
      { 
       // need to shift right 
       shift_count = bit_num - 23; 
       abs_value_copy >>= shift_count; 
      } 
      else 
      { 
       // need to shift left 
       shift_count = 23 - bit_num; 
       abs_value_copy <<= shift_count; 
      } 
      break; 
     } 
    } 

    // exponent is biased by 127 
    int exp = bit_num + 127; 

    // clear leading 1 (bit #23) (it will implicitly be there but not stored) 
    int coeff = abs_value_copy & ~(1<<23); 

    // move exp to the right place 
    exp <<= 23; 

    int ret = sign | exp | coeff; 

    return *((float*)&ret); 
} 

Конечно, есть и другие способы, чтобы найти значение абс ИНТ (сучьев). Аналогично, couting ведущие нули также могут быть выполнены без ветки, поэтому рассмотрим этот пример как пример ;-).

Смежные вопросы