В чем разница между float и double?
Я читал о разнице между двойной точностью и одинарной точностью. Однако в большинстве случаев float
а также double
кажутся взаимозаменяемыми, то есть использование того или другого не влияет на результаты. Это действительно так? Когда поплавки и двойники взаимозаменяемы? Каковы различия между ними?
15 ответов
Огромная разница.
Как следует из названия, double
имеет 2x точность float
[1]. В общем double
имеет 15 десятичных цифр точности, в то время как float
имеет 7.
Вот как рассчитывается количество цифр:
double
имеет 52 бита мантиссы + 1 скрытый бит: log (2 53) ÷ log (10) = 15,95 цифр
float
имеет 23 бита мантиссы + 1 скрытый бит: log (2 24) ÷ log (10) = 7,22 цифры
Эта потеря точности может привести к ошибкам усечения, которые легче всплыть, например
float a = 1.f / 81;
float b = 0;
for (int i = 0; i < 729; ++ i)
b += a;
printf("%.7g\n", b); // prints 9.000023
в то время как
double a = 1.0 / 81;
double b = 0;
for (int i = 0; i < 729; ++ i)
b += a;
printf("%.15g\n", b); // prints 8.99999999999996
Кроме того, максимальное значение с плавающей точкой составляет около 3e38
, но двойка о 1.7e308
так что используя float
может ударить "бесконечность" (то есть специальное число с плавающей точкой) гораздо легче, чем double
для чего-то простого, например, вычисление факториала 60.
Во время тестирования, возможно, несколько тестовых примеров содержат эти огромные числа, которые могут вызвать сбой ваших программ, если вы используете плавающие числа.
Конечно, иногда даже double
не достаточно точен, поэтому мы иногда имеем long double
[1] (приведенный выше пример дает 9.000000000000000066 на Mac), но все типы с плавающей запятой страдают от ошибок округления, поэтому, если точность очень важна (например, обработка денег), вам следует использовать int
или класс дроби.
Кроме того, не используйте +=
суммировать множество чисел с плавающей запятой, так как ошибки быстро накапливаются. Если вы используете Python, используйте fsum
, В противном случае попробуйте реализовать алгоритм суммирования Кахана.
[1]: стандарты C и C++ не определяют представление float
, double
а также long double
, Вполне возможно, что все три реализованы как IEEE двойной точности. Тем не менее, для большинства архитектур (gcc, MSVC; x86, x64, ARM) float
действительно является числом с плавающей запятой IEEE с одинарной точностью (binary32), и double
является числом с плавающей точкой двойной точности IEEE (binary64).
Вот что говорят стандарты C99 (ISO-IEC 9899 6.2.5 §10) или C++2003 (ISO-IEC 14882-2003 3.1.9 §8):
Есть три типа с плавающей точкой:
float
,double
, а такжеlong double
, Типdouble
обеспечивает как минимум такую же точность, какfloat
и типlong double
обеспечивает как минимум такую же точность, какdouble
, Набор значений типаfloat
является подмножеством набора значений типаdouble
; набор значений типаdouble
является подмножеством набора значений типаlong double
,
Стандарт C++ добавляет:
Представление значений типов с плавающей запятой определяется реализацией.
Я бы посоветовал взглянуть на превосходное " Что должен знать каждый компьютерщик" об арифметике с плавающей запятой, в которой подробно рассматривается стандарт IEEE с плавающей запятой. Вы узнаете о деталях представления и поймете, что есть компромисс между величиной и точностью. Точность представления с плавающей запятой увеличивается с уменьшением величины, поэтому числа с плавающей запятой от -1 до 1 являются точными с наибольшей точностью.
Для заданного квадратного уравнения: x2 - 4.0000000 x + 3.9999999 = 0 точные корни из 10 значащих цифр: r1 = 2.000316228 и r2 = 1.999683772.
С помощью float
а также double
Мы можем написать тестовую программу:
#include <stdio.h>
#include <math.h>
void dbl_solve(double a, double b, double c)
{
double d = b*b - 4.0*a*c;
double sd = sqrt(d);
double r1 = (-b + sd) / (2.0*a);
double r2 = (-b - sd) / (2.0*a);
printf("%.5f\t%.5f\n", r1, r2);
}
void flt_solve(float a, float b, float c)
{
float d = b*b - 4.0f*a*c;
float sd = sqrtf(d);
float r1 = (-b + sd) / (2.0f*a);
float r2 = (-b - sd) / (2.0f*a);
printf("%.5f\t%.5f\n", r1, r2);
}
int main(void)
{
float fa = 1.0f;
float fb = -4.0000000f;
float fc = 3.9999999f;
double da = 1.0;
double db = -4.0000000;
double dc = 3.9999999;
flt_solve(fa, fb, fc);
dbl_solve(da, db, dc);
return 0;
}
Запуск программы дает мне:
2.00000 2.00000
2.00032 1.99968
Обратите внимание, что цифры не большие, но вы все равно получаете эффекты отмены, используя float
,
(На самом деле, вышеизложенное не является лучшим способом решения квадратных уравнений с использованием чисел с плавающей запятой одинарной или двойной точности, но ответ остается неизменным, даже если используется более устойчивый метод.)
- Двойное число равно 64, а одинарная точность (число с плавающей запятой) составляет 32 бита.
- Двойник имеет большую мантиссу (целые биты действительного числа).
- Любые неточности будут в два раза меньше.
Я только что натолкнулся на ошибку, которая потребовала у меня целую вечность, чтобы выяснить это и потенциально может дать вам хороший пример точности вычислений.
#include <iostream>
#include <iomanip>
int main(){
for(float t=0;t<1;t+=0.01){
std::cout << std::fixed << std::setprecision(6) << t << std::endl;
}
}
Выход
0.000000
0.010000
0.020000
0.030000
0.040000
0.050000
0.060000
0.070000
0.080000
0.090000
0.100000
0.110000
0.120000
0.130000
0.140000
0.150000
0.160000
0.170000
0.180000
0.190000
0.200000
0.210000
0.220000
0.230000
0.240000
0.250000
0.260000
0.270000
0.280000
0.290000
0.300000
0.310000
0.320000
0.330000
0.340000
0.350000
0.360000
0.370000
0.380000
0.390000
0.400000
0.410000
0.420000
0.430000
0.440000
0.450000
0.460000
0.470000
0.480000
0.490000
0.500000
0.510000
0.520000
0.530000
0.540000
0.550000
0.560000
0.570000
0.580000
0.590000
0.600000
0.610000
0.620000
0.630000
0.640000
0.650000
0.660000
0.670000
0.680000
0.690000
0.700000
0.710000
0.720000
0.730000
0.740000
0.750000
0.760000
0.770000
0.780000
0.790000
0.800000
0.810000
0.820000
0.830000
0.839999
0.849999
0.859999
0.869999
0.879999
0.889999
0.899999
0.909999
0.919999
0.929999
0.939999
0.949999
0.959999
0.969999
0.979999
0.989999
0.999999
Как вы можете видеть после 0,83, точность значительно снижается.
Однако, если я настрою t
как двойной, такой проблемы не будет.
Мне потребовалось пять часов, чтобы понять эту незначительную ошибку, которая разрушила мою программу.
Есть три типа с плавающей запятой:
- плавать
- двойной
- длинный двойной
Простая диаграмма Венна объяснит: Набор значений типов.
ht tps://stackru.com/images/c564283e97287bc27c8df75585f5644835baeda5.jpg
Размер чисел, участвующих в вычислениях с плавающей точкой, - не самая важная вещь. Это расчет, который выполняется, который имеет отношение к делу.
По сути, если вы выполняете вычисление, а результатом является иррациональное число или повторяющееся десятичное число, то при округлении этого числа в используемую вами структуру данных конечного размера будут возникать ошибки округления. Так как double в два раза больше числа с плавающей точкой, ошибка округления будет намного меньше.
Тесты могут специально использовать числа, которые могут вызвать такую ошибку, и поэтому проверяли, что вы использовали соответствующий тип в своем коде.
Тип float, 32 бита, имеет точность 7 цифр. Хотя он может хранить значения с очень большим или очень маленьким диапазоном (+/- 3,4 * 10^38 или * 10^-38), он имеет только 7 значащих цифр.
Тип double, длиной 64 бита, имеет больший диапазон (*10^+/-308) и точность до 15 цифр.
Тип long double номинально равен 80 битам, хотя для данной пары компилятор / ОС может сохранять его как 12-16 байтов для целей выравнивания. Длинный дубль имеет показатель, который просто смехотворно огромен и должен иметь точность до 19 цифр. Microsoft, в своей бесконечной мудрости, ограничивает long double до 8 байтов, так же, как обычный double.
Вообще говоря, просто используйте тип double, когда вам нужно значение / переменная с плавающей точкой. Литеральные значения с плавающей запятой, используемые в выражениях, будут по умолчанию рассматриваться как двойные, и большинство математических функций, возвращающих значения с плавающей запятой, возвращают двойные. Вы избавите себя от многих головных болей и типов, если вы просто используете double.
Поплавки имеют меньшую точность, чем двойные. Хотя вы уже знаете, прочитайте " Что мы должны знать об арифметике с плавающей точкой" для лучшего понимания.
При использовании чисел с плавающей запятой вы не можете полагать, что ваши локальные тесты будут точно такими же, как тесты, выполняемые на стороне сервера. Среда и компилятор, вероятно, различаются в вашей локальной системе и в том месте, где выполняются финальные тесты. Я видел эту проблему много раз в некоторых соревнованиях TopCoder, особенно если вы пытаетесь сравнить два числа с плавающей запятой.
Встроенные операции сравнения отличаются тем, что при сравнении двух чисел с плавающей запятой разница в типе данных (то есть с плавающей или двойной) может привести к разным результатам.
Количественно, как указывали другие ответы, разница в том, что тип имеет примерно вдвое большую точность и в три раза больший диапазон, чем тип (в зависимости от того, как вы считаете).
Но, пожалуй, еще важнее качественная разница. Тип имеет хорошую точность, которой часто будет достаточно для всего, что вы делаете. Type , с другой стороны, имеет превосходную точность, которой почти всегда будет достаточно для того, что вы делаете.
В результате, который далеко не так известен, как должен был бы быть, вы почти всегда должны использовать type. Если у вас нет особой потребности, вы почти никогда не должны использовать type .
Как всем известно, "ошибка округления" часто является проблемой при работе с числами с плавающей запятой. Ошибка округления может быть незаметной, ее трудно отследить и трудно исправить. У большинства программистов нет ни времени, ни опыта, чтобы отслеживать и исправлять числовые ошибки в алгоритмах с плавающей запятой, потому что, к сожалению, детали в конечном итоге различны для каждого отдельного алгоритма. Но тип имеет достаточную точность, так что большую часть времени вам не о чем беспокоиться. В любом случае вы добьетесь хороших результатов. С type , с другой стороны, все время возникают тревожные проблемы с округлением .
И то, что не всегда отличается между типом и скоростью выполнения. На большинстве современных процессоров общего назначения арифметические операции над типом и занимают примерно одинаковое количество времени. Все делается параллельно, поэтому вы не платите штраф за скорость за больший диапазон и точность type . Вот почему можно с уверенностью порекомендовать, что, если у вас нет особой необходимости, вы почти никогда не должны использовать type . (Однако с учетом сказанного одна из таких особых потребностей возникает, когда вы выполняете встроенную работу на микроконтроллере или пишете код, оптимизированный для графического процессора. На этих процессорах введите
double
может быть значительно медленнее или практически отсутствовать, поэтому программисты обычно выбирают тип
float
за скорость и платите за это точностью.)
Если кто-то работает со встроенной обработкой, в конечном итоге базовое оборудование (например, FPGA или какая-то конкретная модель процессора / микроконтроллера) будет иметь float, оптимально реализованную в оборудовании, тогда как double будет использовать программные процедуры. Поэтому, если точности с плавающей запятой достаточно для удовлетворения потребностей, программа будет выполняться в несколько раз быстрее с плавающей точкой, а затем с двойной. Как отмечалось в других ответах, остерегайтесь ошибок накопления.
Разница между float и double заключается в том, что double имеет большую точность, чем переменные типа float. Когда вы объявляете переменную как float, она позволяет вводить только 6 знаков после запятой. То есть для переменной с плавающей точкойfloat f= 2.3333333;
//7 десятичных знаков после. это максимум, который вы можете хранить
Даже если вы сохраните значение, которое больше десяти цифр после десятичной запятой, оно не сохранит все число, а сохранит только до первых шести цифр после десятичной запятой. Кроме того, вы получите ошибку, если попытаетесь сохранить более 7 цифр после десятичной точки для этой переменной. В этом случае вам нужно инициализировать его следующим образом:float f= 2.3333334443f;
// тогда компилятор интерпретирует это как 2.3333334
В случае Double, он будет хранить до 15 цифр после десятичной точки. Пример: double d=1.222222345675423;
// 15 цифр после десятичной точки
В отличие от int
(целое число), float
иметь десятичную точку, и поэтому может double
, Но разница между ними заключается в том, что double
вдвое подробнее, чем float
Это означает, что он может иметь двойное количество чисел после десятичной точки.