使用 JAVA BigDecimal -monomial 计算 sin 函数会变大(?)

Calculating sin function with JAVA BigDecimal -monomial is going bigger(?)

我在 JAVA 中用 BigDecimal 制作 sin 函数,这是我目前为止的情况:

package taylorSeries;

import java.math.BigDecimal;

public class Sin {

    private static final int cutOff = 20;
    
    public static void main(String[] args) {

        System.out.println(getSin(new BigDecimal(3.14159265358979323846264), 100));
        
    }

    public static BigDecimal getSin(BigDecimal x, int scale) {
        
        BigDecimal sign = new BigDecimal("-1");
        BigDecimal divisor = BigDecimal.ONE;
        BigDecimal i = BigDecimal.ONE;
        BigDecimal num = null;
        
        BigDecimal result = x;
        //System.err.println(x);
        do {
            
            x = x.abs().multiply(x.abs()).multiply(x).multiply(sign);
            
            i = i.add(BigDecimal.ONE);
            divisor = divisor.multiply(i);
            i = i.add(BigDecimal.ONE);
            divisor = divisor.multiply(i);
            
            num = x.divide(divisor, scale + cutOff, BigDecimal.ROUND_HALF_UP);
            
            result = result.add(num);
            //System.out.println("d : " + divisor);
            //System.out.println(divisor.compareTo(x.abs()));
            System.out.println(num.setScale(9, BigDecimal.ROUND_HALF_UP));
        } while(num.abs().compareTo(new BigDecimal("0.1").pow(scale + cutOff)) > 0);
        
        System.err.println(num);
        System.err.println(new BigDecimal("0.1").pow(scale + cutOff));
        return result.setScale(scale, BigDecimal.ROUND_HALF_UP);
        
    }

}

它使用泰勒级数: picture of the fomular

单项式 x 每次迭代都会添加,并且始终为负数。 问题是,x 的绝对值越来越大,所以迭代永远不会结束。

是否有找到它们的方法或更好的方法从一开始就实施它?

编辑:
我出于对三角函数的简单兴趣从头开始编写这段代码,现在我看到了很多幼稚的错误。

我的初衷是这样的:
numx^(2k+1) / (2k+1)!
divisor(2k+1)!
i2k+1
dividendx^(2k+1)

所以我用 i 更新 divisordividend 并用 sign * dividend / divisor 计算 num 并用 [= 添加到 result 31=]

如此新的好用代码是:

package taylorSeries;

import java.math.BigDecimal;
import java.math.MathContext;

public class Sin {

    private static final int cutOff = 20;
    private static final BigDecimal PI = Pi.getPi(100);
    
    public static void main(String[] args) {

        System.out.println(getSin(Pi.getPi(100).multiply(new BigDecimal("1.5")), 100)); // Should be -1

    }

    public static BigDecimal getSin(final BigDecimal x, int scale) {
        
        if (x.compareTo(PI.multiply(new BigDecimal(2))) > 0) return getSin(x.remainder(PI.multiply(new BigDecimal(2)), new MathContext(x.precision())), scale);
        if (x.compareTo(PI) > 0) return getSin(x.subtract(PI), scale).multiply(new BigDecimal("-1"));
        if (x.compareTo(PI.divide(new BigDecimal(2))) > 0) return getSin(PI.subtract(x), scale);
        
        BigDecimal sign = new BigDecimal("-1");
        BigDecimal divisor = BigDecimal.ONE;
        BigDecimal i = BigDecimal.ONE;
        BigDecimal num = null;
        BigDecimal dividend = x;
        BigDecimal result = dividend;

        do {
            
            dividend = dividend.multiply(x).multiply(x).multiply(sign);
            
            i = i.add(BigDecimal.ONE);
            divisor = divisor.multiply(i);
            i = i.add(BigDecimal.ONE);
            divisor = divisor.multiply(i);
            
            num = dividend.divide(divisor, scale + cutOff, BigDecimal.ROUND_HALF_UP);
            
            result = result.add(num);

        } while(num.abs().compareTo(new BigDecimal("0.1").pow(scale + cutOff)) > 0);
        
        return result.setScale(scale, BigDecimal.ROUND_HALF_UP);
        
    }

}

  1. new BigDecimal(double) 构造函数通常不是您想要使用的东西; BigDecimal 存在的全部原因首先是 double 是不稳定的:双精度可以表示几乎 2^64 个唯一值,但仅此而已 - (几乎)2^64 个不同的值,以对数方式涂抹,大约四分之一的所有可用数字介于 0 和 1 之间,四分之一从 1 到无穷大,另一半相同但为负数。 3.14159265358979323846264 不是幸运数字之一。请改用字符串构造函数 - 只需在其周围添加 " 个符号即可。

  2. 每个循环,sign应该切换,嗯,签名。你没有那样做。

  3. 在第一个循环中,你用x = x.abs().multiply(x.abs()).multiply(x).multiply(sign);覆盖了x,所以现在'x'的值实际上是-x^3,而原来的x值是走了。下一个循环,你重复这个过程,这样你肯定离你想要的效果还差得很远。解决方案 - 不要覆盖 x。在整个计算过程中,您需要 x。 定稿getSin(final BigDecimal x)帮助自己。

创建另一个 BigDecimal 值并将其称为累加器或其他名称。它以 x 的副本开始。

每个循环,你将 x 乘以它两次然后切换符号。这样,循环中的第一次累加器是 -x^3。第二次,是x^5。第三次是-x^7,以此类推

  1. 还有更多的错误,但在某些时候我只是用金汤匙喂你做作业。

我强烈建议你学会调试。调试很简单!您真正要做的就是跟随计算机。您手动计算并仔细检查您得到的结果(无论是表达式的结果,还是 while 循环是否循环)与计算机得到的结果相匹配。使用调试器进行检查,或者如果您不知道如何操作,请学习,如果您不想,请添加大量 System.out.println 语句作为调试辅助工具。您的期望与计算机正在做什么不匹配?你发现了一个错误。可能是其中之一。

然后考虑拼接部分代码,这样您就可以更轻松地检查计算机的工作。

例如这里的num应该反映:

  • 在第一个循环之前:x
  • 第一个循环:x - x^3/3!
  • 第二个循环:x - x^3/3! + x^5/5!

等等。但是对于调试来说,如果你把这些部分分开,就会简单得多。您最希望:

  • 第一个循环:3 个独立的概念:-1x^33!
  • 第二个循环:+1x^55!

这样调试就简单多了。

通常,它还会导致更清晰的代码,所以我建议您将这些单独的概念作为变量,描述它们,编写一个循环并测试它们是否按照您的意愿执行(例如,您使用 sysouts 或调试器来实际观察功率累加器值从 x 跳跃到 x^3 再到 x^5 - 这很容易检查),最后将它们放在一起。

这种编写代码的方式比“全部写出来”要好得多,运行 它意识到它不起作用,耸耸肩,扬起眉毛,转向堆栈溢出,并祈祷某人的 crystal 球今天很开心,他们看到了我的问题'。

项都是负数的事实并不是问题(尽管您必须交替使用才能获得正确的系列)。

术语量级是 x^(2k+1) / (2k+1)!。分子确实在增长,但分母也在增长,过去k = x,分母开始“赢”,级数总是收敛

无论如何,你应该将自己限制在较小的 xs 内,否则计算将非常冗长,产品非常大。

对于正弦的计算,始终从将参数减少到范围 [0,π] 开始。更好的是,如果你们共同开发一个余弦函数,可以减少到 [0,π/2].