C++ неверная арифметика с плавающей точкой
Для следующей программы:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
for (float a = 1.0; a < 10; a++)
cout << std::setprecision(30) << 1.0/a << endl;
return 0;
}
Я получаю следующий вывод:
1
0.5
0.333333333333333314829616256247
0.25
0.200000000000000011102230246252
0.166666666666666657414808128124
0.142857142857142849212692681249
0.125
0.111111111111111104943205418749
Что, безусловно, неправильно для младших разрядов, особенно в отношении 1/3,1/5,1/7 и 1/9. вещи начинают идти не так, как обычно, около 10^-16. Я ожидаю увидеть более похожее:
1
0.5
0.333333333333333333333333333333
0.25
0.2
0.166666666666666666666666666666
0.142857142857142857142857142857
0.125
0.111111111111111111111111111111
Это наследственный недостаток в классе float? Есть ли способ преодолеть это и иметь правильное разделение? Существует ли специальный тип данных для выполнения точных десятичных операций? Я просто делаю что-то глупое или неправильное в моем примере?
3 ответа
Существует множество чисел, которые компьютеры не могут представить, даже если вы используете float или float двойной точности. 1/3, или.3 повторения, является одним из этих чисел. Так что он просто делает все возможное, что является результатом, который вы получаете.
См. http://floating-point-gui.de/ или Google Float Precision, там есть тонна информации (включая много вопросов SO) по этому вопросу.
Чтобы ответить на ваши вопросы - да, это неотъемлемое ограничение как для класса float, так и для класса double. Некоторые математические программы (MathCAD, возможно, Mathematica) могут выполнять "символическую" математику, которая позволяет вычислять "правильные" ответы. Во многих случаях можно управлять ошибкой округления, даже при очень сложных вычислениях, так что первые 6-8 десятичных знаков являются правильными. Однако верно и обратное - могут быть построены наивные вычисления, которые возвращают дико неверные ответы.
Для небольших задач, таких как деление целых чисел, вы получите приличное количество знаков после запятой (возможно, 4-6 знаков). Если вы используете числа с плавающей запятой двойной точности, это может увеличиться до восьми. Если вам нужно больше... ну, я бы начал спрашивать, зачем вам столько десятичных разрядов.
Прежде всего, так как ваш код делает 1.0/a
, это дает вам double
(1.0
это double
значение, 1.0f
является float
) поскольку правила C++ (и C) всегда расширяют меньший тип на больший, если операнды операции имеют другой размер (так, int
+ char
делает char
в int
перед добавлением значений, long
+ int
сделает int
долго и тд и тп).
Вторые значения с плавающей запятой имеют установленное количество битов для "числа". В float это 23 бита (+ 1 "скрытый" бит), а в двойном - 52 бита (+1). Тем не менее, получите примерно 3 цифры на бит (точнее: log2(10), если мы используем представление десятичных чисел), поэтому 23-битное число дает примерно 7-8 цифр, а 53-битное число - примерно 16-17 цифр. Остальная часть - просто "шум", вызванный последними несколькими битами числа, не выравнивающимися при преобразовании в десятичное число.
Чтобы иметь бесконечную точность, мы должны либо хранить значение в виде дроби, либо иметь бесконечное число битов. И, конечно, у нас может быть какая-то другая конечная точность, например, 100 бит, но я уверен, что вы бы тоже на это пожаловались, потому что у него просто было бы еще 15 или около того цифр, прежде чем он "пойдет не так".
Плавающие имеют только такую точность (23 бита, если быть точным). Если вы ДЕЙСТВИТЕЛЬНО хотите видеть вывод "0.333333333333333333333333333333", вы можете создать собственный класс "Fraction", в котором числитель и знаменатель хранятся отдельно. Тогда вы можете рассчитать цифру в любой точке с полной точностью.