Как int для float работает для больших чисел? - PullRequest
0 голосов
/ 07 января 2020

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

#include <stdio.h>

#define INT2FLOAT(num) printf(" %d: %.0f\n", (num), (float)(num));

int main(void)
{
    INT2FLOAT((1<<24) + 1);
    INT2FLOAT((1<<24) + 2);
    INT2FLOAT((1<<24) + 3);
    INT2FLOAT((1<<24) + 4);
    INT2FLOAT((1<<24) + 5);
    INT2FLOAT((1<<24) + 6);
    INT2FLOAT((1<<24) + 7);
    INT2FLOAT((1<<24) + 8);
    INT2FLOAT((1<<24) + 9);
    INT2FLOAT((1<<24) + 10);

    return 0;
}

Вывод:

 16777217: 16777216
 16777218: 16777218
 16777219: 16777220
 16777220: 16777220
 16777221: 16777220
 16777222: 16777222
 16777223: 16777224
 16777224: 16777224
 16777225: 16777224
 16777226: 16777226

Значения в середине между двумя представимыми целыми числами иногда округляются, иногда округлено вниз Кажется, что применяется какое-то округление к четному. Как это работает точно? Где я могу найти код, который выполняет это преобразование?

Ответы [ 2 ]

5 голосов
/ 07 января 2020

Поведение этого неявного преобразования определяется реализацией: (C11 6.3.1.4/2):

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

Это означает, что ваш компилятор должен задокументировать, как он работает, но вы не сможете управлять им.

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

1 голос
/ 09 января 2020

В дополнение к тому, что было сказано в других ответах, например, модули Intel с плавающей запятой используют внутренне полное 80-битное представление с плавающей запятой с превышением числа битов ... так, когда оно округляет число до ближайшего 23-битное float число (как я предполагаю из вашего вывода) думает, что оно может быть очень точным и учитывает все биты в int.

IEEE-752 определяет 32-битное число с плавающей точкой в ​​качестве числа с 23 битами, выделенными для хранения значения и, что означает, что для нормализованного числа, в котором неявный старший бит является неявным (не хранится, поскольку это всегда 1 бит), у вас фактически есть 24 бита значения и в форме 1xxxxxxx_xxxxxxxx_xxxxxxxx, что означает число 2^24-1 - последнее, что вы сможете точно представить (на самом деле 11111111_11111111_11111111). После этого вы можете представлять все четные числа, но не шансы, так как вам не хватает младшего значащего бита для их представления. Это должно означать, что вы можете представить:

                                                     v decimal dot.
16777210  == 2^24-6        11111111_11111111_11111010.
16777211  == 2^24-5        11111111_11111111_11111011.
16777212  == 2^24-4        11111111_11111111_11111100.
16777213  == 2^24-3        11111111_11111111_11111101.
16777214  == 2^24-2        11111111_11111111_11111110.
16777215  == 2^24-1        11111111_11111111_11111111.
16777216  == 2^24         10000000_00000000_00000000_. <-- here the leap becomes 2 as there are no more than 23 bits to play with.
16777217  == 2^24+1       10000000_00000000_00000000_. (there should be a 1 bit after the last 0)
16777218  == 2^24+2       10000000_00000000_00000001_.
...
33554430  == 2^25-2       11111111_11111111_11111111_.
33554432  == 2^26        10000000_00000000_00000000__. <-- here the leap becomes 4 as there's another shift
33554436  == 2^26+4      10000000_00000000_00000001__.
...

Если вы представляете проблему в базе 10, предположим, что у нас есть числа с плавающей запятой всего 3 десятичных знака в значении и показатель степени 10, чтобы поднять силу , Когда мы начинаем считать с 0, мы получаем:

  1  => 1.00E0
...
  8  => 8.00E0
  9  => 9.00E0
 10  => 1.00E1  <<< see what happened here... this is the same number as the first but with the ten's exponent incremented, meaning a one digit shift of every digit to the left.
 11  => 1.10E1
...
 98  => 9.80E1
 99  => 9.90E1
100  => 1.00E2  <<< and here.
101  => 1.01E2
...
996  => 9.96E2
997  => 9.97E2
998  => 9.98E2
999  => 9.99E2
1000 => 1.00E3  <<< exact, but here you don't have anymore a fourth digit to represent units.
1001 => 1.00E3  (this number cannot be represented exactly)
...
1004 => 1.00E3  (this number cannot be represented exactly)
1005 => 1.01E3  (this number cannot be represented exactly) <<< here rounding is applied, but the implementation is free to do whatever it wants.
...
1009 => 1.01E3  (this number cannot be represented exactly)
1010 => 1.01E3 <<< this is the next number that can be represent exactly with three floating point digits.  So we switched from an increment of one by one to an increment of ten by ten.
...

Примечание

Показанный вами случай - это один из режимов округления, указанных для процессоров Intel, он округляется до четное число ближе, но в случае, если оно составляет половину расстояния, оно подсчитывает число в один бит в значении и округляет в большую сторону, когда оно нечетное, и округляется в меньшую сторону, когда оно четное (это позволяет избежать округления всегда так важно иногда банковские операции --- банки никогда не используют числа с плавающей запятой, потому что они не имеют точного контроля округления)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...