Для реализации триггерных функций с помощью BigDecimal требуется дополнительная точность.

Вступление

Я заинтересован в написании математических функций для BigDecimal (на самом деле, также для моего собственного типа BigDecimal, написанного на Delphi , но это здесь не имеет значения - в этом вопросе я использую BigDecimal из Java, потому что его знает больше людей, и мой BigDecimal очень похож. Приведенный ниже тестовый код написан на Java и отлично работает и одинаково хорошо работает в переводе Delphi).

Я знаю, что BigDecimal не быстрый, но довольно точный. Я не хочу использовать какую-то существующую математическую библиотеку Java BigDecimal, особенно потому, что она предназначена и для моего собственного типа BigDecimalDelphi).

В качестве хорошего примера того, как реализовать триггерные функции, я нашел следующий простой пример (но я забыл где, извините). Очевидно, он использует ряд Маклаурина для вычисления косинуса BigDecimal с заданной точностью.

Вопрос

Эта точность и есть моя проблема. Код ниже использует дополнительную точность 5 для вычисления результата, и только в конце он округляет его до желаемой точности.

У меня есть ощущение, что дополнительная точность 5 подходит, скажем, для целевой точности до 50 или даже немного больше, но не для BigDecimals с гораздо более высокой точностью (скажем, 1000 цифр или больше). К сожалению, я не смог найти способ проверить это (например, с помощью чрезвычайно точного онлайн-калькулятора).

Наконец, мой вопрос: прав ли я — что 5, вероятно, недостаточно для больших чисел — и если да, как я могу рассчитать или оценить требуемую дополнительную точность?


Пример кода вычисляет cos(BigDecimal):

public class BigDecimalTrigTest 
{
    private List _trigFactors;
    private int _precision;
    private final int _extraPrecision = 5; // Question: is 5 enough?

    public BigDecimalTrigTest(int precision) 
    {
        _precision = precision;
        _trigFactors = new Vector();
        BigDecimal one = new BigDecimal("1.0");
        BigDecimal stopWhen = one.movePointLeft(precision + _extraPrecision);
        System.out.format("stopWhen = %s\n", stopWhen.toString());
        BigDecimal factorial = new BigDecimal(2.0);
        BigDecimal inc = new BigDecimal(2.0);
        BigDecimal factor = null;
        do 
        {
            factor = one.divide(factorial, precision + _extraPrecision,
                    BigDecimal.ROUND_HALF_UP);            // factor = 1/factorial
            _trigFactors.add(factor);
            inc = inc.add(one);                           // factorial = factorial * (factorial + 1)   
            factorial = factorial.multiply(inc);
            inc = inc.add(one);                           // factorial = factorial * (factorial + 1)  
            factorial = factorial.multiply(inc);
        } while (factor.compareTo(stopWhen) > 0);
    }

    // sin(x) = x - x^3/3! + x^5/5! - x^7/7! + x^9/9! - ... = Sum[0..+inf] (-1^n) * (x^(2*n + 1)) / (2*n + 1)!
    // cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + x^8/8! - ... = Sum[0..+inf] (-1^n) * (x^(2*n)) / (2*n)!

    public BigDecimal cos(BigDecimal x) 
    {
        BigDecimal res = new BigDecimal("1.0");
        BigDecimal xn = x.multiply(x);
        for (int i = 0; i < _trigFactors.size(); i++) 
        {
            BigDecimal factor = (BigDecimal) _trigFactors.get(i);
            factor = factor.multiply(xn);
            if (i % 2 == 0) 
            {
                factor = factor.negate();
            }
            res = res.add(factor);
            xn = xn.multiply(x);
            xn = xn.multiply(x);
            xn = xn.setScale(_precision + _extraPrecision, BigDecimal.ROUND_HALF_UP);
        }
        return res.setScale(_precision, BigDecimal.ROUND_HALF_UP);
    }

    public static void main(String[] args) 
    {
        BigDecimalTrigTest bdtt = new BigDecimalTrigTest(50);
        BigDecimal half = new BigDecimal("0.5");

        System.out.println("Math.cos(0.5) = " + Math.cos(0.5));
        System.out.println("this.cos(0.5) = " + bdtt.cos(half));
    }

}

Обновлять

Тест с Wolfram Alpha для cos(.5) to 10000 digits (как прокомментировал @RC) дает тот же результат, что и мой тестовый код, с той же точностью. Возможно, 5 достаточно для дополнительной точности. Но мне нужно больше тестов, чтобы быть уверенным.


person Rudy Velthuis    schedule 20.08.2016    source источник
comment
wolfram alpha довольно точен для cos, см. wolframalpha.com /input/?i=cos(12)+to+1000+цифр   -  person    schedule 20.08.2016
comment
А, спасибо, попробую проверить свои результаты с Wolfram Alpha. Хороший совет!   -  person Rudy Velthuis    schedule 20.08.2016
comment
Исключительно идея: если вы выполняете символьные вычисления, вы можете лениво оценивать (бесконечные) серии, комбинировать их, иметь точность ошибки для каждой серии и, возможно, получать более быстрые результаты. Использование лямбда-выражений Java 8.   -  person Joop Eggen    schedule 20.08.2016
comment
Хммм... wolframalpha.com/input/?i=cos(0.5)+to+1000+digits (и настройка радианов) дает мне точно такой же вывод, как и мой тестовый код с точностью 1000, поэтому в этом примере достаточно 5 . Нужно попробовать еще больше цифр и много разных значений. Я предполагаю, что входные значения также не должны быть слишком далеки от 0.   -  person Rudy Velthuis    schedule 20.08.2016
comment
@Joop: Спасибо за предложение, но, как я уже писал, это также должно быть переведено в Delphi и использовать BigDecimal.   -  person Rudy Velthuis    schedule 20.08.2016
comment
FWIW, результаты одинаковы и для 10 000 цифр. Я не ожидал этого.   -  person Rudy Velthuis    schedule 20.08.2016
comment
@Joop: один из моих следующих шагов — реализация BigRational в Delphi. Думаю, это тоже должно быть довольно точно. Но я буду помнить о символической (лениво оцениваемой) математике.   -  person Rudy Velthuis    schedule 20.08.2016


Ответы (1)


Вы можете уменьшить числа не из -pi>x>=pi до этого диапазона. Разложение Тейлора для sin(x) становится менее точным по мере увеличения abs(x), поэтому уменьшение x до этого диапазона повысит точность для больших чисел.

person Deniz Olgun    schedule 20.12.2016
comment
Я знаю это. Но даже тогда я все еще не знаю, достаточно ли, скажем, 5 лишних цифр. То же самое для вычисления числа пи. Чтобы уменьшить значение до этого диапазона, у меня должно быть по крайней мере такое же точное значение числа пи. - person Rudy Velthuis; 20.12.2016
comment
вы можете попробовать преобразовать в градусы, скорректировать диапазон, а затем преобразовать обратно в радианы, но это само по себе может привести к некоторым ошибкам исправления. - person Deniz Olgun; 21.12.2016
comment
Чтобы получить точность, преобразование в градусы по-прежнему потребует пи той же точности. Таким образом, для точности, скажем, 10000, мне также нужно было бы иметь число пи в этом диапазоне (точность плюс 5 — или что-то еще — просто для уверенности). - person Rudy Velthuis; 21.12.2016