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

420

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

VaioIsBorn
источник

Ответы:

521

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

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

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

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

kennytm
источник
9
Обычный совет для суммирования состоит в том, чтобы отсортировать числа с плавающей запятой по величине (сначала наименьшее) перед суммированием.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
Обратите внимание, что хотя C / C ++ float и double почти всегда имеют одинарную и двойную точность IEEE, соответственно, long / C ++ long double намного более изменчив, в зависимости от вашего процессора, компилятора и ОС. Иногда это то же самое, что и double, иногда это какой-то системный расширенный формат, иногда это четверная точность IEEE.
штепсельная вилка
@ R..GitHubSTOPHELPINGICE: почему? Могли бы вы объяснить?
InQusitive
@InQusitive: Рассмотрим, например, массив, состоящий из значения 2 ^ 24, за которым следуют 2 ^ 24 повторения значения 1. Суммирование в порядке дает 2 ^ 24. Реверс дает 2 ^ 25. Конечно, вы можете привести примеры (например, сделать 2 ^ 25 повторений из 1), где любой ордер в конечном итоге оказывается катастрофически неправильным с одним аккумулятором, но наименьшая величина в первую очередь является лучшей среди таких. Чтобы сделать лучше, вам нужно какое-то дерево.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
56

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

Григорий Пакош
источник
27

Для  заданного квадратного уравнения: 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
  • Двойное число равно 64, а одинарная точность (число с плавающей запятой) составляет 32 бита.
  • Двойник имеет большую мантиссу (целые биты действительного числа).
  • Любые неточности будут в два раза меньше.
graham.reeds
источник
12

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

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

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

Dolbz
источник
9

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

Заин Али
источник
На самом деле, для float это между 7 и 8, а точнее 7.225 .
Питер Мортенсен
9

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

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

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

Клык Эллископа
источник
4
просто чтобы быть уверенным: решение вашей проблемы должно состоять в том, чтобы использовать int предпочтительно? Если вы хотите выполнить итерацию 100 раз, вы должны считать с помощью int, а не двойного
BlueTrin
8
Использование doubleздесь не является хорошим решением. Вы используете intдля подсчета и внутреннего умножения, чтобы получить значение с плавающей точкой.
Ричард
3

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

Туомас Пелконен
источник
3

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

Джонатан Лау
источник
1

Если кто-то работает со встроенной обработкой, в конечном итоге базовое аппаратное обеспечение (например, FPGA или некоторая конкретная модель процессора / микроконтроллера) будет оптимально реализовано в аппаратном обеспечении с плавающей запятой, тогда как double будет использовать программные процедуры. Таким образом, если точность float достаточна для удовлетворения потребностей, программа будет выполняться в несколько раз быстрее с float, а затем double. Как отмечено в других ответах, остерегайтесь ошибок накопления.

Lissandro
источник
-1

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

Nykal
источник
4
Это совсем не значит. На самом деле это означает вдвое больше целых десятичных цифр, и это больше, чем вдвое. Соотношение между дробными цифрами и точностью не является линейным: оно зависит от значения: например, 0,5 является точным, а 0,33333333333333333333 - нет.
маркиз Лорн