Неявное преобразование типов в C

#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).