В чем разница между float и double?

Я читал о разнице между двойной точностью и плавающей запятой одинарной точности. Однако, в большинстве случаев, float и double кажутся взаимозаменяемыми, т. е. использование одного или другого не влияет на результаты. Это действительно так? Когда поплавки и двойники взаимозаменяемы? В чем разница между ними?

11 ответов


огромная разница.

как следует из названия, a double имеет 2x точность float[1]. В общем double состоит из 15 десятичных цифр точности, в то время как float в 7.

вот как рассчитывается количество цифр:

double имеет 52 бита мантиссы + 1 скрытый бит: log (253)÷log (10) = 15.95 цифр

float в 23 мантиссы + 1 скрытый бит: log (224)÷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

кроме того, максимальное значение float составляет около 3e38, но около 1.7e308, таким образом, используя float может попасть в "бесконечность" (т. е. специальное число с плавающей запятой) гораздо легче, чем double для чего-то простого, например, вычисление факториала 60.

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


конечно, иногда, даже double недостаточно точно, поэтому у нас иногда есть long double[1] (приведенный выше пример дает 9.000000000000000066 на Mac), но все типы с плавающей запятой страдают от ошибки округления, поэтому, если точность очень важна (например, обработка денег) , вы должны использовать int или класс fraction.


кроме того, не используйте += суммировать много чисел с плавающей запятой, так как ошибки быстро накапливаются. Если вы используете Python, используйте fsum. В противном случае попробуйте реализовать алгоритм суммирования Кахана.


[1]: стандарты C и C++ не определяют представление float, double и long double. Возможно, что все три реализованы как IEEE двойной точности. Тем не менее, для большинства архитектур (gcc, MSVC; x86, x64, ARM) float is действительно число с плавающей запятой IEEE с одной точностью (binary32) и double is номер с плавающей запятой двойной точности 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 is подмножество значений типа 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 бита.
  • двойник имеет большую мантиссу (целочисленные биты действительного числа).
  • любые неточности будут меньше в двойном.

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

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

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


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


тип поплавок, 32 бита длиной, имеет точность 7 цифр. Хотя он может хранить значения с очень большим или очень маленьким диапазоном (+/- 3.4 * 10^38 или * 10^-38), он имеет только 7 значащих цифр.

тип двойной, 64 бита длиной, имеет более большой ряд (*10^+/-308) и точность 15 чисел.

тип long double номинально составляет 80 бит, хотя данное сопряжение компилятора / ОС может хранить его как 12-16 байт для целей выравнивания. Длинный двойник имеет показатель, который просто смехотворно огромен и должен иметь точность 19 цифр. Microsoft, в своей бесконечной мудрости, ограничивает long double до 8 байтов, так же, как простой double.

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


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

#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 как double, такая проблема не произойдет.

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


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


встроенные операции сравнения отличаются, как при сравнении 2 чисел с плавающей запятой, разница в типе данных (т. е. float или double) может привести к различным результатам.


в отличие от int (целое число), a float имеют десятичную точку, а также double. Но разница между ними в том, что a double в два раза как float, что означает, что он может иметь двойное количество чисел после десятичной точки.