Search code examples
javatrigonometrybigdecimal

How do you get accurate results for sine values with BigDecimal in Java?


My goal is to generate an accurate sine wave. My problem is that when I use BigDecimal and StrictMath to generate the values, some of the zero crossings are wrong and the symmetry is broken.

Here is an array generated with a frequency of 1, a phase of 0, an amplitude of 1, a time of 1 second and a sample rate of 10 (I'll post the code later in this post):

>[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 

Shouldn't [5] be 0 for accuracy? Shouldn't (4 = 1) as well as (2 = 3),(9 = 6) and (7 = 8)?

A 2nd case, where the phase is equal to StrictMath.PI/2.0 appears to produce accuracy at [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  

In this case, where the starting point is less accurate , [5] is more accurate, but once again, shouldn't (-4 = 1) as well as (-2 = 3),(-9 = 6) and (-7 = 8)?

So my question is why is this the case? Why are the zero crossings wrong, but the 1 and -1 crossings right? Why is the sine symmetry broken?

Here is my code for generating the values:


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

        }

and the main method:


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

Thank you for taking the time to look over my post. Your help is greatly appreciated.


Solution

  • As Erwin recommended, I found a library that work for my needs. BigDecimalMath It has a generous license and fixed my problem with these particular arrays when I set the accuracy to 1074 decimal places, which is the maximum absolute value of the negative exponent of a Java primitive double value.

    Thank you again for your help Erwin!