【问题标题】:How do you get accurate results for sine values with BigDecimal in Java?如何在 Java 中使用 BigDecimal 获得正弦值的准确结果?
【发布时间】:2019-11-24 23:40:19
【问题描述】:

我的目标是生成准确的正弦波。我的问题是,当我使用 BigDecimal 和 StrictMath 生成值时,一些过零错误并且对称性被破坏。

这是一个频率为 1、相位为 0、幅度为 1、时间为 1 秒、采样率为 10 的数组(我将在本文后面发布代码):

>[0]    0.0

>[1]    0.5877852522924731  
[2] 0.9510565162951535  
[3] 0.9510565162951536  
[4] 0.5877852522924732  
[5] 1.2246467991473532E-16  
[6] -0.587785252292473  
[7] -0.9510565162951535 

>[8]    -0.9510565162951536 

>[9]    -0.5877852522924734 

为了准确度,[5] 不应该为 0 吗?不应该 (4 = 1) 以及 (2 = 3)、(9 = 6) 和 (7 = 8) 吗?

在第 2 种情况下,相位等于 StrictMath.PI/2.0 似乎会在 [5] 处产生准确度:

>[0]    1.0 

>[1]    0.8090169943749475  
[2] 0.3090169943749475  
[3] -0.3090169943749473 

>[4]    -0.8090169943749473 

>[5]    -1.0    
[6] -0.8090169943749476 

>[7]    -0.3090169943749476 

>[8]    0.3090169943749472  
[9] 0.8090169943749472  

在这种情况下,起点不太准确,[5] 更准确,但同样,不应该 (-4 = 1) 以及 (-2 = 3),(-9 = 6 ) 和 (-7 = 8)?

所以我的问题是为什么会这样?为什么零交叉是错误的,但 1 和 -1 交叉是正确的?为什么正弦对称性被破坏了?

这是我生成值的代码:


    package Wave;

    import java.math.BigDecimal;

    /**
     * @author Alexander Johnston
     * Copyright 2019
     * A class for sine waves
     */
    public class SineWave extends Wave {

        /** Creates a sine wave
         * @param a as the amplitude of the sin wave from -amplitude to amplitude
         * @param f as the frequency of the sine wave in Hz
         * @param p as the phase of the sine wave
         */
        public SineWave(BigDecimal a, BigDecimal f, BigDecimal p) {
            this.a = a;
            this.f = f;
            this.p = p;
        }

        /* (non-Javadoc)
         * @see waves.Wave#getSample(BigDecimal, float)
         */
        public double[] getSample(BigDecimal t, float sr) {
            int nsd;
            BigDecimal nsdp = (new BigDecimal(Float.toString(sr)).multiply(t));
            if(nsdp.compareTo(new BigDecimal(Integer.MAX_VALUE)) == -1) {
                nsd = nsdp.intValue();
                } else {
                    System.out.print("wave time is too long to fit in an array");
                    return null;
                }
            double[] w = new double[nsd];
            for(int i = 0; i < w.length; i++) {
                w[i] = a.multiply(new BigDecimal(StrictMath.sin(((new BigDecimal(2.0).multiply(new BigDecimal(StrictMath.PI)).multiply(f).multiply(new BigDecimal(i)).divide((new BigDecimal(Float.toString(sr))))).add(p)).doubleValue()))).doubleValue();
            }
            p = p.add(new BigDecimal(2.0).multiply(new BigDecimal(StrictMath.PI).multiply(f).multiply(t)));
            return w;
        }
    }


    The wave class:

    package Wave;

    import java.math.BigDecimal;

    /**
     * @author Alexander Johnston
     * Copyright 2019
     * A class for waves to extend
     */
    public abstract class Wave {

        // Amplitude of the wave
        protected BigDecimal a;

        // Frequency of the wave in Hz
        protected BigDecimal f;

        // Phase of the wave, between 0 and (2*Math.PI)
        protected BigDecimal p;

        /** Generates a wave with with the correct amplitude
         * @param t as the length of the wave in seconds
         * @return An array with the wave generated with specified amplitude as amplitude over time
         */
        abstract public double[] getSample(BigDecimal t, float sr);

        }

及主要方法:


    import java.math.BigDecimal;
    import Wave.SineWave;

    public class main {

        public static void main(String[] args) {
            BigDecimal a = new BigDecimal(1.0); 
            BigDecimal f = new BigDecimal(1.0); 
            BigDecimal p = new BigDecimal(0.0);
            SineWave sw = new SineWave(a, f, p);        
            p = new BigDecimal(StrictMath.PI).divide(new BigDecimal(2.0));
            SineWave swps = new SineWave(a, f, p);
            BigDecimal t = new BigDecimal(1.0);
            float sr = 10;
        // The first array in this post
            double [] swdns = sw.getSample(t, sr);
        // The second array in this post
            double [] swpsdns = swps.getSample(t, sr);
        }

感谢您花时间查看我的帖子。非常感谢您的帮助。

【问题讨论】:

  • StrictMath.sin 不适用于 BigDecimals - 只有双精度数 - 而且您不断在 BigDecimal 和双精度数之间转换。这如何提高准确性?您需要找到一个库(或您自己的代码)来将 sin 函数直接计算为具有所需精度的 BigDecimal。
  • 我只在绝对必要的情况下使用转换,结果确实比我没有使用 BigDecimal 提供了更好的准确性。我去找图书馆。感谢您的帮助。
  • 您可以查看此问题以获取有关库的一些建议:stackoverflow.com/questions/2173512/… 我认为您看到的问题仍然源于double 的有限精度。如果你只看更少的小数,你所期望的所有等式都在那里。例如,1.2246467991473532E-16 实际上非常非常接近于零——小数部分以 15 个零开头,然后才有一些非零数字,它们都源于预期的计算错误。
  • 我认为我需要更高的准确性,因为我正在研究生成声波。在上述我使用 Math.sin 的情况下,相位计算中存在漂移,并且在一段时间内,随着所有这些小的过零误差加在一起,这将变得更加明显。也许我很挑剔,在做了一些数学之后,这对应于 3 分钟后 0.00000000000097208748 的幅度。很高兴我找到了解决方案,但我在项目的其他部分遇到了准确性问题。

标签: java trigonometry bigdecimal


【解决方案1】:

按照 Erwin 的建议,我找到了一个可以满足我需求的库。 BigDecimalMath 当我将精度设置为 1074 位小数(这是 Java 原始双精度值的负指数的最大绝对值)时,它具有慷慨的许可证并解决了我对这些特定数组的问题。

再次感谢欧文的帮助!

【讨论】:

    猜你喜欢
    • 2021-09-18
    • 2014-05-12
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多