Странное поведение при приведении int к плавающему в C

У меня есть сомнения относительно вывода следующей программы на Си. Я попытался скомпилировать его, используя Visual C++ 6.0 и MinGW32 (gcc 3.4.2).

#include <stdio.h>

int main() {
    int x = 2147483647;
    printf("%f\n", (float)2147483647);
    printf("%f\n", (float)x);
    return 0;
}

Выход:

2147483648.000000
2147483647.000000

Мой вопрос: почему обе линии разные? При преобразовании целочисленного значения 2147483647 в формат IEEE 754 с плавающей запятой оно приближается к 2147483648.0. Итак, я ожидал, что обе строки будут равны 2147483648.000000.

РЕДАКТИРОВАТЬ: значение "2147483647.000000" не может быть значением с плавающей запятой одинарной точности, поскольку число 2147483647 не может быть точно представлено в формате с плавающей запятой IEEE 754 без потери точности.

5 ответов

Решение

В обоих случаях код стремится преобразовать некоторый целочисленный тип в float а затем double.. double преобразование происходит как это float значение передается в функцию с переменным числом.

Проверьте настройки FLT_EVAL_METHODподозреваю, что оно имеет значение 1 или 2 (отчеты OP 2 по крайней мере с одним компилятором). Это позволяет компилятору оценить float "... операции и константы дальности и точности" больше, чем float,

Ваш компилятор оптимизирован (float)x идти прямо int в double арифметика. Это улучшение производительности во время выполнения.

(float)2147483647 приведение времени компиляции и компилятор оптимизирован для int в float в double Точность как производительность не является проблемой здесь.


[Edit2] Интересно, что спецификация C11 более специфична, чем спецификация C99 с добавлением "За исключением назначения и приведения...". Это означает, что компиляторы C99 иногда позволяли int в double прямое преобразование без предварительного прохождения float и что C11 был изменен, чтобы явно не допустить пропуска актеров.

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


[Редактировать] Принимая комментарии вместе @Keith Томпсон, @tmyklebu, @Matt McNabb, компилятор, даже с ненулевым FLT_EVAL_METHOD, следует ожидать, чтобы произвести 2147483648.0..., Таким образом, либо флаг оптимизации компилятора явно переопределяет правильное поведение, либо компилятор имеет угловую ошибку.


C99dr §5.2.4.2.2 8 Значения операций с плавающими операндами и значениями, подлежащими обычному арифметическому преобразованию и плавающими константами, оцениваются в формате, диапазон и точность которого могут быть больше, чем требуется типом. Использование форматов оценки характеризуется значением реализации FLT_EVAL_METHOD:

-1 неопределимо;

0 оценивать все операции и константы только для диапазона и точности типа;

1 оценить операции и константы типа float а также double в диапазоне и точности double тип, оценить long double операции и константы в диапазоне и точности long double type`;

2 оценить все операции и константы для диапазона и точности long double тип.


C11dr §5.2.4.2.2 9 За исключением присваивания и приведения (которые удаляют весь дополнительный диапазон и точность), значения, полученные операторами с плавающими операндами, и значениями, подлежащими обычному арифметическому преобразованию и плавающим константам, оцениваются в формате, диапазон которого и точность может быть больше, чем требуется типом. Использование форматов оценки характеризуется значением, определенным реализацией FLT_EVAL_METHOD

-1 (такой же как C99)

0 (так же, как C99)

1 (такой же, как C99)

2 (так же, как C99)

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

  • Типы имеют набор представимых значений (подразумевается)
  • Все значения представлены float также могут быть представлены double (6.2.5/10)
  • преобразование float в double не меняет значение (6.3.1.5/1)
  • Кастинг int в floatкогда значение int находится в наборе представимых значений для float, дает это значение.
  • Кастинг int в float, когда величина значения int меньше, чем FLT_MAX и int не представимое значение для float, вызывает либо следующий самый высокий или следующий самый низкий float значение, которое будет выбрано, и то, которое выбрано, определяется реализацией. (6.3.1.4/2)

Третий из этих пунктов гарантирует, что float значение предоставляется printf не изменяется при продвижении аргумента по умолчанию.

Если 2147483647 представима в float, затем (float)x а также (float)2147483647 должен дать 2147483647.000000,

Если 2147483647 не представлен в float, затем (float)x а также (float)2147483647 должен либо дать следующий самый высокий или следующий самый низкий float, Им не нужно делать одинаковый выбор. Но это означает, что распечатка 2147483647.000000 не допускается1, каждый из которых должен иметь либо более высокое значение, либо более низкое значение.


1 Хорошо - теоретически возможно, что следующий самый низкий поплавок был 2147483646.9999999... поэтому, когда значение отображается с точностью до 6 цифр printf тогда это округляется, чтобы дать то, что было замечено. Но это не так в IEEE754, и вы можете легко экспериментировать, чтобы обесценить эту возможность.

Во-первых printfПреобразование из целого числа в число с плавающей точкой выполняется компилятором. На втором это выполняется библиотекой времени выполнения C. Нет особой причины, по которой они должны давать идентичные ответы в пределах своей точности.

Visual C++ 6.0 был выпущен в прошлом веке, и я считаю, что он предшествует стандарту C++. Совершенно неудивительно, что VC++ 6.0 демонстрирует нарушенное поведение.

Вы также заметите, что gcc-3.4.2 относится к 2004 году. Действительно, вы используете 32-битный компилятор. GCC на x86 играет довольно быстро и свободно с математикой с плавающей точкой. Технически это может быть оправдано стандартом C, если gcc устанавливает FLT_EVAL_METHOD к чему-то ненулевому.

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

http://ideone.com/Ssw8GR

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

http://ideone.com/OGypBC

(с явным преобразованием числа с плавающей запятой).

во всяком случае, если мы посчитаем ошибку, это 4.656612875245797e-10 так много, и следует считать довольно точным.

это может быть связано с предпочтением printf тоже.

Другие вопросы по тегам