使用 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
的绝对值越来越大,所以迭代永远不会结束。
是否有找到它们的方法或更好的方法从一开始就实施它?
编辑:
我出于对三角函数的简单兴趣从头开始编写这段代码,现在我看到了很多幼稚的错误。
我的初衷是这样的:
num
是 x^(2k+1) / (2k+1)!
divisor
是 (2k+1)!
i
是 2k+1
dividend
是 x^(2k+1)
所以我用 i
更新 divisor
和 dividend
并用 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);
}
}
new BigDecimal(double)
构造函数通常不是您想要使用的东西; BigDecimal 存在的全部原因首先是 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。 定稿(getSin(final BigDecimal x)
帮助自己。
创建另一个 BigDecimal 值并将其称为累加器或其他名称。它以 x 的副本开始。
每个循环,你将 x 乘以它两次然后切换符号。这样,循环中的第一次累加器是 -x^3
。第二次,是x^5
。第三次是-x^7
,以此类推
- 还有更多的错误,但在某些时候我只是用金汤匙喂你做作业。
我强烈建议你学会调试。调试很简单!您真正要做的就是跟随计算机。您手动计算并仔细检查您得到的结果(无论是表达式的结果,还是 while 循环是否循环)与计算机得到的结果相匹配。使用调试器进行检查,或者如果您不知道如何操作,请学习,如果您不想,请添加大量 System.out.println
语句作为调试辅助工具。您的期望与计算机正在做什么不匹配?你发现了一个错误。可能是其中之一。
然后考虑拼接部分代码,这样您就可以更轻松地检查计算机的工作。
例如这里的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
- 这很容易检查),最后将它们放在一起。
这种编写代码的方式比“全部写出来”要好得多,运行 它意识到它不起作用,耸耸肩,扬起眉毛,转向堆栈溢出,并祈祷某人的 crystal 球今天很开心,他们看到了我的问题'。
项都是负数的事实并不是问题(尽管您必须交替使用才能获得正确的系列)。
术语量级是 x^(2k+1) / (2k+1)!
。分子确实在增长,但分母也在增长,过去k = x
,分母开始“赢”,级数总是收敛
无论如何,你应该将自己限制在较小的 x
s 内,否则计算将非常冗长,产品非常大。
对于正弦的计算,始终从将参数减少到范围 [0,π]
开始。更好的是,如果你们共同开发一个余弦函数,可以减少到 [0,π/2]
.
我在 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
的绝对值越来越大,所以迭代永远不会结束。
是否有找到它们的方法或更好的方法从一开始就实施它?
编辑:
我出于对三角函数的简单兴趣从头开始编写这段代码,现在我看到了很多幼稚的错误。
我的初衷是这样的:
num
是 x^(2k+1) / (2k+1)!
divisor
是 (2k+1)!
i
是 2k+1
dividend
是 x^(2k+1)
所以我用 i
更新 divisor
和 dividend
并用 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);
}
}
new BigDecimal(double)
构造函数通常不是您想要使用的东西; BigDecimal 存在的全部原因首先是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。 定稿(getSin(final BigDecimal x)
帮助自己。
创建另一个 BigDecimal 值并将其称为累加器或其他名称。它以 x 的副本开始。
每个循环,你将 x 乘以它两次然后切换符号。这样,循环中的第一次累加器是 -x^3
。第二次,是x^5
。第三次是-x^7
,以此类推
- 还有更多的错误,但在某些时候我只是用金汤匙喂你做作业。
我强烈建议你学会调试。调试很简单!您真正要做的就是跟随计算机。您手动计算并仔细检查您得到的结果(无论是表达式的结果,还是 while 循环是否循环)与计算机得到的结果相匹配。使用调试器进行检查,或者如果您不知道如何操作,请学习,如果您不想,请添加大量 System.out.println
语句作为调试辅助工具。您的期望与计算机正在做什么不匹配?你发现了一个错误。可能是其中之一。
然后考虑拼接部分代码,这样您就可以更轻松地检查计算机的工作。
例如这里的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
- 这很容易检查),最后将它们放在一起。
这种编写代码的方式比“全部写出来”要好得多,运行 它意识到它不起作用,耸耸肩,扬起眉毛,转向堆栈溢出,并祈祷某人的 crystal 球今天很开心,他们看到了我的问题'。
项都是负数的事实并不是问题(尽管您必须交替使用才能获得正确的系列)。
术语量级是 x^(2k+1) / (2k+1)!
。分子确实在增长,但分母也在增长,过去k = x
,分母开始“赢”,级数总是收敛
无论如何,你应该将自己限制在较小的 x
s 内,否则计算将非常冗长,产品非常大。
对于正弦的计算,始终从将参数减少到范围 [0,π]
开始。更好的是,如果你们共同开发一个余弦函数,可以减少到 [0,π/2]
.