英文:
Calculating sin function with JAVA BigDecimal -monomial is going bigger(?)
问题
这是使用Java中的BigDecimal
计算sine
函数的代码,它使用了泰勒级数进行计算。以下是代码的翻译:
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;
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);
} while(num.abs().compareTo(new BigDecimal("0.1").pow(scale + cutOff)) > 0);
return result.setScale(scale, BigDecimal.ROUND_HALF_UP);
}
}
此代码使用泰勒级数来计算sine
函数。在代码中,x
是输入的值,result
是计算结果。它使用了循环来迭代计算,并根据精度要求截断结果。
最终的目标是计算sine
函数的近似值,但是这个代码存在一些问题,因为x
的绝对值在每次迭代中会变得越来越大,导致迭代无法结束。
作者在后来的编辑中进行了修正,改进了计算方法。新的代码使用了更好的方式来计算sine
函数,解决了之前版本中的问题。
希望这个翻译对您有所帮助。如果您有其他问题,请随时提问。
英文:
I'm making sin
function with BigDecimal
in JAVA, and this is as far as I go:
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.
And the problem is, absolute value of x
is getting bigger and bigger, so iteration never ends.
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)!
divisor
is (2k+1)!
i
is 2k+1
dividend
is x^(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)
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
得分: 3
-
new BigDecimal(double)
构造函数通常不是你想使用的东西;BigDecimal存在的主要原因是double
不稳定:double
能够表示近似2^64个独特值,但仅限于这些 - 几乎2^64个不同的值,以对数方式分布,约四分之一的可用数字在0到1之间,四分之一在1到无穷大之间,另一半相同,但为负数。3.14159265358979323846264
不是其中一个受保护的数字。使用字符串构造函数代替 - 只需在其周围添加"
符号。 -
每次循环,
sign
应该切换符号。你没有这样做。 -
在第一个循环中,你使用
x = x.abs().multiply(x.abs()).multiply(x).multiply(sign);
重写了x,所以现在x
的值实际上是-x^3
,而原始的x值消失了。下一个循环,你重复这个过程,因此你肯定与期望的效果相去甚远。解决方法 - 不要重写x。你需要始终保留x,在整个计算过程中。将其声明为final
(getSin(final BigDecimal x)
)可以帮助你自己。
创建另一个BigDecimal值,称其为累加器或其他名称。它最初是x的一个副本。
每次循环,你将x乘以它两次,然后切换符号。这样,在循环中的第一次,累加器为-x^3
。第二次,它为x^5
。第三次为-x^7
,依此类推。
- 存在更多问题,但在某一点上,我只是在用金色的匙子喂你做作业。
我强烈建议你学会调试。调试很简单!你所要做的就是跟随计算机的执行过程。你可以手动计算,并仔细检查你得到的结果(无论是表达式的结果,还是while循环是否循环等),是否与计算机得到的结果相匹配。可以使用调试器来检查,如果你不知道如何使用调试器,就学习一下;如果你不想学,那就添加大量的System.out.println
语句作为调试辅助工具。当你的预期与计算机的实际执行不一致时,你就发现了一个bug。可能是众多bug中的一个。
然后,考虑将代码的各个部分拆分开,以便更轻松地检查计算机的工作情况。
例如,在这里,num
应该反映出:
- 在第一个循环之前:
x
- 第一个循环:
x - x^3/3!
- 第二个循环:
x - x^3/3! + x^5/5!
等等。但是为了调试,如果你将这些部分分开会更简单。你最好有:
- 第一个循环:3个独立的概念:
-1
,x^3
和3!
。 - 第二个循环:
+1
,x^5
和5!
。
这样调试会简单得多。
这还会导致代码更清晰,因此我建议你将这些独立的概念作为变量进行定义,对它们进行描述,编写一个循环并测试它们是否按照预期工作(例如,你可以使用sysouts或调试器来实际观察幂累加器的值是如何从x
跳到x^3
再到x^5
的 - 这很容易检查),最后将所有东西组合在一起。
这比只是"把所有东西写出来,运行一下,意识到不起作用,耸耸肩,扬起眉毛,转向Stack Overflow,祈祷有人的水晶球今天状态不错,他们能看到我的问题"要好得多。
英文:
-
The
new BigDecimal(double)
constructor is not something you generally want to be using; the whole reason BigDecimal exists in the first place is thatdouble
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.3.14159265358979323846264
is not one of the blessed numbers. Use the string constructor instead - just toss"
symbols around it. -
every loop,
sign
should switch, well, sign. You're not doing that. -
In the first loop, you overwrite x with
x = x.abs().multiply(x.abs()).multiply(x).multiply(sign);
, so now the 'x' value is actually-x^3
, and the original x value is gone. Next loop, you repeat this process, and thus you definitely are nowhere near the desired effect. The solution - don't overwrite x. You need x, throughout the calculation. Make it final (getSin(final BigDecimal x)
to help yourself.
Make another BigDecimal value and call it accumulator or what not. It starts out as a copy of x.
Every loop, you multiply x to it twice then toggle the sign. That way, the first time in the loop the accumulator is -x^3
. The second time, it is x^5
. The third time it is -x^7
, and so on.
- 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. 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. 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:
- before first loop:
x
- first loop:
x - x^3/3!
- second loop:
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
, and3!
. - second loop:
+1
,x^5
, and5!
.
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 (e.g. 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.
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'.
答案2
得分: 0
代码部分不要翻译,只返回翻译好的部分:
所有术语都是负数并不是问题(尽管你必须使其交替以获得正确的序列)。
术语的大小为 x^(2k+1) / (2k+1)!
。分子的确在增加,但分母也在增加,过了 k = x
,分母开始“占上风”,序列总是收敛的。
无论如何,你应该将自己限制在小的 x
值范围内,否则计算会非常冗长,产生非常大的乘积。
对于计算正弦值,始终从将参数缩小到范围 [0,π]
开始。甚至更好的是,如果你同时开发一个余弦函数,可以缩小到 [0,π/2]
范围内。
英文:
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)!
. The numerator is indeed growing, but so is the denominator, and past k = x
, the denominator starts to "win" and the series always converges.
Anyway, you should limit yourself to small x
s, otherwise the computation will be extremely lengthy, with very large products.
For the computation of the sine, always begin by reducing the argument to the range [0,π]
. Even better, if you jointly develop a cosine function, you can reduce to [0,π/2]
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论