【问题标题】:Calculating sin function with JAVA BigDecimal -monomial is going bigger(?)用 JAVA BigDecimal -monomial 计算 sin 函数会变大(?)
【发布时间】:2020-09-09 12:52:20
【问题描述】:

我正在用 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 by result = result.add(num)

所以新的和运行良好的代码是:

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);
        
    }

}

【问题讨论】:

    标签: java math trigonometry


    【解决方案1】:
    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 - 这很容易检查),最后将它们放在一起。

    这是一种更好的编写代码的方式,而不是“全部编写,运行它,意识到它不起作用,耸耸肩,扬起眉毛,转向堆栈溢出,然后祈祷某人的水晶球有一个美好的一天,他们看到了我的问题。

    【讨论】:

    • 你写的很好的答案!优秀的东西!我特别喜欢最后两段,谈论编写更好的代码。尽管最后一段最好放在评论中;-) ...但我不是在这里判断的人,因为我自己已经写过很多次这样的段落。
    • 嗯,在一定程度上,但double 并不是“古怪”。浮点算术与真正的算术不同,奇怪的是对两者之间差异的天真误解,广泛存在于无数问题和答案中,可能是非常天真地未能理解存在 差异。当知道自己在做什么的人使用时,浮点运算确实非常有用。那些预测明天天气并试图弄清楚我们的密友 Covid-19 结构的超级计算机不是用 BigDecimals 来做的。
    • @HighPerformanceMark 确实如此,马克。我决定用 uh, 'wonky' 这个词来总结你的完全正确的批评:) - 就像代码中的大多数东西一样,没有什么是真正的'wonky',但事情是'不必要的复杂'或'对目标受众来说太难了理解”,当然这也适用于 IEEE 浮点的细微差别。
    • @rzwitserloot 似乎我犯了很多幼稚的错误! (这让我脸红了很多)我会修复代码并将其发布在问题中,以便它可以帮助其他人。
    【解决方案2】:

    这些术语都是否定的这一事实不是 问题(尽管您必须使其交替以获得正确的系列)。

    术语幅度是x^(2k+1) / (2k+1)!。分子确实在增长,但分母也在增长,在k = x 之后,分母开始“获胜”,级数总是收敛。

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

    对于正弦的计算,总是首先将参数减少到[0,π] 的范围内。更棒的是,如果联合开发一个余弦函数,可以归约到[0,π/2]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-18
      • 2016-08-13
      • 1970-01-01
      • 2014-06-06
      • 1970-01-01
      • 2021-10-25
      相关资源
      最近更新 更多