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



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

1462   11  

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и действительно число с плавающей запятой 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 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 и одиночная точность (float) - 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, когда вам нужно значение с плавающей запятой/переменная. Литеральные значения с плавающей запятой, используемые в выражениях, по умолчанию будут рассматриваться как двойные, а большинство математических функций, возвращающих значения с плавающей запятой, возвращают двойные значения. Вы избавите себя от многих головных болей и типизаций, если вы просто используете double.

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

#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 как двойной, такой вопрос, не произойдет.

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

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

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

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

Comments

    Ничего не найдено.