грех, кос, загар и ошибка округления - PullRequest
4 голосов
/ 06 октября 2009

Я делаю некоторые тригонометрические вычисления в C / C ++ и сталкиваюсь с проблемами с ошибками округления. Например, в моей системе Linux:

#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[]) {
    printf("%e\n", sin(M_PI));
    return 0;
}

Эта программа дает следующий вывод:

1.224647e-16

когда правильный ответ, конечно, 0.

Какую ошибку округления можно ожидать при использовании функций триггера? Как я могу лучше всего справиться с этой ошибкой? Я знаком с техникой «Единицы в последнем месте» для сравнения чисел с плавающей запятой, из Сравнения чисел с плавающей запятой Брюса Доусона , но здесь это не работает, поскольку 0 и 1.22e-16 довольно несколько ULP друг от друга.

Ответы [ 9 ]

13 голосов
/ 06 октября 2009

Ответ за 0 (pi) - только 0 - вы включили все цифры числа Pi?

- Кто-нибудь еще заметил здесь явное отсутствие иронии / чувства юмора?

12 голосов
/ 06 октября 2009

Двойной IEEE хранит 52 бита мантиссы с «неявным ведением» один ", формирующий 53-битное число. Ошибка в нижнем бите результата поэтому составляет около 1/2 ^ 53 шкалы чисел. Ваш вывод того же порядка, что и 1.0, так что получается примерно ровно один часть в 10 ^ 16 (потому что 53 * log (2) / log (10) == 15,9).

Так что да. Это примерно предел точности, которую вы можете ожидать. я не знаю, какой метод ULP вы используете, но я подозреваю, что вы применяя это неправильно.

6 голосов
/ 10 октября 2017

Синус π равен 0,0.
Синус M_PI составляет около 1,224647e-16.

M_PI не π.

программа выдает ... 1.224647e-16, когда правильный ответ, конечно, 0.

Код дал правильный ответ на 7 мест.


Следующее не печатает синус π . Он печатает синус числа, близкого к π. Смотрите ниже рис.

π                            // 3.1415926535897932384626433832795...
printf("%.21\n", M_PI);      // 3.141592653589793115998
printf("%.21f\n", sin(M_PI));// 0.000000000000000122465

Примечание. С помощью математической функции sine (x) наклон кривой равен -1,0 при x = π . Разница π и M_PI составляет примерно sin(M_PI) - , как и ожидалось .


У меня проблемы с округлением

Проблема округления возникает при использовании M_PI для представления π. M_PI - это double, ближайший к π, но поскольку π иррационально и все конечные double рациональны, они должны различаться - даже на небольшое количество. Так что не проблема прямого округления с sin(), cos(), tan(). sin(M_PI) просто разоблачил проблему, начавшуюся с использования неточного π.


Эта проблема с другими ненулевыми результатами, равными sin(M_PI), возникает, если код использует другой тип FP, такой как float, long double или double с чем-то отличным от 53 двоичных битов точности. Это вопрос не столько точности, сколько иррационального / рационального.

Sine(x) near π

6 голосов
/ 06 октября 2009

@ Джош Келли - хорошо, серьезный ответ.
В общем, вы никогда не должны сравнивать результаты какой-либо операции с плавающими или двойными числами друг с другом.

Единственное исключение - это присвоение.
float a = 10,0;
поплавок b = 10,0;
тогда a == b

В противном случае вам всегда нужно написать какую-то функцию, такую ​​как bool IsClose (float a, float b, float error), чтобы вы могли проверить, находятся ли два числа в пределах «error» друг от друга.
Не забудьте также проверить знаки / использовать fabs - вы можете иметь -1.224647e-16

1 голос
/ 07 октября 2009

Есть два источника ошибок. Функция sin () и приблизительное значение M_PI. Даже если функция sin () была бы «совершенной», она не вернула бы ноль, если бы значение M_PI также не было идеальным - что не так.

0 голосов
/ 16 августа 2015

Возможно, слишком низкая точность реализации

M_PI = 3.14159265358979323846 (20 digits)

http://fresh2refresh.com/c/c-function/c-math-h-library-functions/

0 голосов
/ 07 октября 2009

Если ваша программа не требует значащих цифр до 16-го знака после запятой, вы, вероятно, можете выполнить округление вручную. Исходя из моего опыта программирования игр, мы всегда округляли наши десятичные дроби до приемлемых значащих цифр. Например:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define HALF 0.5
#define GREATER_EQUAL_HALF(X) (X) >= HALF

double const M_PI = 2 * acos(0.0);

double round(double val, unsigned  places = 1) 
{
    val = val * pow(10.0f, (float)places);
    long longval = (long)val;
    if ( GREATER_EQUAL_HALF(val - longval) ) {
       return ceil(val) / pow(10.0f, (float)places);
    } else {
      return floor(val) / pow(10.0f, (float)places);
    }
}

int main() 
{
    printf("\nValue %lf", round(sin(M_PI), 10));
    return 0;
}
0 голосов
/ 06 октября 2009

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

Я бы решил проблему, изменив строку формата на "% f \ n":)

Однако, это дает вам "лучший" результат, или, по крайней мере, в моей системе это дает -3.661369e-245

#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[]) {
    printf("%e\n", (long double)sin(M_PI));
    return 0;
}
0 голосов
/ 06 октября 2009

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

...