Понимание приведений от целого числа к числу с плавающей точкой - PullRequest
0 голосов
/ 13 мая 2018

Может ли кто-нибудь объяснить этот странный вывод на 32-битной машине?

#include <stdio.h>

int main() {
  printf("16777217 as float is %.1f\n",(float)16777217);
  printf("16777219 as float is %.1f\n",(float)16777219);

  return 0;
}

выход

16777217 as float is 16777216.0
16777219 as float is 16777220.0

Странно то, что 16777217 приводит к более низкому значению, а 16777219 - к более высокому значению ...

Ответы [ 4 ]

0 голосов
/ 14 мая 2018

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

num*base^exp

Обычно мы используем 10 в качестве основы, потому что у нас в руках 10 пальцев, поэтому мы привыкли к числам вроде 1e2, то есть 100=1*10^2.

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

Правильным количеством цифр может быть количество, которое мы можем обработать умом, или то, что требуется для инженерного приложения.Когда мы решили, сколько цифр нам нужно, мы больше не будем заботиться о том, насколько привязанным к реальному значению будет числовое представление, которое мы собираемся обработать.Т.е. для числа, подобного 123456.789e5, подразумевается, что, сложив 99 единицу, мы можем допустить округленное представление и в любом случае считать его приемлемым, если нет, нам следует изменить представление и использовать другое с соответствующим количеством цифр, как в 12345678900.

На компьютере, когда вам приходится работать с очень большими числами, которые не укладываются в стандартное целое число, или когда вам нужно представлять действительное число (с десятичной частью), правильный выборfloating или double представление с плавающей запятой.Он использует тот же макет, который мы обсуждали выше, но основание 2 вместо 10 .Это потому, что компьютер может иметь только 2 пальца, состояния 0 или 1.Вот формула, которую мы использовали ранее, чтобы представить 100, стать:

100100*2^0

Это все еще не реальное представление с плавающей запятой, но дает идею.Теперь рассмотрим, что в компьютере формат с плавающей запятой стандартизирован и для стандартного числа с плавающей запятой, согласно IEE-754, он использует в качестве схемы памяти (мы увидим, почему для мантиссы предполагается еще 1 бит), 23 бита длямантисса, 1 бит для знака и 8 бит для показателя степени, смещенного на -127 (это просто означает, что он будет находиться в диапазоне от -126 до +127 без необходимости знакового бита, а значения 0x00 и 0xff зарезервированыдля специального значения).

Теперь рассмотрим использование 0 в качестве показателя степени, это означает, что значение 2^exponent=2^0=1, умноженное на мантиссу, дает такое же поведение целого числа в 23 бита.Это означает, что при увеличении числа, как в:

float f = 0;
while(1)
{
    f +=1;
    printf ("%f\n", f);
}

Вы увидите, что напечатанное значение линейно увеличивается на единицу, пока оно не насытит 23 бита, и показатель не станет расти.

Еслиоснование или основание нашего числа с плавающей запятой было бы равно 10, мы бы увидели увеличение каждые 10 циклов для первых 100 (10 ^ 2) значений, чем увеличение на 100 для следующих 1000 (10 ^ 3) значений искоро.Вы видите, что это соответствует * усечению **, которое мы должны сделать из-за ограниченного числа доступных цифр.

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

То, что мы обсуждали до сих пор, называется денормализованной формой с плавающей запятой, обычно используется аналог нормализованный .Последнее просто означает, что существует 24-й бит, не сохраненный, то есть всегда 1.В плоских словах мы не будем использовать показатель степени 0 для числа, меньшего 2^24, но мы сместим его (умножим на 2) до достижения MSbit==1 значения 24-го бита, чем показатель степени настроен на такое значение.отрицательное значение, которое заставляет преобразование сдвинуть число обратно к его первоначальному значению.

Помните зарезервированное значение показателя степени, о котором мы говорили выше?Ну, exponent==0x00 означает, что у нас есть денормализованное число.exponent==0xff указывает nan (не число) или +/- бесконечность, если mantissa==0.

Теперь должно быть ясно, что когда число, которое мы выражаем, выходит за пределы 24 бит значащего (мантисса), нам следует ожидать приближения реального значения в зависимости от того, как далеко мы находимся от 2^24.

Теперь номер, который вы используете, находится на грани 2^24=16,277,216:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|0|0|1|0|1|1|0|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| = 16,277,215
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 s\______ _______/\_____________________ _______________________/
 i       v                              v
 g   exponent                        mantissa
 n

Теперь увеличиваясь на 1, мы имеем:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|0|0|1|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0| = 16,277,216
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 s\__ exponent __/\_________________ mantissa __________________/

Обратите внимание, что мы включили 1 24-й бит, но с этого момента мы находимся выше 24-битного представления, и каждое возможное дальнейшее представление выполняется с шагом 2^1=2. Просто продвиньтесь на 2 или можете представить только четные числа (кратные 2^1=2). То есть установив в 1 менее значимый бит, мы имеем:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|0|0|1|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1| = 16,277,218
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 s\__ exponent __/\_________________ mantissa __________________/

Снова увеличивается:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|0|0|1|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0| = 16,277,220
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 s\__ exponent __/\_________________ mantissa __________________/

Как видите, мы не можем точно представить 16 277 219. В вашем коде:

// This will print 16777216, because 1 increment isn't enough to
// increase the significant that can express only intervals
// that are > 2^1
printf("16777217 as float is %.1f\n",(float)16777217);
// This will print 16777220, because an increment of 3 on
// the base 16777216=2^24 will trigger an exponent increase rounded
// to the closer exact representation
printf("16777219 as float is %.1f\n",(float)16777219);

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

В случае, если нам нужна большая точность, мы можем использовать double или целое число long long int.

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

Подробнее см .:

Онлайн-апплеты:

0 голосов
/ 13 мая 2018

В базовом 32-битном двоичном формате IEEE-754 с плавающей запятой все целые числа от -16 777 216 до + 16 777 216 представимы. От 16,777,216 до 33,554,432 представимы только четные целые числа. Затем с 33 554 432 до 67 108 864 представляются только кратные четыре. (Поскольку вопрос не требует обсуждения того, какие числа представимы, я опущу объяснение и просто приму это как должное.)

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

16,777,217 равноудалено между двумя представленными значениями 16,777,216 и 16,777,218. Эти значения представлены как 100000000000000000000000 2 • 2 1 и 100000000000000000000001 2 • 2 1 . Первый имеет 0 в младшем бите своего значащего, поэтому он выбран в качестве результата.

16,777,219 равноудалено между двумя представимыми значениями 16,777,218 и 16,777,220. Эти значения представлены как 100000000000000000000001 2 • 2 1 и 100000000000000000000010 2 • 2 1 . Последний имеет 0 в младшем бите своего значащего, поэтому он выбирается в качестве результата.

0 голосов
/ 13 мая 2018

Возможно, вы слышали о понятии «точность», так как «это дробное представление имеет 3 цифры точности».

Об этом очень легко думать в представлении с фиксированной точкой.Если у меня есть, скажем, три цифры точности после десятичной точки, то я могу точно представить 1/2 = 0,5, и я могу точно представить 1/4 = 0,25, и я могу точно представить 1/8 = 0,125, но если япопробуйте представить 1/16, я могу не получить 0,0625;Мне придется либо согласиться на 0,062, либо на 0,063.

Но это с фиксированной точкой.Используемый вами компьютер использует с плавающей точкой , что очень похоже на научную нотацию.Вы получаете определенное количество значащих цифр всего , а не только цифры справа от десятичной точки.Например, если у вас есть точность в 3 десятичных знака в формате с плавающей запятой, вы можете представить 0,123, но не 0,1234, и вы можете представить 0,0123 и 0,00123, но не 0,01234 или 0,001234.И если у вас есть цифры слева от десятичной точки, они убираются из числа, которое вы можете использовать справа от десятичной точки.Вы можете использовать 1.23, но не 1.234, а 12.3, но не 12.34 и 123.0, но не 123.4 или 123.anythingelse.

И - вы, вероятно, уже можете видеть шаблон - если вы используете плавающий-точечный формат только с тремя значащими цифрами, вы не можете совершенно точно представить все числа больше 999, даже если они не имеют дробной части.Вы можете представить 1230, но не 1234, а 12300, но не 12340.

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

В частности, введитеfloat на большинстве машин имеет точность в 24 двоичных разряда, что дает точность до 6-7 десятичных цифр.Этого явно недостаточно для чисел типа 16777217.

Так откуда же взялись числа 16777216 и 16777220?Как уже объяснил Эрик Постпишил, в итоге получается, что они кратны 2. Если мы посмотрим на двоичные представления соседних чисел, картина становится ясной:

16777208     111111111111111111111000
16777209     111111111111111111111001
16777210     111111111111111111111010
16777211     111111111111111111111011
16777212     111111111111111111111100
16777213     111111111111111111111101
16777214     111111111111111111111110
16777215     111111111111111111111111
16777216    1000000000000000000000000
16777218    1000000000000000000000010
16777220    1000000000000000000000100

16777215 - это наибольшее число, котороеможет быть представлен точно в 24 битах.После этого вы можете представлять только четные числа, потому что младший бит является 25-м, и, по сути, должен быть 0.

0 голосов
/ 13 мая 2018

Тип float не может иметь такого большого значения.Значение и может содержать только 24 бита.Из этих 23 хранятся, а 24-е - 1 и не сохраняется, потому что значение и нормализуется.

Пожалуйста прочитайте это , которое говорит "Целые числа в [- 16777216, 16777216] может быть точно представлено ", но ваши находятся вне этого диапазона.

...