В чем разница между float и double? - PullRequest
364 голосов
/ 05 марта 2010

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

Ответы [ 11 ]

457 голосов
/ 05 марта 2010

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

Как видно из названия, double имеет в 2 раза большую точность, чем 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, а double - около 1.7e308, поэтому использование float может достичь «бесконечности» (то есть специального числа с плавающей запятой) гораздо легче, чем double для чего-то простого, например вычисление факториала 60.

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


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


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


[1]: стандарты C и C ++ не определяют представление float, double и long double. Вполне возможно, что все три реализованы как IEEE двойной точности. Тем не менее, для большинства архитектур (gcc, MSVC; x86, x64, ARM) float - это действительно число с плавающей запятой IEEE с одинарной точностью (binary32), а double - число с плавающей точкой двойной точности IEEE (binary64).

54 голосов
/ 05 марта 2010

Вот что говорят стандартные стандарты 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 - это числа с наибольшей точностью.

26 голосов
/ 05 марта 2010

Учитывая квадратное уравнение: x 2 - 4.0000000 x + 3.9999999 = 0, точные корни до 10 значащих цифр: r 1 = 2.000316228 и r 2 = 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.

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

19 голосов
/ 05 марта 2010
  • Двойное число равно 64, а одинарная точность (число с плавающей запятой) равно 32 битам.
  • Двойное число имеет большую мантиссу (целые биты действительного числа).
  • Любые неточностибудет меньше в двойном.
11 голосов
/ 05 марта 2010

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

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

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

8 голосов
/ 08 марта 2011

Тип 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.

8 голосов
/ 05 марта 2010

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

6 голосов
/ 20 октября 2015

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

#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 как двойное, такой проблемы не будет.

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

3 голосов
/ 05 марта 2010

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

2 голосов
/ 07 декабря 2011

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

...