Преобразование int до float/Float в int с помощью побитового

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

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

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

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

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

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

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

3 ответов


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

а теперь к мясу.

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

поплавки IEEE-754 с одной точностью делятся на три поля: бит одного знака, 8 бит экспоненты и 23 бита сигнификации (иногда называемый мантиссой). IEEE-754 использует скрытые 1 significand, что означает, что significand на самом деле составляет 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 = 0x80: показатель = 128-127 = 1 (он же. 21)
  • биты 22 .. 0 - это все 0: Significand = 1.00000000_00000000_0000000. (Заметьте, я восстановил скрытые 1).

таким образом, значение равно 1.0 x 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, целое число не вписывается в поле significand 32-битного формата float. Вот почему вам нужно "округлить": вы теряете LSBs, чтобы сделать значение подходящим. Таким образом, несколько целых чисел в конечном итоге сопоставляются с одним и тем же шаблоном с плавающей запятой. Точное отображение зависит от режима округления (округление к бесконечности, округление до +инф, вокруг к нулю, округление до ближайшего четного). Но дело в том, что вы не можете засунуть 24 биты в меньше, чем 24 бита без потерь.

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


вы проверили представление с плавающей запятой IEEE 754?

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


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

[-224...+224] = [-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);
}

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