简体   繁体   English

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

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

I'm making sin function with BigDecimal in JAVA, and this is as far as I go:我正在用 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);
        
    }

}

It uses Taylor series : picture of the fomular它使用泰勒级数:公式的图片

The monomial x is added every iteration and always negative number.每次迭代都会添加单项式x并且总是负数。 And the problem is, absolute value of x is getting bigger and bigger, so iteration never ends.问题是, x绝对值越来越大,所以迭代永远不会结束。

Is there and way to find them or better way to implement it from the first place?有没有办法找到它们,或者从一开始就有更好的方法来实现它?

EDIT:编辑:
I made this code from scratch with simple interest about trigonometric functions, and now I see lots of childish mistakes.我出于对三角函数的简单兴趣从头开始编写了这段代码,现在我看到了很多幼稚的错误。

My intention first was like this:我的初衷是这样的:
num is x^(2k+1) / (2k+1)! numx^(2k+1) / (2k+1)!
divisor is (2k+1)! divisor(2k+1)!
i is 2k+1 i2k+1
dividend is x^(2k+1) dividendx^(2k+1)

So I update divisor and dividend with i and compute num by sign * dividend / divisor and add it to result by result = result.add(num)所以我用i更新divisordividend ,并通过sign * dividend / divisor计算num ,然后通过result = result.add(num)将其添加到result

so new and good-working code is:所以新的和运行良好的代码是:

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. The new BigDecimal(double) constructor is not something you generally want to be using; new BigDecimal(double)构造函数不是您通常想要使用的; the whole reason BigDecimal exists in the first place is that double is wonky: There are almost 2^64 unique values that a double can represent, but that's it - (almost) 2^64 distinct values, smeared out logarithmically, with about a quarter of all available numbers between 0 and 1, a quarter from 1 to infinity, and the other half the same but as negative numbers. BigDecimal 首先存在的全部原因是double是不稳定的:double 可以表示几乎 2^64 个唯一值,但仅此而已 -(几乎)2^64 个不同值,以对数方式涂抹,大约四分之一在 0 到 1 之间的所有可用数字中,从 1 到无穷大的四分之一,另一半相同但为负数。 3.14159265358979323846264 is not one of the blessed numbers. 3.14159265358979323846264不是受祝福的数字之一。 Use the string constructor instead - just toss " symbols around it.改用字符串构造函数 - 只需在它周围折腾"符号。

  2. every loop, sign should switch, well, sign.每个循环, sign应该切换,好吧,标志。 You're not doing that.你不这样做。

  3. In the first loop, you overwrite x with x = x.abs().multiply(x.abs()).multiply(x).multiply(sign);在第一个循环中,你用x = x.abs().multiply(x.abs()).multiply(x).multiply(sign);覆盖 x , so now the 'x' value is actually -x^3 , and the original x value is gone . ,所以现在 'x' 值实际上是-x^3 ,而原来的 x 值已经消失了 Next loop, you repeat this process, and thus you definitely are nowhere near the desired effect.下一个循环,您重复此过程,因此您肯定离想要的效果还差得很远。 The solution - don't overwrite x.解决方案 - 不要覆盖 x。 You need x, throughout the calculation.在整个计算过程中都需要 x。 Make it final ( getSin(final BigDecimal x) to help yourself.让它成为最终的getSin(final BigDecimal x)来帮助你自己。

Make another BigDecimal value and call it accumulator or what not.创建另一个 BigDecimal 值并将其称为累加器或其他名称。 It starts out as a copy of x.它最初是 x 的副本。

Every loop, you multiply x to it twice then toggle the sign.每个循环,您将 x 乘以两次,然后切换符号。 That way, the first time in the loop the accumulator is -x^3 .这样,循环中的第一次累加器是-x^3 The second time, it is x^5 .第二次,它是x^5 The third time it is -x^7 , and so on.第三次是-x^7 ,依此类推。

  1. There is more wrong, but at some point I'm just feeding you your homework on a golden spoon.还有更多错误,但在某些时候,我只是用金勺子喂你做作业。

I strongly suggest you learn to debug.我强烈建议你学习调试。 Debugging is simple!调试很简单! All you really do, is follow along with the computer.你真正要做的,就是跟着电脑走。 You calculate by hand and double check that what you get (be it the result of an expression, or whether a while loop loops or not), matches what the computer gets.您手动计算并仔细检查您得到的结果(无论是表达式的结果,还是 while 循环是否循环)与计算机得到的匹配。 Check by using a debugger, or if you don't know how to do that, learn, and if you don't want to, add a ton of System.out.println statements as debugging aids.使用调试器进行检查,或者如果您不知道如何去做,请学习,如果您不想,添加大量System.out.println语句作为调试帮助。 There where your expectations mismatch what the computer is doing?您的期望与计算机正在做什么不符? You found a bug.你发现了一个错误。 Probably one of many.可能是众多之一。

Then consider splicing parts of your code up so you can more easily check the computer's work.然后考虑拼接部分代码,以便您可以更轻松地检查计算机的工作。

For example, here, num is supposed to reflect:例如,在这里, num应该反映:

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

etcetera.等等。 But for debugging it'd be so much simpler if you have those parts separated out.但是对于调试来说,如果您将这些部分分开,就会简单得多。 You optimally want:您最希望:

  • first loop: 3 separated concepts: -1 , x^3 , and 3!第一个循环:3 个分离的概念: -1x^33! . .
  • second loop: +1 , x^5 , and 5!第二个循环: +1x^55! . .

That debugs so much simpler.这样调试就简单多了。

It also leads to cleaner code, generally, so I suggest you make these separate concepts as variables, describe them, write a loop and test that they are doing what you want (eg you use sysouts or a debugger to actually observe the power accumulator value hopping from x to x^3 to x^5 - this is easily checked), and finally put it all together.通常,它还可以使代码更清晰,因此我建议您将这些单独的概念作为变量,对其进行描述,编写一个循环并测试它们是否在执行您想要的操作(例如,您使用 sysouts 或调试器来实际观察功率累加器值从x跳到x^3再到x^5 - 这很容易检查),最后把它们放在一起。

This is a much better way to write code than to just 'write it all, run it, realize it doesn't work, shrug, raise an eyebrow, head over to stack overflow, and pray someone's crystal ball is having a good day and they see my question'.这是一种更好的编写代码的方式,而不是仅仅“写完所有内容,运行它,意识到它不起作用,耸耸肩,扬起眉毛,转向堆栈溢出,并祈祷某人的水晶球有美好的一天和他们看到我的问题'。

The fact that the terms are all negative is not the problem (though you must make it alternate to get the correct series).该条款都是负的事实是没有问题(尽管你必须让它替代以获得正确的系列)。

The term magnitude is x^(2k+1) / (2k+1)!项幅度是x^(2k+1) / (2k+1)! . . The numerator is indeed growing, but so is the denominator, and past k = x , the denominator starts to "win" and the series always converges.分子确实在增长,但分母也在增长,过去k = x ,分母开始“获胜”并且级数总是收敛。

Anyway, you should limit yourself to small x s, otherwise the computation will be extremely lengthy, with very large products.无论如何,您应该将自己限制在较小的x s,否则计算将非常冗长,并且产品非常大。

For the computation of the sine, always begin by reducing the argument to the range [0,π] .对于正弦的计算,始终首先将参数减少到范围[0,π] Even better, if you jointly develop a cosine function, you can reduce to [0,π/2] .更好的是,如果你联合开发一个余弦函数,你可以减少到[0,π/2]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM