Search code examples
c++precisiondouble-precision

C++ : sin(M_PI/6) = 0.5?


I am writing a scientific program which uses common values of sines in its main algorithm, namely sin(M_PI/N) for N = 1, 2, 3, 4, 5, 6.

Since I want my program to be as fast as possible, I thought : let's store these values in a vector instead of having them computed over and over again. It looks like this:

sin_pi_over_n_.clear();
sin_pi_over_n_.push_back(0.0);
sin_pi_over_n_.push_back(1.0);
sin_pi_over_n_.push_back(sqrt(3.0)/2.0);
sin_pi_over_n_.push_back(sqrt(2.0)/2.0);
sin_pi_over_n_.push_back(sqrt(2.0)*0.25*sqrt(5.0-sqrt(5.0)));
sin_pi_over_n_.push_back(0.5);

so now in my main algorithm I write s = sin_pi_over_n_[n-1]; instead of s = sin(M_PI/n);.

But to my great surprise, the program turned out to be almost twice as slow! I thought, really, does it take that long to read a value in a vector? But then I realized that wasn't the problem: if I write instead

sin_pi_over_n_.push_back(sin(M_PI/1.0));
sin_pi_over_n_.push_back(sin(M_PI/2.0));
sin_pi_over_n_.push_back(sin(M_PI/3.0));
sin_pi_over_n_.push_back(sin(M_PI/4.0));
sin_pi_over_n_.push_back(sin(M_PI/5.0));
sin_pi_over_n_.push_back(sin(M_PI/6.0));

then the program is fast again! I then thought: something's wrong with my values of sines. But the crazy thing is, even if I only replace the last line sin_pi_over_n_.push_back(sin(M_PI/6.0)); by sin_pi_over_n_.push_back(0.5); then the program is slow again! Is it about double precision? I kinda doubt it: if I ask std::cout << abs(sin(M_PI/6.0) - 0.5) << std::endl;, I get 0 in my terminal.

Oops : I just realized ; if I ask std::cout << sin(M_PI/6.0) - 0.5 << std::endl; (without the abs), I then get -5.55112e-17. I am still going to go ahead and post that question, because this behavior seems incredible to me. How can I possibly optimize my program's speed if such unpredictable phenomenons have such a great impact on the performance?

Thanks for your insights!

Edit : Maybe I haven't made myself clear enough. In my program, I have a class Algo. When I execute my program, some function, say my_function, is called an enormous amount of times. In this function, one line is: s = sin(M_PI/n);. I thought I'd replace this line by s = sin_pi_over_n_[n-1];, where sin_pi_over_n_[n-1] is a vector that is stored as a member variable of the class Algo and that I fill once and for all in the constructor of Algo. Hope that makes things clearer.

Edit 2 : Okay, it appears some of you want me to post more code. Here it comes:

Class Algo:

class Algo : public QThread
{
    Q_OBJECT

public:
    Algo() {}
    void reset_algo(...)

public slots:
    void run();

private:
    void create_sines();
    double super_total_neighbors_angle(const unsigned int &index, double &error);
    double super_radius_update(const unsigned int &index, double &error);
    // etc

    std::vector<double> radii_;
    std::vector<double> sin_pi_over_n_;
    std::vector<unsigned int> neighbors_lists_sizes_;
    // etc
};

Member function create_sines:

void Algo::create_sines()
{
    sin_pi_over_n_.clear();

    /*sin_pi_over_n_.push_back(0.0);
    sin_pi_over_n_.push_back(1.0);
    sin_pi_over_n_.push_back(0.5*sqrt(3.0));
    sin_pi_over_n_.push_back(0.5*sqrt(2.0));
    sin_pi_over_n_.push_back(0.25*sqrt(2.0)*sqrt(5.0-sqrt(5.0)));
    sin_pi_over_n_.push_back(0.5);*/

    sin_pi_over_n_.push_back(sin(M_PI/1.0));
    sin_pi_over_n_.push_back(sin(M_PI/2.0));
    sin_pi_over_n_.push_back(sin(M_PI/3.0));
    sin_pi_over_n_.push_back(sin(M_PI/4.0));
    sin_pi_over_n_.push_back(sin(M_PI/5.0));
    sin_pi_over_n_.push_back(sin(M_PI/6.0));

    return;
}

Member function super_radius_update (which I renamed my_function above):

inline double Algo::super_radius_update(const unsigned int &index, double &error)
{
    int n = neighbors_lists_sizes_[index];
    double s = sin(super_total_neighbors_angle(index, error)*0.5/n);
    double rv = radii_[index]*s/(1-s);
    //s = sin(M_PI/n);
    s = sin_pi_over_n_[n-1];
    return (1-s)*rv/s;
}

I'm afraid it's hard for me to send a minimal complete example that you could run, because the entire class is a bit long and complicated. I could try, but I would sill have to post a big amount of code, and it would take me quite some time to extract...

I'm using Qt creator 4.8 64 bits on a Linux Ubuntu 12.04 64 bits laptop.

Edit 3 or 4 : I apologize for all the edits. There's an important piece of info I did not tell you : the program is going to run (basically call the function super_radius_update) until some error falls under some tolerance. As a consequence, what is making the program slower or faster is not the time of execution of super_radius_update but the number of calls of that function, I believe. Using such or such value for sin(M_PI/N) is going to have an impact on how quickly the tolerance is reached by the error.


Solution

  • As was pointed out, it turned out my algorithm was unstable, couldn't blame anything on the computer or C++ ;)