Подтвердить что ты не робот

Странное поведение при бросании int для плавания в C

У меня есть сомнения относительно вывода следующей программы на C. Я попытался скомпилировать его, используя как Visual С++ 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.

EDIT. Значение "2147483647.000000" не может быть значением с плавающей запятой с одной точностью, поскольку номер 2147483647 не может быть представлен точно в плавающей точке IEEE 754 с плавающей точкой формат без потери точности.

4b9b3361

Ответ 1

В обоих случаях код пытается преобразовать из некоторого целочисленного типа в 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 to double без предварительного прохождения float и что C11 был изменен, чтобы явно не разрешить пропуски приведения.

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


[Edit] Принимая комментарии вместе @Keith Thompson, @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;

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


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

-1 (То же, что и C99)

0 (То же, что и C99)

1 (То же, что и C99)

2 (То же, что и C99)

Ответ 2

Это, безусловно, ошибка компилятора. Из стандарта 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, и вы можете легко экспериментировать, чтобы уменьшить эту возможность.

Ответ 3

В первом printf преобразование из integer в float выполняется компилятором. На втором это выполняется библиотекой времени выполнения C. Нет особых причин, по которым они должны давать ответы, идентичные в пределах их точности.

Ответ 4

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

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

Ответ 5

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

http://ideone.com/Ssw8GR

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

http://ideone.com/OGypBC

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

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

он может относиться также к предпочтению printf.