#c #floating-point #type-conversion
#c #с плавающей запятой #преобразование типа
Вопрос:
Я наткнулся на следующий пример в википедии (http://en.wikipedia.org/wiki/Type_conversion#Implicit_type_conversion ).
#include <stdio.h>
int main()
{
int i_value = 16777217;
float f_value = 16777217.0;
printf("The integer is: %in", i_value); // 16777217
printf("The float is: %fn", f_value); // 16777216.000000
printf("Their equality: %in", i_value == f_value); // result is 0
}
Их объяснение: «Это странное поведение вызвано неявным преобразованием i_value в float при сравнении с f_value; приведение, которое теряет точность, делая сравниваемые значения разными».
Разве это не неправильно? Если бы i_value было приведено к значению float, то оба значения имели бы одинаковую потерю точности и были бы равны. Поэтому i_value должно быть приведено к double.
Комментарии:
1. С помощью g (GCC 4.6.2) я получаю
1
для равенства.2. @Kerrek: И я. В VS я получаю 0.
3. @OliCharlesworth: Мне интересно изменить литерал на
f
или тип наdouble
— я получаю1
во всех случаях…4. Вероятно, вы используете систему x64, которая не использует расширенные 10 байт, а скорее обычные удвоения. Вы также можете наблюдать это на 32-разрядном процессоре с помощью -mfpmath=sse -msse.
Ответ №1:
Нет, в случае оператора равенства, происходят «обычные арифметические преобразования», которые начинаются с:
- Во-первых, если соответствующий действительный тип любого из операндов является
long double
, другой операнд преобразуется, без изменения домена типа, в тип, соответствующий действительному типу которого являетсяlong double
.- В противном случае, если соответствующий действительный тип любого из операндов является
double
, другой операнд преобразуется, без изменения домена типа, в тип, соответствующий действительному типу которого являетсяdouble
.- В противном случае, если соответствующий действительный тип любого из операндов является
float
, другой операнд преобразуется, без изменения домена типа, в тип, соответствующий действительному типу которого являетсяfloat
.
Здесь применим этот последний случай: i_value
преобразуется в float
.
Причина, по которой вы можете увидеть нечетный результат сравнения, несмотря на это, заключается в этом предостережении к обычным арифметическим преобразованиям:
Значения плавающих операндов и результатов плавающих выражений могут быть представлены с большей точностью и диапазоном, чем это требуется типом; типы при этом не изменяются.
Вот что происходит: тип преобразованного i_value
сохраняется float
, но в этом выражении ваш компилятор использует преимущества этой широты и представляет его с большей точностью, чем float
. Это типичное поведение компилятора при компиляции для 387-совместимой системы с плавающей запятой, поскольку компилятор оставляет временные значения в стеке с плавающей запятой, который хранит числа с плавающей запятой в формате расширенной точности 80 бит.
Если ваш компилятор является gcc
, вы можете отключить эту дополнительную точность, указав -ffloat-store
параметр командной строки.
Комментарии:
1. На x64 gcc использует явную инструкцию cvtsi2ssl, которая преобразует целое число в float. Однако на x86 именно это и происходит, и эта большая точность на самом деле даже больше, чем double.
2. @konrad.kruczynski: Да, и вы можете получить тот же результат на x86, указав
-mfpmath=sse
опцию (которая требует также requires-msse
или опцию, которая подразумевает это).
Ответ №2:
Здесь есть несколько хороших ответов. Вы должны быть очень осторожны при преобразовании между различными целыми числами и различными представлениями с плавающей запятой.
Обычно я не проверяю числа с плавающей запятой на равенство, особенно если одно из них получено в результате неявного или явного приведения из целочисленного типа. Я работаю над приложением, которое полно геометрических вычислений. Насколько это возможно, мы работаем с нормализованными целыми числами (задавая максимальную точность, которую мы примем во входных данных). В случаях, когда вы должны использовать значение с плавающей запятой, мы применим абсолютное значение к разнице, если потребуется сравнение.
Ответ №3:
Я полагаю, что наибольшее целое значение, которое может содержать 32-разрядная IEEE с плавающей запятой, равно 1048576, что меньше приведенного выше числа. Итак, определенно верно, что значение с плавающей запятой не будет содержать точно 16777217.
Часть, в которой я не уверен, заключается в том, как компилятор проводит сравнение между двумя разными типами чисел (т. Е. float и int). Я могу придумать три разных способа, которыми это можно было бы сделать:
1) Преобразуйте оба значения в «float» (это должно сделать значения одинаковыми, так что это, вероятно, не то, что делает компилятор).
2) Преобразуйте оба значения в «int» (это может показывать или не показывать их одинаково … преобразование в int часто приводит к усечению, поэтому, если значение с плавающей запятой равно 16777216.99999, то преобразование в «int» приведет к усечению)
3) Преобразуйте оба значения в «double». Мое предположение состояло бы в том, что это то, что сделал бы компилятор. Если это то, что делает компилятор, то эти два значения определенно будут отличаться. Двойное значение может содержать в точности 16777217, и оно также может точно представлять значение с плавающей запятой, в которое преобразуется 16777217.0 (что не совсем 16777217.0).