Точность Math.Sin () и Math.Cos () в C # - PullRequest
7 голосов
/ 14 июля 2010

Меня ужасно раздражает неточность встроенных тригонометрических функций в CLR.Хорошо известно, что

Math.Sin(Math.PI)=0.00000000000000012246063538223773

вместо 0. Нечто подобное происходит и с Math.Cos(Math.PI/2).

Но когда я выполняю длинную серию вычислений, в особых случаях оцениваю как

Math.Sin(Math.PI/2+x)-Math.Cos(x)

и результат равен нулю для x = 0.2, но не равен нулю для x = 0.1Это).Другая проблема заключается в том, что когда аргумент является большим числом, неточность становится пропорционально большой.

Так что мне интересно, если кто-то кодировал какое-то лучшее представление функций триггера в C # для обмена с миром.CLR вызывает некоторую стандартную C математическую библиотеку, реализующую CORDIC или что-то подобное?ссылка: Википедия CORDIC

Ответы [ 6 ]

18 голосов
/ 14 июля 2010

Это не имеет ничего общего с точностью тригонометрических функций, но больше связано с системой типов CLS. Согласно документации, double имеет точность 15-16 цифр (что и получается), поэтому вы не можете быть более точным с этим типом. Поэтому, если вы хотите большей точности, вам нужно создать новый тип, способный его хранить.

Также обратите внимание, что вы никогда не должны писать код, подобный этому:

double d = CalcFromSomewhere();
if (d == 0)
{
    DoSomething();
}

Вы должны сделать вместо:

double d = CalcFromSomewhere();
double epsilon = 1e-5; // define the precision you are working with
if (Math.Abs(d) < epsilon)
{
    DoSomething();
}
9 голосов
/ 15 июля 2010

Я вас слышу.Меня ужасно раздражает неточность деления.На днях я сделал:

Console.WriteLine(1.0 / 3.0);

, и вместо правильного ответа 0 ...

я получил 0,333333333333333, возможно, теперь вы видите, в чем проблема. Math.Pi не равно пи больше 1,0 / 3,0 равно одной трети.Оба они отличаются от истинного значения на несколько сотен квадриллионов, и поэтому любые вычисления, которые вы выполняете с помощью Math.Pi или 1.0 / 3.0, также будут отклоняться на несколько сотен квадриллионов, включая принятие синуса.

Если вам не нравится, что приблизительная арифметика равна приблизительная , тогда не используйте приблизительную арифметику.Используйте точную арифметику.Я использовал Ватерлоо Maple, когда мне нужна была точная арифметика;возможно, вам стоит купить копию этого.

6 голосов
/ 14 июля 2010

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

Лучшее промежуточное объяснение - «Что должен знать каждый ученый об арифметике с плавающей точкой» .Если вы не хотите вдаваться в подробности, просто помните, что числа с плавающей точкой обычно являются приблизительными, а вычисления с плавающей точкой похожи на движущиеся груды песка на земле: со всем, что вы делаете с ними, вы теряете немного песка ивозьмите немного грязи.

Если вы хотите получить точное представление, вам нужно найти себе систему символической алгебры.

2 голосов
/ 15 июля 2010

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

Доступно несколько популярных:

1 голос
/ 18 мая 2018

Наша текущая реализация синусов и косинусов

    public static double Sin(double d) {
        d = d % (2 * Math.PI); // Math.Sin calculates wrong results for values larger than 1e6
        if (d == 0 || d == Math.PI || d == -Math.PI) {
            return 0.0;
        }
        else {
            return Math.Sin(d);
        }
    }

    public static double Cos(double d) {
        d = d % (2 * Math.PI); // Math.Cos calculates wrong results for values larger than 1e6
        double multipleOfPi = d / Math.PI; // avoid calling the expensive modulo function twice
        if (multipleOfPi == 0.5 || multipleOfPi == -0.5 || multipleOfPi == 1.5 || multipleOfPi == -1.5) { 
            return 0.0;
        }
        else {
            return Math.Cos(d);
        }
    }
1 голос
/ 27 июня 2012

Я отвергаю идею, что ошибки связаны с округлением.Что можно сделать, это определить sin(x) следующим образом, используя расширение Тейлора с 6 терминами:

    const double π=Math.PI;
    const double π2=Math.PI/2;
    const double π4=Math.PI/4;

    public static double Sin(double x)
    {

        if (x==0) { return 0; }
        if (x<0) { return -Sin(-x); }
        if (x>π) { return -Sin(x-π); }
        if (x>π4) { return Cos(π2-x); }

        double x2=x*x;

        return x*(x2/6*(x2/20*(x2/42*(x2/72*(x2/110*(x2/156-1)+1)-1)+1)-1)+1);
    }

    public static double Cos(double x)
    {
        if (x==0) { return 1; }
        if (x<0) { return Cos(-x); }
        if (x>π) { return -Cos(x-π); }
        if (x>π4) { return Sin(π2-x); }

        double x2=x*x;

        return x2/2*(x2/12*(x2/30*(x2/56*(x2/90*(x2/132-1)+1)-1)+1)-1)+1;
    }

Типичная ошибка - 1e-16, а худший случай - 1e-11Это хуже, чем CLR, но его можно контролировать, добавляя больше терминов.Хорошей новостью является то, что для особых случаев в ОП и для Sin(45°) ответ точен.

...