Я так понимаю: вычитая два double
числа с двойной точностью в c ++ они сначала преобразуются в значение и начинаются с одного раза 2 до степени экспоненты. Тогда можно получить ошибку, если вычтенные числа имеют одинаковый показатель степени и много одинаковых цифр в значении, что приводит к потере точности. Чтобы проверить это для моего кода, я написал следующую функцию безопасного добавления:
double Sadd(double d1, double d2, int& report, double prec) {
int exp1, exp2;
double man1=frexp(d1, &exp1), man2=frexp(d2, &exp2);
if(d1*d2<0) {
if(exp1==exp2) {
if(abs(man1+man2)<prec) {
cout << "Floating point error" << endl;
report=0;
}
}
}
return d1+d2;
}
Однако, проверяя это, я замечаю кое-что странное: кажется, что фактическая ошибка (не о том, сообщает ли функция об ошибке, а о фактической ошибке, полученной в результате вычислений), кажется, зависит от абсолютных значений вычитаемых чисел, а не только от числа равных цифр. в значимом …
Например, используя 1e-11
как точность prec
и вычитая следующие числа:
1) 9.8989898989898-9.8989898989897: функция сообщает об ошибке, и я получаю крайне неправильное значение 9.9475983006414e-14
2) 98989898989898-98989898989897: функция сообщает об ошибке, но я получаю правильное значение 1
Очевидно, я что-то неправильно понял. Есть идеи?
Если вычесть два значения с плавающей точкой, которые почти равны, результат будет в основном отражать шум в младших битах. Почти равный здесь больше, чем просто один и тот же показатель степени и почти те же цифры. Например, 1.0001 и 1.0000 почти равны, и вычитание их может быть поймано таким тестом. Но 1.0000 и 0.9999 отличаются на одинаковую величину, и такой тест не будет зафиксирован.
Далее это не функция безопасного добавления. Скорее, это специальная проверка на наличие ошибки проектирования / кодирования. Если вы вычитаете два значения, которые находятся настолько близко друг к другу, что шум имеет значение, вы допустили ошибку. Исправьте ошибку. Я не возражаю против использования чего-то подобного в качестве средства отладки, но, пожалуйста, назовите это как-то, что подразумевает, что это то, чем оно является, вместо того, чтобы предположить, что в добавлении с плавающей запятой есть что-то опасное по своей природе. Кроме того, размещение проверки внутри функции сложения кажется чрезмерным: утверждение о том, что два значения не вызовут проблем, а затем простое старое сложение с плавающей точкой, вероятно, было бы лучше. В конце концов, большинство дополнений в вашем коде не приведут к проблемам, и вам лучше знать, где находятся проблемные места; поставить утверждения в проблемных точках.
+1 к ответу Пита Беккера.
Обратите внимание, что проблема вырожденного результата может также возникнуть с exp1! = Exp2
Например, если вычесть
1.0-0.99999999999999
Так,
bool degenerated =
(epx1==exp2 && abs(d1+d2)<prec)
|| (epx1==exp2-1 && abs(d1+2*d2)<prec)
|| (epx1==exp2+1 && abs(2*d1+d2)<prec);
Вы можете опустить проверку для d1 * d2<0, или сохраните его, чтобы избежать всего теста в противном случае …
Если вы хотите также справиться с потерей точности с вырожденными денормализованными числами с плавающей запятой, это будет немного более сложным (как будто бы у значимого и меньше битов).
Это довольно легко доказать, что для арифметики IEEE 754 с плавающей точкой, если х / 2 <= у <= 2x, то вычисление x — y является точной операцией и даст точный результат без ошибок округления.
И если результатом сложения или вычитания является денормализованное число, то результат всегда точный.