Is 'float a = 3.0;' правильное утверждение?

Если у меня есть следующее объявление:

float a = 3.0 ;

- это ошибка? Я прочитал в книге, что 3.0 является значением double и что я должен указать его как float a = 3.0f. Это так?

Ответ 1

Нельзя объявить float a = 3.0: если вы это сделаете, компилятор преобразует двойной литерал 3.0 в float для вас.


Однако вы должны использовать нотацию литералов с плавающей запятой в определенных сценариях.

  • По соображениям производительности:

    В частности, рассмотрим:

    float foo(float x) { return x * 0.42; }
    

    Здесь компилятор выдает преобразование (которое вы будете платить во время выполнения) для каждого возвращаемого значения. Чтобы избежать этого, вы должны объявить:

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  • Чтобы избежать ошибок при сравнении результатов:

    например. следующее сравнение не выполняется:

    float x = 4.2;
    if (x == 4.2)
       std::cout << "oops"; // Not executed!
    

    Мы можем исправить его с помощью буквенного обозначения:

    if (x == 4.2f)
       std::cout << "ok !"; // Executed!
    

    (Примечание: конечно, это не то, как вы должны сравнивать float или double numbers для равенства вообще)

  • Чтобы вызвать правильную перегруженную функцию (по той же причине):

    Пример:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    int main()
    {       
        foo(42.0);   // calls double overload
        foo(42.0f);  // calls float overload
        return 0;
    }
    
  • Как отмечено в Cyber ​​, в контексте вывода типа необходимо помочь компилятору вывести float:

    В случае auto:

    auto d = 3;      // int
    auto e = 3.0;    // double
    auto f = 3.0f;   // float
    

    И аналогично, в случае вывода типа шаблона:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    template<typename T>
    void bar(T t)
    {
          foo(t);
    }
    
    int main()
    {   
        bar(42.0);   // Deduce double
        bar(42.0f);  // Deduce float
    
        return 0;
    }
    

Живая демонстрация

Ответ 2

Компилятор превратит любой из следующих литералов в float, потому что вы объявили переменную как float.

float a = 3;     // converted to float
float b = 3.0;   // converted to float
float c = 3.0f;  // float

Было бы важно, если бы вы использовали auto (или другие методы вычитания типа), например:

auto d = 3;      // int
auto e = 3.0;    // double
auto f = 3.0f;   // float

Ответ 3

Литералы с плавающей точкой без суффикса имеют тип double, это описано в стандартном разделе проекта С++ 2.14.4 Плавающие литералы:

[...] Тип плавающего литерала является двойным, если явно не указан суффиксом. [...]

так это ошибка, чтобы присваивать 3.0 двойной литерал для float?:

float a = 3.0

Нет, это не так, оно будет преобразовано, которое описано в разделе 4.8 Преобразования с плавающей запятой:

Значение типа с плавающей запятой может быть преобразовано в prvalue другой тип с плавающей точкой. Если исходное значение может быть точно представленный в типе адресата, результатом преобразования является это точное представление. Если значение источника находится между двумя соседними целевые значения, результатом преобразования является определенный реализацией выбор любого из этих значений. В противном случае, поведение undefined.

Мы можем прочитать более подробную информацию о последствиях этого в GotW # 67: двойной или ничего, в котором говорится:

Это означает, что двойная константа может быть неявно (т.е. молча) преобразуется в постоянную поплавка, даже если при этом теряется точность (т.е. данные). Этому разрешалось оставаться для совместимости и удобства использования C причины, но стоит иметь в виду, когда вы делаете плавающие точки работа.

Компилятор качества предупредит вас, если вы попытаетесь сделать что-то, что undefined, а именно, поместить двойное количество в float, что меньше минимального или больше максимального значения, которое плавает может представлять. Действительно хороший компилятор предоставит необязательный предупреждение, если вы попытаетесь сделать что-то, что может быть определено, но можете проиграть информации, а именно, поместить двойное количество в поплавок, который находится между минимальные и максимальные значения, представляемые поплавком, но которые не может быть представлен точно как float.

Так что в общем случае есть оговорки, о которых вы должны знать.

С практической точки зрения, в этом случае результаты, скорее всего, будут одинаковыми, хотя технически существует преобразование, мы можем это увидеть, попробовав следующий код на godbolt:

#include <iostream>

float func1()
{
  return 3.0; // a double literal
}


float func2()
{
  return 3.0f ; // a float literal
}

int main()
{  
  std::cout << func1() << ":" << func2() << std::endl ;
  return 0;
}

и мы видим, что результаты для func1 и func2 идентичны, используя как clang, так и gcc:

func1():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret
func2():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret

Как Pascal указывает в этом комментарии, вы не всегда сможете рассчитывать на это. Использование 0.1 и 0.1f соответственно приводит к тому, что сгенерированная сборка отличается, поскольку преобразование должно выполняться явно. Следующий код:

float func1(float x )
{
  return x*0.1; // a double literal
}

float func2(float x)
{
  return x*0.1f ; // a float literal
}

выводится следующая сборка:

func1(float):  
    cvtss2sd    %xmm0, %xmm0    # x, D.31147    
    mulsd   .LC0(%rip), %xmm0   #, D.31147
    cvtsd2ss    %xmm0, %xmm0    # D.31147, D.31148
    ret
func2(float):
    mulss   .LC2(%rip), %xmm0   #, D.31155
    ret

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

Примечание

Как указывает суперкарт, умножение на, например, 0.1 и 0.1f не эквивалентны. Я просто собираюсь процитировать комментарий, потому что он был отличным, и резюме, вероятно, не сделало бы это справедливым:

Например, если f равно 100000224 (что точно представляемый как float), умножая его на одну десятую, должно результат округляется до 10000022, но умножение на 0.1f будет вместо этого получим результат, который ошибочно округляется до 10000023. Если намерение состоит в том, чтобы разделить на десять, умножение на двойную константу 0,1 скорее всего, будет быстрее, чем деление на 10f, и точнее, чем умножение на 0,1f.

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

Ответ 4

Это не ошибка в том смысле, что компилятор ее отклонит, но это ошибка в том смысле, что она может быть не такой, какой вы хотите.

Как правильно указывается ваша книга, 3.0 - это значение типа double. Существует неявное преобразование из double в float, поэтому float a = 3.0; является допустимым определением переменной.

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

3.0f устраняет эту проблему: хотя технически компилятору по-прежнему разрешено вычислять константу во время выполнения (она всегда есть), здесь нет абсолютно никакой причины, почему любой компилятор может это сделать.

Ответ 5

Пока не ошибка, сама по себе, она немного неряшлива. Вы знаете, что хотите поплавок, поэтому инициализируйте его плавающей точкой.
Еще один программист может прийти и не быть уверенным, какая часть декларации верна, тип или инициализатор. Почему бы им не быть правильными?
  float Отвечать = 42.0f;

Ответ 6

Когда вы определяете переменную, она инициализируется предоставленным инициализатором. Это может потребовать преобразования значения инициализатора в тип инициализируемой переменной. То, что происходит, когда вы говорите float a = 3.0;: значение инициализатора преобразуется в float, а результат преобразования становится начальным значением a.

Это вообще прекрасно, но не помешает писать 3.0f, чтобы показать, что вы знаете, что делаете, и особенно если вы хотите написать auto a = 3.0f.

Ответ 7

Если вы попробуете следующее:

std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;

вы получите вывод как:

4:8

который показывает, что размер 3.2f принимается за 4 байта на 32-разрядной машине. 3.2 интерпретируется как двойное значение, принимающее 8 байтов на 32-битной машине. Это должно обеспечить ответ, который вы ищете.

Ответ 8

Компилятор выводит наиболее подходящий тип из литералов, или на лизу, что, по его мнению, лучше всего подходит. Это скорее снижает эффективность по сравнению с точностью, т.е. Использует double вместо float. Если вы сомневаетесь, используйте командлеты-фиксаторы, чтобы сделать это явным:

auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int

История становится более интересной, если вы инициализируетесь из другой переменной, в которой применяются правила преобразования типов. Хотя законно составлять двойную форму литерала, она не может быть выполнена из int без сужения:

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double'