Является ли "float A = 3.0;" правильным утверждением?



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



float a = 3.0 ;


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

552   8  

8 ответов:

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


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

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

    в частности, считать:

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

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

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

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

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

    мы можем исправить это с помощью буквенной нотации float:

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

    (Примечание: конечно, это не так, как вы должны сравнивать плавающие или двойные числа для равенства в целом)

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

    пример:

    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;
    }
    
  4. как отмечает Cyber, в контексте вывода типа, необходимо помочь компилятору вывести a 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;
    }
    

Live демо

компилятор превратит любой из следующих литералов в floats, потому что вы объявили переменную как 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

литералы с плавающей запятой без суффикса имеют тип двойной это описано в проекте стандарта C++ раздел 2.14.4плавающие литералы:

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

так это ошибка назначить 3.0 a Double литерал до float?:

float a = 3.0

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

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

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

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

качественный компилятор предупредит вас, если вы попытаетесь сделать что-нибудь неопределенное поведение, а именно поместите двойную величину в поплавок, который меньше минимального или больше максимального, значения, поплавок умеет представлять. Действительно хороший компилятор предоставит необязательный предупреждение, если вы попытаетесь сделать что-то, что может быть определено, но может потерять информация, а именно положить двойную величину в поплавок, который находится между минимум и максимальные значения, представимые с помощью 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

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

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

4:8

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

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

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' 

Comments

    Ничего не найдено.