что происходит в фоновом режиме при конвертации int в float - PullRequest
5 голосов
/ 02 ноября 2011

Я немного не понимаю, как можно бросить int на float, шаг за шагом?Предположим, у меня есть целое число со знаком в двоичном формате.Более того, я хочу, чтобы его бросили плавать вручную.Однако я не могу.Таким образом, можно ли мне показать, как сделать это преобразование шаг за шагом?

Я делаю это преобразование в с, много раз?как;

  int a = foo ( );
  float f = ( float ) a ;

Но я не понимаю, что происходит на заднем плане.Кроме того, чтобы хорошо понять, я хочу сделать это преобразование вручную.

РЕДАКТИРОВАТЬ: Если вы знаете много о преобразовании, вы также можете предоставить информацию о для преобразования с плавающей запятой в двойное преобразование.Более того, для float до int

Ответы [ 2 ]

14 голосов
/ 02 ноября 2011

Значения с плавающей точкой (в любом случае IEEE754) в основном состоят из трех компонентов:

  • знак s;
  • серия битов экспоненты e
  • серия битов мантиссы m.

Точность определяет, сколько битов доступно для показателя степени и мантиссы.Давайте рассмотрим значение 0,1 для плавающей запятой одинарной точности:

s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm    1/n
0 01111011 10011001100110011001101
           ||||||||||||||||||||||+- 8388608
           |||||||||||||||||||||+-- 4194304
           ||||||||||||||||||||+--- 2097152
           |||||||||||||||||||+---- 1048576
           ||||||||||||||||||+-----  524288
           |||||||||||||||||+------  262144
           ||||||||||||||||+-------  131072
           |||||||||||||||+--------   65536
           ||||||||||||||+---------   32768
           |||||||||||||+----------   16384
           ||||||||||||+-----------    8192
           |||||||||||+------------    4096
           ||||||||||+-------------    2048
           |||||||||+--------------    1024
           ||||||||+---------------     512
           |||||||+----------------     256
           ||||||+-----------------     128
           |||||+------------------      64
           ||||+-------------------      32
           |||+--------------------      16
           ||+---------------------       8
           |+----------------------       4
           +-----------------------       2

Знак положительный, это довольно просто.

Показатель степени 64+32+16+8+2+1 = 123 - 127 bias = -4, поэтому множитель 2 -4 или 1/16.Смещение есть, так что вы можете получить действительно маленькие числа (например, 10 -30 ), а также большие.

Мантисса короткая.Он состоит из 1 (неявная база) плюс (для всех этих битов, каждый из которых стоит 1 / (2 n ), так как n начинается с 1 и увеличивается вправо), {1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}.

Когда вы сложите все это, вы получите 1.60000002384185791015625.

Когда вы умножите это на множитель 2 -4 , вы получите 0.100000001490116119384765625Вот почему они говорят, что вы не можете представлять 0.1 точно в виде числа с плавающей запятой IEEE754.

С точки зрения преобразования целых чисел в числа с плавающей запятой, если в мантиссе столько битов (включаянеявный 1), вы можете просто передать целочисленную битовую комбинацию и выбрать правильный показатель степени.Там не будет потеря точности.Например, IEEE754 двойной точности (64 бита, 52/53 из которых являются мантиссами) без проблем принимает 32-битное целое число.

Если в вашем целом числе больше битов (например, 32-битное)целое число и 32-разрядное число с плавающей запятой одинарной точности, которое имеет только 23/24 бита мантиссы), тогда вам нужно масштабировать целое число.

Это включает в себя удаление наименьших значащих бит (фактически округление), чтобывписаться в биты мантиссы.Это, конечно, подразумевает потерю точности, но это неизбежно.


Давайте посмотрим на конкретное значение, 123456789.Следующая программа выводит биты каждого типа данных.

#include <stdio.h>

static void dumpBits (char *desc, unsigned char *addr, size_t sz) {
    unsigned char mask;
    printf ("%s:\n  ", desc);
    while (sz-- != 0) {
        putchar (' ');
        for (mask = 0x80; mask > 0; mask >>= 1, addr++)
            if (((addr[sz]) & mask) == 0)
                putchar ('0');
            else
                putchar ('1');
    }
    putchar ('\n');
}

int main (void) {
    int intNum = 123456789;
    float fltNum = intNum;
    double dblNum = intNum;

    printf ("%d %f %f\n",intNum, fltNum, dblNum);
    dumpBits ("Integer", (unsigned char *)(&intNum), sizeof (int));
    dumpBits ("Float", (unsigned char *)(&fltNum), sizeof (float));
    dumpBits ("Double", (unsigned char *)(&dblNum), sizeof (double));

    return 0;
}

Вывод в моей системе выглядит следующим образом:

123456789 123456792.000000 123456789.000000
integer:
   00000111 01011011 11001101 00010101
float:
   01001100 11101011 01111001 10100011
double:
   01000001 10011101 01101111 00110100 01010100 00000000 00000000 00000000

И мы рассмотрим их по одному за раз.Сначала целое число, простые степени двух:

   00000111 01011011 11001101 00010101
        |||  | || || ||  || |    | | +->          1
        |||  | || || ||  || |    | +--->          4
        |||  | || || ||  || |    +----->         16
        |||  | || || ||  || +---------->        256
        |||  | || || ||  |+------------>       1024
        |||  | || || ||  +------------->       2048
        |||  | || || |+---------------->      16384
        |||  | || || +----------------->      32768
        |||  | || |+------------------->      65536
        |||  | || +-------------------->     131072
        |||  | |+---------------------->     524288
        |||  | +----------------------->    1048576
        |||  +------------------------->    4194304
        ||+---------------------------->   16777216
        |+----------------------------->   33554432
        +------------------------------>   67108864
                                         ==========
                                          123456789

Теперь давайте посмотрим на число с плавающей запятой одинарной точности.Обратите внимание на битовую комбинацию мантиссы, совпадающую с целым числом, как почти идеальное совпадение:

mantissa:       11 01011011 11001101 00011    (spaced out).
integer:  00000111 01011011 11001101 00010101 (untouched).

Слева от мантиссы есть бит неявный 1, и он также округленна другом конце, откуда и происходит эта потеря точности (значение меняется с 123456789 на 123456792, как в выходных данных этой программы выше).

Обработка значений:

s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm    1/n
0 10011001 11010110111100110100011
           || | || ||||  || |   |+- 8388608
           || | || ||||  || |   +-- 4194304
           || | || ||||  || +------  262144
           || | || ||||  |+--------   65536
           || | || ||||  +---------   32768
           || | || |||+------------    4096
           || | || ||+-------------    2048
           || | || |+--------------    1024
           || | || +---------------     512
           || | |+-----------------     128
           || | +------------------      64
           || +--------------------      16
           |+----------------------       4
           +-----------------------       2

Знак положительный.Показатель степени равен 128+16+8+1 = 153 - 127 bias = 26, поэтому множитель равен 2 26 или 67108864.

Мантисса равна 1 (неявное основание) плюс (как описано выше), {1/2, 1/4, 1/16, 1/64, 1/128, 1/512, 1/1024, 1/2048, 1/4096, 1/32768, 1/65536, 1/262144, 1/4194304, 1/8388608}.Когда вы складываете все это, вы получаете 1.83964955806732177734375.

Когда вы умножаете это на множитель 2 26 , вы получаете 123456792, то же самое, что и вывод программы.

Вывод двойной битовой маски:

s eeeeeeeeeee mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
0 10000011001 1101011011110011010001010100000000000000000000000000

Я не собираюсь пройти через процесс определения значения этого зверя :-) Однако, я будет показывать мантиссу рядом с целочисленным форматом, чтобы показать общее представление битов:

mantissa:       11 01011011 11001101 00010101 000...000 (spaced out).
integer:  00000111 01011011 11001101 00010101           (untouched).

Вы можете снова увидеть сходство с неявным битом слева и значительно большую доступность битов наПраво, поэтому в этом случае нет потери точности.


С точки зрения преобразования между числами с плавающей запятой и двойными, это также достаточно легко понять.

Сначала вы должныпроверьте специальные значения, такие как NaN и бесконечности.Они обозначены специальными комбинациями экспонента / мантисса, и, вероятно, их легче обнаружить, создав прямой эквивалент в новом формате.

Тогда в случае, когда вы переходите от двойного к плавающему, у вас, очевидно, меньше доступного диапазона, поскольку в показателе степени меньше битов.Если ваш двойник находится за пределами диапазона с плавающей точкой, вам нужно с этим справиться.

Предполагая, что он подойдет, вам необходимо:

  • перебазировать показатель степени (смещение отличаетсядля этих двух типов).
  • скопируйте столько битов из мантиссы, сколько потребуется (округление, если необходимо).
  • добавление остальной части целевой мантиссы (если есть) с нулевыми битами.
1 голос
/ 02 ноября 2011

Концептуально это довольно просто.A float (в IEEE 754-1985) имеет следующее представление:

  • 1-битный знак
  • 8-битный показатель (0 означает денормализованные числа, 1 означает -126, 127 означает0, 255 означает бесконечность)
  • 23-битная мантисса (часть, которая следует за «1»)

В общем, это примерно:

  • определитьзнак и величина числа
  • находят 24 наиболее значимых и правильных округленных бита
  • корректируют показатель степени
  • кодируют эти три части в 32-битную форму

При реализации собственного преобразования его легко протестировать, поскольку вы можете просто сравнить результаты со встроенным оператором преобразования типов.

...