#c #floating-point
Вопрос:
У меня есть математическая функция решения задач в C.
double task1_double() {
double a = 1000;
double b = 0.0001;
double result = (pow((a b), 2) - (pow(a, 2) 2 * a * b))/(pow(b, 2));
return resu< }
float task1_float() {
float a = 1000;
float b = 0.0001f;
float result = (powf((a b), 2) - (powf(a, 2) 2 * a * b))/(powf(b, 2));
return resu< }
Когда я использую двойной тип данных, он возвращает 1.001172, хотя при использовании типа данных с плавающей точкой результат составляет 6250000.000000.
Кто-нибудь может объяснить, почему это происходит? Заранее спасибо
Комментарии:
1. Разделите составные выражения на более простые, используя временные двойники для хранения промежуточных результатов. Распечатайте все и таким образом сузьте область возникновения ошибки. Это называется «отладкой». Вы также можете использовать настоящий отладчик для отслеживания выполнения построчно, проверяя значения на каждом этапе.
2. я нашел проблему, float b = 0.0001 экономит 9.99999975 e-05, несмотря на экономию 0.0001. Могу я как-нибудь это исправить?
3. Используйте двойное для всего. Это смягчит, но не устранит внутреннюю проблему с любым представлением с плавающей запятой, которое пытается (и терпит неудачу) охватить бесконечный диапазон значений с бесконечной оперативной памятью.
4. На всякий случай это реальная проблема: формула математически упрощается до 1.
Ответ №1:
Ваш числитель точно
1000000.20000001 - 1000000.2 = 0.00000001
но когда вы вычитаете два больших числа, которые почти равны, относительная ошибка округления в результате может увеличиться. Это то, что вы видите. Это следствие того факта, что float
числа имеют точность около 7 десятичных знаков, в то double
время как числа имеют около 16 десятичных знаков.
Давайте сделаем это шаг за шагом:
exact float double
x = pow((a b), 2) 1000000.20000001 1000000.25 1000000.200000009965
y = pow(a, 2) 2 * a * b 1000000.2 1000000.1875 1000000.199999999953
x - y 0.00000001 0.0625 0.000000010011717677
При такой небольшой разнице между двумя числами относительно их величины вы обычно получаете float
результат, равный 0,0. Но в этом случае просто случается, что 1000000.20000001 и 1000000.2 лежат по обе стороны границы округления, в результате чего первое округляется вверх, а второе-вниз. Таким образом, их разница составляет шесть порядков величины.
Комментарии:
1. Я очень ценю вашу помощь, ваше объяснение идеально
Ответ №2:
Выводя промежуточные результаты, можно обнаружить, что это вызвано потерей точности поплавка
#include <stdio.h>
#include <math.h>
double task1_double() {
double a = 1000;
double b = 0.0001;
double s1, s2, s3;
s1 = pow((a b), 2);
s2 = pow(a, 2) 2 * a * b;
s3 = pow(b, 2);
double result = (pow((a b), 2) - (pow(a, 2) 2 * a * b)) / (pow(b, 2));
printf("_double : %lf - %lf = %.15lfn", s1, s2, s1 - s2);
printf("_double : (%lf - %lf) / %.10lf = %lfn", s1, s2, s3, result);
return resu<
}
float task1_float() {
float a = 1000;
float b = 0.0001f;
float s1, s2, s3;
s1 = powf((a b), 2);
s2 = powf(a, 2) 2 * a * b;
s3 = powf(b, 2);
float result = (powf((a b), 2) - (powf(a, 2) 2 * a * b)) / (powf(b, 2));
printf("_float : %lf - %lf = %.15lfn", s1, s2, s1 - s2);
printf("_float : (%lf - %lf) / %.10lf = %lfn", s1, s2, s3, result);
return resu<
}
int main()
{
printf("%.10lfn%.10lfn", task1_double(), task1_float());
return 0;
}
Выход:
_float : 1000000.250000 - 1000000.187500 = 0.062500000000000
_float : (1000000.250000 - 1000000.187500) / 0.0000000100 = 6250000.500000
_double : 1000000.200000 - 1000000.200000 = 0.000000010011718
_double : (1000000.200000 - 1000000.200000) / 0.0000000100 = 1.001172
1.0011717677
6250000.5000000000
Как можно видеть:
После срабатывания поплавка возникает небольшая ошибка, но деление на небольшое значение b приведет к многократному увеличению ошибки
Комментарии:
1. Нет, проблема не в делении, а в вычитании. Смотрите мой ответ.
2. @TonyK: Вычитание имеет результат с повышенной относительной погрешностью. Разделение приводит к увеличению абсолютной погрешности.
3. @EricPostpischil: но проблема заключается в вычитании, не так ли? Подразделение хорошо себя ведет, без проблем с округлением. Это никак не объясняет неожиданный результат операции.
4. @TonyK: Я бы так не сказал. Вычитание двух чисел, близких по значению, никогда не добавляет никакой ошибки. То, что относительная погрешность на его выходе больше, чем относительная погрешность на его входах, не является его ошибкой. Фактическая причина ошибки находится ранее, в округлении исходного текста
0.0001f
доfloat
и
pow
в операциях и. Почему-
его должны винить, если он отлично выполнил свою работу без ошибок, а все остальные облажались?5. @EricPostpischil: Я не нахожу слов (а это случается не часто). Вы читали мой ответ? Вы с этим не согласны?