2014-09-06 3 views
0

Это общий вопрос о том, что именно происходит, когда я накладываю очень большое/маленькое значение SIGNED в плавающую точку с использованием gcc 4.4.Что произойдет, если вы нажмете большой int на float

Я вижу какое-то странное поведение при выполнении заклинания. Вот некоторые примеры:

MUSTBE будет получено с помощью этого метода:

float f = (float)x; 
unsigned int r; 
memcpy(&r, &f, sizeof(unsigned int)); 

./btest -f float_i2f -1 0x80800001 
input:   10000000100000000000000000000001 
absolute value: 01111111011111111111111111111111 

exponent:  10011101 
mantissa:  00000000011111101111111111111111 (right shifted absolute value) 

EXPECT:   11001110111111101111111111111111 (sign|exponent|mantissa) 
MUST BE:  11001110111111110000000000000000 (sign ok, exponent ok, 
                mantissa???) 

./btest -f float_i2f -1 0x3f7fffe0 

EXPECT: 01001110011111011111111111111111 
MUST BE: 01001110011111100000000000000000 

./btest -f float_i2f -1 0x80004999                 


EXPECT: 11001110111111111111111101101100 
MUST BE: 11001110111111111111111101101101 (<- 1 added at the end) 

Так что меня беспокоит, что мантисса в обоих примерах различных затем, если я просто переместить мое целое значение вправо. Например, нули в конце. Откуда они?

Я вижу это поведение только для больших/малых значений. Значения в диапазоне -2^24, 2^24 работают нормально.

Интересно, может ли кто-нибудь просветить меня, что здесь происходит. Каковы шаги, которые также принимают очень большие/маленькие значения.

Это вопрос о добавлении к вопросу: function to convert float to int (huge integers), который не является таким общим, как этот здесь.

EDIT Код:

unsigned float_i2f(int x) { 
    if (x == 0) return 0; 
    /* get sign of x */ 
    int sign = (x>>31) & 0x1; 

    /* absolute value of x */ 
    int a = sign ? ~x + 1 : x; 

    /* calculate exponent */ 
    int e = 158; 
    int t = a; 
    while (!(t >> 31) & 0x1) { 
    t <<= 1; 
    e--; 
    }; 

    /* calculate mantissa */ 
    int m = (t >> 8) & ~(((0x1 << 31) >> 8 << 1)); 
    m &= 0x7fffff; 

    int res = sign << 31; 
    res |= (e << 23); 
    res |= m; 

    return res; 
} 

EDIT 2:

После Адамса замечания и ссылки на книги Напишите Великий код, я обновил свою рутину с округлением. Тем не менее, я получаю некоторые ошибки округления (теперь, к счастью, только 1 бит).

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

input: 0xfefffff5 
result: 11001011100000000000000000000101 
GOAL: 11001011100000000000000000000110 (1 too low) 

input: 0x7fffff 
result: 01001010111111111111111111111111 
GOAL: 01001010111111111111111111111110 (1 too high) 

unsigned float_i2f(int x) { 
    if (x == 0) return 0; 
    /* get sign of x */ 
    int sign = (x>>31) & 0x1; 

    /* absolute value of x */ 
    int a = sign ? ~x + 1 : x; 

    /* calculate exponent */ 
    int e = 158; 
    int t = a; 
    while (!(t >> 31) & 0x1) { 
    t <<= 1; 
    e--; 
    }; 

    /* mask to check which bits get shifted out when rounding */ 
    static unsigned masks[24] = { 
    0, 1, 3, 7, 
    0xf, 0x1f, 
    0x3f, 0x7f, 
    0xff, 0x1ff, 
    0x3ff, 0x7ff, 
    0xfff, 0x1fff, 
    0x3fff, 0x7fff, 
    0xffff, 0x1ffff, 
    0x3ffff, 0x7ffff, 
    0xfffff, 0x1fffff, 
    0x3fffff, 0x7fffff 
    }; 

    /* mask to check wether round up, or down */ 
    static unsigned HOmasks[24] = { 
    0, 
    1, 2, 4, 0x8, 0x10, 0x20, 0x40, 0x80, 
    0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000 
    }; 

    int S = a & masks[8]; 
    int m = (t >> 8) & ~(((0x1 << 31) >> 8 << 1)); 
    m &= 0x7fffff; 

    if (S > HOmasks[8]) { 
    /* round up */ 
    m += 1; 
    } else if (S == HOmasks[8]) { 
    /* round down */ 
    m = m + (m & 1); 
    } 

    /* special case where last bit of exponent is also set in mantissa 
    * and mantissa itself is 0 */ 
    if (m & (0x1 << 23)) { 
    e += 1; 
    m = 0; 
    } 

    int res = sign << 31; 
    res |= (e << 23); 
    res |= m; 
    return res; 
} 

ли кто-то есть идеи, где проблема лежит?

+0

Я не очень понимаю, что вы просите, но 32-бит с плавающей точкой имеет меньшую точность, чем 32-битный int, поэтому вы получите некоторые неточности. – interjay

+0

Я добавил еще 2 значения к моим примерам. –

+0

Вы должны подумать о том, чтобы показать свой код. Кроме того, что вы получаете против того, что вы ожидаете от небольших значений, таких как 0, 1, 2, 4 и т. Д.? –

ответ

1

C/C++ поплавки, как правило, совместимы с плавающей точкой стандарта IEEE 754 (например, в GCC). Нули идут от rounding rules.

Смещение числа вправо делает несколько бит с правой стороны. Назовем их guard bits. Теперь давайте назовем HO самым высоким битом и LO самым низким номером нашего номера. Теперь предположим, что guard bits все еще являются частью нашего номера. Если, например, у нас есть 3 guard bits, это означает, что значение нашего LO бит равно 8 (если оно установлено). Теперь, если:

  1. значение guard bits> 0,5 * значение LO

    округляет число до Смоллинг возможного большего значения, не обращая внимания на знаке

  2. значение == 'Гвардия' BITS 0.5 * значение LO

    • использование текущее значение числа, если LO == 0
    • номер + = 1 в противном случае
  3. значение guard bits < 0.5 * значение LO

    • использования текущего значения числа

почему 3 защитные биты означают значение LO 8?

Предположим, что мы имеем бинарное 8 битное число:

weights: 128 64 32 16 8 4 2 1 
binary num: 0 0 0 0 1 1 1 1 

Давайте переместить его вправо на 3 бита:

weights:  x x x 128 64 32 16 8 | 4 2 1 
binary num: 0 0 0 0 0 0 0 1 | 1 1 1 

Как вы видите, с 3 охранником битами LO бит заканчивается на 4-й позиции и имеет вес 8. Это верно только для целей округления. После этого весы должны быть «нормализованы», так что вес LO бит снова станет 1.

И как я могу проверить с помощью битовых операций, если защитные биты> 0,5 * значение?

Самый быстрый способ - использовать таблицы поиска. Предположим, что мы работаем на 8-битовое число:

unsigned number;   //our number 
unsigned bitsToShift;  //number of bits to shift 

assert(bitsToShift < 8); //8 bits 

unsigned guardMasks[8] = {0, 1, 3, 7, 0xf, 0x1f, 0x3f} 
unsigned LOvalues[8] = {0, 1, 2, 4, 0x8, 0x10, 0x20, 0x40} //divided by 2 for faster comparison 

unsigned guardBits = number & guardMasks[bitsToShift]; //value of the guard bits 
number = number >> bitsToShift; 

if(guardBits > LOvalues[bitsToShift]) { 
... 
} else if (guardBits == LOvalues[bitsToShift]) { 
... 
} else { //guardBits < LOvalues[bitsToShift] 
... 
} 

Ссылка: Написать Великий кодекс, Том 1 Рэндалл Хайда

+0

Можете ли вы, возможно, разработать больше, как это округление работает? –

+1

«Поплавки C/C++ совместимы с стандартом IEEE 754 с плавающей запятой» - или, может быть, нет. В стандарте (-ях) не обязательно, чтобы они были такими. –

+0

Вы правы. Но они, как я знаю, имеют тенденцию быть. –

3

32-разрядный float использует некоторые из битов для экспоненты и поэтому не может точно представлять все 32-битные целочисленные значения.

64-разрядный double может точно хранить любое 32-разрядное целочисленное значение.

Wikipedia имеет сокращенную запись на IEEE 754 с плавающей запятой и множество деталей внутренних чисел чисел с плавающей запятой по адресу IEEE 754-1985 - текущий стандарт IEEE 754: 2008. Он отмечает, что 32-битный float использует один бит для знака, 8 бит для экспоненты, оставляя 23 явных и 1 неявный бит для мантиссы, поэтому абсолютные значения до 2 могут быть представлены точно.


Я думал, что это было ясно, что 32-битное целое, не может быть в точности сохранить в 32-битный поплавок. Мой вопрос: что произойдет, если я сохраню целое число больше 2^24 или меньше -2^24? И как я могу его воспроизвести?

После того, как абсолютные значения больше, чем 2 , целочисленные значения не могут быть точно представлены в 24 эффективных цифр мантиссы 32-битной float, так что только ведущие 24 цифры надежно доступны. Всплывает также плавающая точка.

Вы можете продемонстрировать с кодом похожее на это: #include #include

typedef union Ufloat 
{ 
    uint32_t i; 
    float  f; 
} Ufloat; 

static void dump_value(uint32_t i, uint32_t v) 
{ 
    Ufloat u = { .i = v }; 
    printf("0x%.8" PRIX32 ": 0x%.8" PRIX32 " = %15.7e = %15.6A\n", i, v, u.f, u.f); 
} 

int main(void) 
{ 
    uint32_t lo = 1 << 23; 
    uint32_t hi = 1 << 28; 
    Ufloat u; 

    for (uint32_t v = lo; v < hi; v <<= 1) 
    { 
     u.f = v; 
     dump_value(v, u.i); 
    } 

    lo = (1 << 24) - 16; 
    hi = lo + 64; 

    for (uint32_t v = lo; v < hi; v++) 
    { 
     u.f = v; 
     dump_value(v, u.i); 
    } 

    return 0; 
} 

Пример вывода:

0x00800000: 0x4B000000 = 8.3886080e+06 = 0X1.000000P+23 
0x01000000: 0x4B800000 = 1.6777216e+07 = 0X1.000000P+24 
0x02000000: 0x4C000000 = 3.3554432e+07 = 0X1.000000P+25 
0x04000000: 0x4C800000 = 6.7108864e+07 = 0X1.000000P+26 
0x08000000: 0x4D000000 = 1.3421773e+08 = 0X1.000000P+27 
0x00FFFFF0: 0x4B7FFFF0 = 1.6777200e+07 = 0X1.FFFFE0P+23 
0x00FFFFF1: 0x4B7FFFF1 = 1.6777201e+07 = 0X1.FFFFE2P+23 
0x00FFFFF2: 0x4B7FFFF2 = 1.6777202e+07 = 0X1.FFFFE4P+23 
0x00FFFFF3: 0x4B7FFFF3 = 1.6777203e+07 = 0X1.FFFFE6P+23 
0x00FFFFF4: 0x4B7FFFF4 = 1.6777204e+07 = 0X1.FFFFE8P+23 
0x00FFFFF5: 0x4B7FFFF5 = 1.6777205e+07 = 0X1.FFFFEAP+23 
0x00FFFFF6: 0x4B7FFFF6 = 1.6777206e+07 = 0X1.FFFFECP+23 
0x00FFFFF7: 0x4B7FFFF7 = 1.6777207e+07 = 0X1.FFFFEEP+23 
0x00FFFFF8: 0x4B7FFFF8 = 1.6777208e+07 = 0X1.FFFFF0P+23 
0x00FFFFF9: 0x4B7FFFF9 = 1.6777209e+07 = 0X1.FFFFF2P+23 
0x00FFFFFA: 0x4B7FFFFA = 1.6777210e+07 = 0X1.FFFFF4P+23 
0x00FFFFFB: 0x4B7FFFFB = 1.6777211e+07 = 0X1.FFFFF6P+23 
0x00FFFFFC: 0x4B7FFFFC = 1.6777212e+07 = 0X1.FFFFF8P+23 
0x00FFFFFD: 0x4B7FFFFD = 1.6777213e+07 = 0X1.FFFFFAP+23 
0x00FFFFFE: 0x4B7FFFFE = 1.6777214e+07 = 0X1.FFFFFCP+23 
0x00FFFFFF: 0x4B7FFFFF = 1.6777215e+07 = 0X1.FFFFFEP+23 
0x01000000: 0x4B800000 = 1.6777216e+07 = 0X1.000000P+24 
0x01000001: 0x4B800000 = 1.6777216e+07 = 0X1.000000P+24 
0x01000002: 0x4B800001 = 1.6777218e+07 = 0X1.000002P+24 
0x01000003: 0x4B800002 = 1.6777220e+07 = 0X1.000004P+24 
0x01000004: 0x4B800002 = 1.6777220e+07 = 0X1.000004P+24 
0x01000005: 0x4B800002 = 1.6777220e+07 = 0X1.000004P+24 
0x01000006: 0x4B800003 = 1.6777222e+07 = 0X1.000006P+24 
0x01000007: 0x4B800004 = 1.6777224e+07 = 0X1.000008P+24 
0x01000008: 0x4B800004 = 1.6777224e+07 = 0X1.000008P+24 
0x01000009: 0x4B800004 = 1.6777224e+07 = 0X1.000008P+24 
0x0100000A: 0x4B800005 = 1.6777226e+07 = 0X1.00000AP+24 
0x0100000B: 0x4B800006 = 1.6777228e+07 = 0X1.00000CP+24 
0x0100000C: 0x4B800006 = 1.6777228e+07 = 0X1.00000CP+24 
0x0100000D: 0x4B800006 = 1.6777228e+07 = 0X1.00000CP+24 
0x0100000E: 0x4B800007 = 1.6777230e+07 = 0X1.00000EP+24 
0x0100000F: 0x4B800008 = 1.6777232e+07 = 0X1.000010P+24 
0x01000010: 0x4B800008 = 1.6777232e+07 = 0X1.000010P+24 
0x01000011: 0x4B800008 = 1.6777232e+07 = 0X1.000010P+24 
0x01000012: 0x4B800009 = 1.6777234e+07 = 0X1.000012P+24 
0x01000013: 0x4B80000A = 1.6777236e+07 = 0X1.000014P+24 
0x01000014: 0x4B80000A = 1.6777236e+07 = 0X1.000014P+24 
0x01000015: 0x4B80000A = 1.6777236e+07 = 0X1.000014P+24 
0x01000016: 0x4B80000B = 1.6777238e+07 = 0X1.000016P+24 
0x01000017: 0x4B80000C = 1.6777240e+07 = 0X1.000018P+24 
0x01000018: 0x4B80000C = 1.6777240e+07 = 0X1.000018P+24 
0x01000019: 0x4B80000C = 1.6777240e+07 = 0X1.000018P+24 
0x0100001A: 0x4B80000D = 1.6777242e+07 = 0X1.00001AP+24 
0x0100001B: 0x4B80000E = 1.6777244e+07 = 0X1.00001CP+24 
0x0100001C: 0x4B80000E = 1.6777244e+07 = 0X1.00001CP+24 
0x0100001D: 0x4B80000E = 1.6777244e+07 = 0X1.00001CP+24 
0x0100001E: 0x4B80000F = 1.6777246e+07 = 0X1.00001EP+24 
0x0100001F: 0x4B800010 = 1.6777248e+07 = 0X1.000020P+24 
0x01000020: 0x4B800010 = 1.6777248e+07 = 0X1.000020P+24 
0x01000021: 0x4B800010 = 1.6777248e+07 = 0X1.000020P+24 
0x01000022: 0x4B800011 = 1.6777250e+07 = 0X1.000022P+24 
0x01000023: 0x4B800012 = 1.6777252e+07 = 0X1.000024P+24 
0x01000024: 0x4B800012 = 1.6777252e+07 = 0X1.000024P+24 
0x01000025: 0x4B800012 = 1.6777252e+07 = 0X1.000024P+24 
0x01000026: 0x4B800013 = 1.6777254e+07 = 0X1.000026P+24 
0x01000027: 0x4B800014 = 1.6777256e+07 = 0X1.000028P+24 
0x01000028: 0x4B800014 = 1.6777256e+07 = 0X1.000028P+24 
0x01000029: 0x4B800014 = 1.6777256e+07 = 0X1.000028P+24 
0x0100002A: 0x4B800015 = 1.6777258e+07 = 0X1.00002AP+24 
0x0100002B: 0x4B800016 = 1.6777260e+07 = 0X1.00002CP+24 
0x0100002C: 0x4B800016 = 1.6777260e+07 = 0X1.00002CP+24 
0x0100002D: 0x4B800016 = 1.6777260e+07 = 0X1.00002CP+24 
0x0100002E: 0x4B800017 = 1.6777262e+07 = 0X1.00002EP+24 
0x0100002F: 0x4B800018 = 1.6777264e+07 = 0X1.000030P+24 

Первая часть вывода показывает, что некоторые целые значения по-прежнему может быть хранится точно; в частности, полномочия 2 могут храниться точно. На самом деле, точнее (но менее сжато), любое целое число, где двоичное представление абсолютного значения имеет не более 24 значащих цифр (любые конечные цифры - нули), может быть точно представлено. Значения не обязательно должны быть напечатаны точно, но это отдельная проблема от их хранения точно.

Вторая (большая) часть вывода показывает, что до 2 -1 целые значения могут быть представлены точно. Значение 2 само по себе также точно представимо, но 2 +1 нет, поэтому он отображается так же, как 2 . В противоположность этому, 2 +2 может быть представлен только 24 двоичными цифрами, за которыми следует 1 ноль, и, следовательно, может быть представлен точно. Повторите ad nauseam для приращений больше 2. Похоже, что действует режим «круглой четности»; поэтому результаты показывают 1 значение, а затем 3 значения.

(Заметаю мимоходом, что не существует способ предусматривает, что double передается printf() - конвертируется из float по правилам по умолчанию аргумента поощрений (ISO/IEC 9899: 2011 §6.5.2.2 вызовов функций , ¶6) печатается как float() -. модификатор h логически быть использован, но не определен)

+0

Я думал, что было ясно, что 32-битное целое не может быть точно сохранено в 32-битном плавании. Мой вопрос: что произойдет, если я сохраню целое число больше 2^24 или меньше -2^24. И как я могу его воспроизвести. –

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