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

Должны ли мы обычно использовать float-литералы для поплавков вместо простых двойных литералов?

В С++ (или, может быть, только наши компиляторы VC8 и VC10)3.14 - это двойной литерал, а 3.14f - это плавающий литерал.

Теперь у меня есть коллега, который заявил:

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

В частности, я думаю, он имел в виду:

double d1, d2;
float f1, f2;
... init and stuff ...
f1 = 3.1415  * f2;
f1 = 3.1415f * f2; // any difference?
d1 = 3.1415  * d2;
d1 = 3.1415f * d2; // any difference?

Или, добавленный мной, даже:

d1 = 42    * d2;
d1 = 42.0f * d2; // any difference?
d1 = 42.0  * d2; // any difference?

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

Может кто-то пролить свет на это? Вы указываете постфикс f? Почему?

Чтобы процитировать ответ, я неявно принял это как должное:

Если вы работаете с переменной float и двойным литеральным операция будет выполняться как двойная, а затем конвертируется обратно в float.

Может ли быть какой-либо вред в этом? (За исключением очень, очень теоретического воздействия на производительность?)

Дальнейшее редактирование: Было бы неплохо, если бы ответы, содержащие технические детали (оцененные!), также могли бы включать в себя то, как эти различия влияют на код общего назначения. (Да, если вы хрустали число, вы, вероятно, хотели бы убедиться, что ваши операции с большими числами с плавающей запятой максимально эффективны (и правильны), насколько это возможно, но имеет ли значение код общего назначения, который называется несколько раз? Isn ' t чище, если код просто использует 0.0 и пропускает суффикс с плавающей точкой для поддержания! - float?)

4b9b3361

Ответ 1

Да, вы должны использовать суффикс f. Причины включают:

  • Производительность

    . Когда вы пишете float foo(float x) { return x*3.14; }, вы вынуждаете компилятор испускать код, который преобразует x в double, затем выполняет умножение, а затем возвращает результат в одиночный. Если вы добавите суффикс f, то оба преобразования будут устранены. На многих платформах каждый из этих преобразований примерно такой же дорогой, как и само умножение.

  • Производительность (продолжение). Существуют платформы (например, большинство мобильных телефонов), на которых арифметика с двойной точностью значительно медленнее, чем с одной точностью. Даже игнорируя накладные расходы на конвертацию (см. 1.), каждый раз, когда вы вынуждаете вычисление оцениваться в два раза, вы замедляете свою программу. Это не просто "теоретическая" проблема.

  • Уменьшите подверженность ошибкам. Рассмотрим пример float x = 1.2; if (x == 1.2) // something; Is something? Нет, это не так, потому что x удерживает 1.2 округленным до float, но сравнивается с значением двойной точности 1.2. Эти два не равны.

Ответ 2

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

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

Ответ 3

Я сделал тест.

Я скомпилировал этот код:

float f1(float x) { return x*3.14; }            
float f2(float x) { return x*3.14F; }   

Использование gcc 4.5.1 для i686 с оптимизацией -O2.

Это был код сборки, сгенерированный для f1:

pushl   %ebp
movl    %esp, %ebp
subl    $4, %esp # Allocate 4 bytes on the stack
fldl    .LC0     # Load a double-precision floating point constant
fmuls   8(%ebp)  # Multiply by parameter
fstps   -4(%ebp) # Store single-precision result on the stack
flds    -4(%ebp) # Load single-precision result from the stack
leave
ret

И это код сборки, сгенерированный для f2:

pushl   %ebp
flds    .LC2          # Load a single-precision floating point constant
movl    %esp, %ebp
fmuls   8(%ebp)       # Multiply by parameter
popl    %ebp
ret

Итак, интересно то, что для f1 компилятор сохранил значение и снова загрузил его, чтобы убедиться, что результат был усечен до одной точности.

Если мы используем опцию -ffast-math, то это различие значительно уменьшается:

pushl   %ebp
fldl    .LC0             # Load double-precision constant
movl    %esp, %ebp
fmuls   8(%ebp)          # multiply by parameter
popl    %ebp
ret


pushl   %ebp
flds    .LC2             # Load single-precision constant
movl    %esp, %ebp
fmuls   8(%ebp)          # multiply by parameter
popl    %ebp
ret

Но по-прежнему существует разница между загрузкой одной или двойной точности.

Обновление для 64-разрядных

Это результаты с gcc 5.2.1 для x86-64 с оптимизацией -O2:

f1:

cvtss2sd  %xmm0, %xmm0       # Convert arg to double precision
mulsd     .LC0(%rip), %xmm0  # Double-precision multiply
cvtsd2ss  %xmm0, %xmm0       # Convert to single-precision
ret

f2:

mulss     .LC2(%rip), %xmm0  # Single-precision multiply
ret

С -ffast-math результаты одинаковы.

Ответ 4

Как правило, я не думаю, что это будет иметь значение, но это стоит указывая, что 3.1415f и 3.1415 (обычно) не равны. На с другой стороны, вы обычно не делаете никаких вычислений в float во всяком случае, по крайней мере на обычных платформах. (double так же быстро, если не быстрее.) О единственном времени, когда вы должны увидеть float, - это когда являются большими массивами, и даже тогда все вычисления будут обычно сделайте в double.

Ответ 5

Есть разница: если вы используете двойную константу и умножаете ее на переменную float, переменная сначала преобразуется в double, вычисление выполняется в double, а затем результат преобразуется в float. Хотя точность здесь не является проблемой, это может привести к путанице.

Ответ 6

Я лично склонен использовать нотацию f postfix в качестве основы и сделать ее очевидной, насколько могу, это тип float, а не двойной.

Мои два цента

Ответ 7

Из С++ Standard (рабочий проект), раздел 5 о бинарных операторах

Многие двоичные операторы, которые ожидают операнды арифметики или тип перечисления вызывает преобразования и дает результаты в аналогичных путь. Цель состоит в том, чтобы дать общий тип, который также является типом результат. Этот шаблон называется обычным арифметическим преобразованием, которые определяются следующим образом: - Если либо операнд имеет область действия тип перечисления (7.2), конверсии не выполняются; если другой операнд не имеет одного и того же типа, выражение плохо сформировано. - Если любой из операндов имеет тип long double, другой должен быть преобразован длинный двойной. - В противном случае, если один операнд двойной, другой должны быть преобразованы в двойные. - В противном случае, если один из операндов плавает, другой должен быть преобразован в float.

А также раздел 4.8

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

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