Разница между поплавком и двойным

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

Я решал проблему на конкурсе программирования, и были вычисления с числами с плавающей запятой, которые были не очень большими, поэтому я решил использовать float вместо double, и я проверил его - я получил правильные результаты. Но когда я отправляю решение, он сказал, что только 1 из 10 тестов были правильными. Я проверил снова и снова, пока не обнаружил, что использование float не совпадает с использованием double. Я поместил double для вычислений и удвоил для вывода, и программа дала результаты SAME, но на этот раз правильно провела все 10 тестов.

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

Почему? В чем разница?

Ответ 1

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

Как следует из названия, 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,2 цифры

Эта прецизионная потеря может привести к значительному уменьшению ошибок усечения, например,

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

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


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


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


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

Ответ 2

Вот что говорят стандарты стандарта C99 (ISO-IEC 9899 6.2.5 §10) или С++ 2003 (ISO-IEC 14882-2003 3.1.9 §8):

Существует три типа с плавающей запятой: float, double и long double. Тип double обеспечивает как минимум такую ​​же точность, как float, а тип long double обеспечивает как минимум такую ​​же точность, как double. Набор значений типа float является подмножеством набора значений типа double; набор значений типа double является подмножеством набора значений типа long double.

Стандарт С++ добавляет:

Представление значений типов с плавающей запятой определяется реализацией.

Я бы предложил взглянуть на отличный Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой, которая охватывает стандарт с плавающей точкой IEEE в глубине. Вы узнаете о деталях представления, и вы поймете, что существует компромисс между величиной и точностью. Точность представления с плавающей запятой увеличивается с уменьшением величины, поэтому числа с плавающей запятой между -1 и 1 являются наиболее точными.

Ответ 3

Учитывая квадратичное уравнение: 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.

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

Ответ 4

  • Двойной 64 и одинарная точность (float) - 32 бита.
  • У двойника есть большая мантисса (целочисленные биты действительного числа).
  • Любые неточности будут меньше в двойном.

Ответ 5

"были расчеты с плавающей которые не были действительно большой"

Размер чисел не имеет значения. Это расчет, который выполняется, что имеет значение.

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

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

Ответ 7

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

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

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

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

Ответ 8

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

Ответ 9

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

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

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

Ответ 10

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

Ответ 11

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