C присваивание числа с плавающей запятой из литерала

Читая часть книги о 3D-графике, я пришел по следующему заданию:

const float vertexPositions[] = {
0.75f, 0.75f, 0.0f, 1.0f,
0.75f, -0.75f, 0.0f, 1.0f,
-0.75f, -0.75f, 0.0f, 1.0f,
};

Зачем нужен суффикс f? Можно ли определить тип литералов по типу переменной? Я считаю, что без f литералы с плавающей запятой интерпретируются как удвоения, но почему, когда массив явно имеет тип float?


person leeman    schedule 08.08.2014    source источник
comment
Я считаю, что это из-за того, как компилируется код, компилятор знает о литералах до того, как узнает тип массива.   -  person Drew McGowen    schedule 08.08.2014
comment
Используя GCC и без f, я не получаю никаких ошибок.   -  person TRKemp    schedule 08.08.2014
comment
Тем не менее, рекомендуется добавить f.   -  person Igor    schedule 08.08.2014
comment
на некоторых компиляторах ASSERT(1.1 != 1.1f);   -  person franji1    schedule 08.08.2014
comment
@ franji1 Это есть в большинстве компиляторов. Вам нужно только float для IEEE 754 binary32 и double для binary64, так как они почти везде. blog.frama-c.com/ index.php?post/2011/11/08/Опрос с плавающей запятой   -  person Pascal Cuoq    schedule 08.08.2014


Ответы (4)


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

Короче говоря, автор только говорит явно, что в случае массива float хорошо.

Для произвольных значений, записанных в десятичном формате, вы не хотите инициализировать float переменных double константами из-за округление» (в котором слово «двойной» означает дважды и не относится к одноименному типу). Если вы инициализируете f1 и f2 следующим образом, они получат разные значения, и f1 на самом деле ближе всего к предполагаемому значению 1,01161128282547:

  float f1 = 1.01161128282547f;
  float f2 = 1.01161128282547;

Объяснение состоит в том, что в случае f2 десятичное значение 1,01161128282547 сначала округляется до ближайшего double, а затем до ближайшего float. Эти два шага вносят больше ошибок, чем прямое округление до ближайшего float, как инициализируется f1.

person Pascal Cuoq    schedule 08.08.2014

  1. Некоторые терминологические придирки.

    • This is not an assignment, it is an initialization.
    • То, что у вас есть между {} (например, 1.0f), не является литералом, это константы. В C есть только строковые литералы и составные литералы, и все. В C 1.0 является константой с плавающей запятой.
  2. В языке C тип правой части (присваивания или инициализации) никогда не зависит от типа левой части. Тип значения в правой части всегда определяется независимо. Оно определяется исключительно тем, как указано это значение. Левая сторона никогда не принимается во внимание. Затем, когда тип правого размера известен, исходное значение преобразуется в тип левого. Другими словами, константа 1.0 всегда является константой типа double, даже если вы используете ее для инициализации объекта float.

  3. Нет ничего неправильного в использовании константы double (например, 1.0) для инициализации объекта типа float. Как я сказал выше, значение double будет преобразовано в тип float. Однако некоторые компиляторы выдают предупреждение об этом преобразовании, уведомляя пользователя о возможной потере точности. В частности, чтобы подавить это предупреждение, люди использовали бы явный суффикс с плавающей запятой (например, 1.0f) или явное приведение к float (например, (float) 1.0) при инициализации объектов float. Это выглядит не очень хорошо, так как противоречит правилу DRY (Don't-Repeat-Yourself), но в реальной жизни такой способ работы с предупреждениями встречается довольно часто.

person AnT    schedule 08.08.2014
comment
Является ли инициализация не просто типом присваивания? Я сказал назначение в своем вопросе, потому что я думал, что это относится ко всем назначениям, а не только к инициализации. - person leeman; 08.08.2014
comment
Спасибо за указание на то, что стандарт C не использует слово literal для констант с плавающей запятой. - person Pascal Cuoq; 08.08.2014

Хотя вы можете не получить никаких ошибок из-за пропуска f, это хорошая практика, и есть несколько причин, почему, например:

float num = 0.1;
(num == 0.1)

Оценка даст 0, потому что num округляется до 0.1 как число с плавающей запятой, а 0.1 в выражении удваивается. Так что на самом деле они не одинаковы, хотя могут показаться.

person Igor    schedule 08.08.2014
comment
обратите внимание, что в приведенной ниже оценке вы получаете повышение в два раза для num, потому что вы сравниваете с двойным литералом - person pqnet; 08.08.2014
comment
@pqnet Неправда. Попробуйте, прежде чем комментировать. - person Igor; 08.08.2014
comment
@Igor Я могу подтвердить, что num повысили до double в (num == 0.1). Кроме того, выражение (num == 0.1f) будет оцениваться как 1, как и ожидалось. Проблема в float num = 0.1; (num == 0.1) полностью связана со сравнением, и float num = 0.1f; (num == 0.1) даст точно такой же результат. - person Pascal Cuoq; 08.08.2014
comment
Это производит ноль, я клянусь. @PascalCuoq - person Igor; 08.08.2014
comment
@Игорь Конечно. Все, кто участвует в обсуждении здесь, знают, что (num == 0.1) производит 0. Ни pqnet, ни я не спорим об этом. - person Pascal Cuoq; 08.08.2014
comment
Мы поняли, мы программисты, мы этим зарабатываем на жизнь. @PascalCuoq - person Igor; 08.08.2014
comment
@Igor Игорь, я бы не знал, как это попробовать, но мне кажется, что вам нужно сравнивать либо два числа с плавающей запятой, либо два числа с двойной точностью, потому что нет смысла сравнивать их такими, какие они есть. Если бы было верно обратное (то есть 0.0 преобразуется в число с плавающей запятой, а сравнение выполняется в число с плавающей запятой), вы действительно получите 1 в результате сравнения, поэтому я думаю, что вы нас неправильно поняли, мы на самом деле говорим одно и то же. - person pqnet; 08.08.2014
comment
Да, я неправильно понял. Мы поняли, мы программисты, мы этим зарабатываем на жизнь. Если бы не было ошибок, мы бы остались без работы. @pqnet - person Igor; 08.08.2014

Нет, тип литерала никогда не выводится. Без f они являются double литералами, а инструкция представляет собой преобразование, которое может потерять точность (не совсем в этом случае, потому что все литералы также точно представлены с плавающей запятой).

То же предупреждение, которое вы получите, если сделаете

float x = 3.0;
person pqnet    schedule 08.08.2014
comment
В моем компиляторе (gcc) float x = 3.0 не выдает ошибок - person leeman; 08.08.2014
comment
Преобразование, происходящее в float x = 3.0;, называется конверсией. cast — это явная синтаксическая конструкция, которая вызывает преобразование. В float x = (float) 3.0; есть приведение, а в float x = 3.0; нет (есть конверсия). - person Pascal Cuoq; 08.08.2014
comment
это не ошибка, но какой-нибудь компилятор предупредит вас, что это неявное приведение, которое может потерять точность - person pqnet; 08.08.2014
comment
@PascalCuoq хорошо, наверное, я использовал неправильное слово. Я много слышал о термине неявного приведения, но я думаю, что он всегда использовался неправильно. - person pqnet; 08.08.2014
comment
Неявного приведения не существует. Кстати, в этом случае литералы представлены точно, если FLT_RADIX является степенью 2 или 10, что почти наверняка так и есть (на самом деле FLT_RADIX != 2 встречается редко). - person Keith Thompson; 08.08.2014
comment
@KeithThompson Я признаю, что это была моя ошибка при написании неявного приведения. Я знаю, что 3.0 имеет точное представление в вещественном числе (на самом деле это верно для любых FLT_RADIX % 2 = 0 или FLT_RADIX % 3 = 0, а не только для степени двойки или десятки), но я не это имел в виду. Моя точка зрения заключалась в том, что существует неявное преобразование из одного типа в другой, поскольку тип для 3.0 является 64-битным числом с плавающей запятой, а тип для x — 32-битным числом с плавающей запятой, и это вызвало бы предупреждение от многих компиляторов. - person pqnet; 08.08.2014
comment
@pqnet: тип 3.0 равен double (часто 64-битный, но это не гарантируется). Тип 3.0ffloat (часто это 32 бита, но это также не гарантируется). double и float (а также long double) являются типами с плавающей запятой. - person Keith Thompson; 08.08.2014
comment
если вы хотите только троллить, придираясь к именам, продолжайте, вы не добавляете информацию. Я вне этого разговора - person pqnet; 08.08.2014