Вступление
Я заинтересован в написании математических функций для BigDecimal
(на самом деле, также для моего собственного типа BigDecimal
, написанного на Delphi , но это здесь не имеет значения - в этом вопросе я использую BigDecimal
из Java, потому что его знает больше людей, и мой BigDecimal
очень похож. Приведенный ниже тестовый код написан на Java и отлично работает и одинаково хорошо работает в переводе Delphi).
Я знаю, что BigDecimal
не быстрый, но довольно точный. Я не хочу использовать какую-то существующую математическую библиотеку Java BigDecimal
, особенно потому, что она предназначена и для моего собственного типа BigDecimal
(в Delphi).
В качестве хорошего примера того, как реализовать триггерные функции, я нашел следующий простой пример (но я забыл где, извините). Очевидно, он использует ряд Маклаурина для вычисления косинуса 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 достаточно для дополнительной точности. Но мне нужно больше тестов, чтобы быть уверенным.
BigRational
в Delphi. Думаю, это тоже должно быть довольно точно. Но я буду помнить о символической (лениво оцениваемой) математике. - person Rudy Velthuis   schedule 20.08.2016