としおの読書生活

田舎に住む社会人の読書記録を綴ります。 主に小説や新書の内容紹介と感想を書きます。 読書の他にもワイン、紅茶、パソコン関係などの趣味を詰め込んだブログにしたいです。

カテゴリ:C++ > 忘備録

C++で微分を実装してみたので忘備録として記録しておきます。


数値微分を実装する


f(x)=f(x+Δx)f(x)Δx

まずは典型的な上記の式の前進差分近似を使った方法で微分をしてみます。

#include <iostream>

double func(double x){
    return x*x;
}

double numericalDiff(double (*f)(double), double xdouble eps=0.001){
    return (f(x + eps) - f(x)) / eps;
}

int main(void){
    double (*pfunc)(double);
    pfunc = func;
    printf("%lf"numericalDiff(pfunc2));
    return 0;
}

4.001000

多少は誤差があるものの微分ができました。

関数ポインタを使ってnumericalDiffに微分したい関数を渡しているため、他の式に対しても微分を行うことが可能です。

また、引数epsの値を小さくすることでより精度の高い微分を多なうことができます。



誤差の少ない数値微分


前進差分近似を使うよりも中心差分近似を使った微分の方が誤差が少なくなるみたいですのでそちらも実装していきます。

先ほどのコードからnumericalDiff関数だけ以下の式に合わせて変更していきます。

f(x)=f(x+Δx)f(x - Δx)x


double numericalDiff(double (*f)(double), double xdouble eps=0.001){
    return (f(x + eps) - f(x - eps)) / (2*eps);
}

4.000000

中心差分近似を使うことで上記のサンプルに対しては誤差なく微分を行うことができました。



3753817_s

C++でモンテカルロ法を使って円周率を求めるプログラムを作ったので概要と実装を忘備録として残します。


モンテカルロ法とは


モンテカルロ法とは乱数を用いてシミュレーションや数値計算を行う方法の一つです。

少し前には将棋や囲碁のAIでモンテカルロ法を使うのがブームになりました。

今回はモンテカルロ法を使った例題としてポピュラーな円周率の近似値を求めるという問題を解いていきます。



円周率を求める手順


①正方形内にランダムに点を打つ。今回は正方形の大きさを1×1として(x, y)座標のx, yを0~1の範囲で乱数を使って点を打っていきます。

②生成した点が円の内側にあるか、外側にあるか判定する。内側にある場合はポイントを加算する。

キャプチャ

点が上記の図の青色の範囲内ならポイントを加算するということです。

③上記の①と②の行為をn回繰り返す。試行回数が多いほど円周率が正しい値へと近づいていきます。

④円周率の近似値をもとめる。円周率の近似値は以下の式で求めることができます。

π = 4 × ポイント






実装


#include <iostream>
#include <ctime>
#include <cstdlib>

int main(void){
    int n = 0;
    std::cout << "試行回数を入力してください" << std::endl;
    std::cin >> n;

    // 試行回数が0以下ならエラー
    if (n <= 0){
        return -1;
    }

    // 試行回数分ランダムに点を描画
    std::srand(time(NULL));
    int point = 0;
    double x = 0;
    double y = 0;
    for(int i=0i<n; ++i){
        // ①x, yは0~1の範囲
        x = (double)rand() / 32767;
        y = (double)rand() / 32767;
        // ②
        if (x*x+y*y < 1.0){
            // 円内に点が打たれた場合
            point++;
        }
    }

    // ④円周率を求める
    float pi = 4.0 * point / n;
    std::cout << "pi = " << pi << std::endl;
    
    return 0;
}




実行結果


1000
pi = 3.064

1000000
pi = 3.14275

試行回数が多いほど近似した値になっていることが分かりますね。





3753817_s

本記事ではVisual Studio Codeで利用できるコンパイラのMinGwでDLLを作成して、呼び出す方法までを紹介します。

DLLを作成するとか難しそうというイメージをもっている人もいるかもしれませんが、やってみると意外と簡単なのでこの機会に作り方を覚えてしまいましょう。



DLLを作成する


今回は、サンプルとして足し算と引き算を行う関数が実装されているDLLを作成します。

ソースコードは以下の通りです。


DLLのヘッダファイル


// Calc.h
#ifndef H_CALC
#define H_CALC

int addition(int xint y);     // x+yをする
int subtraction(int xint y);  // x-yをする

#endif // H_CALC


DLLのソースファイル


// Calc.cpp
#include "Calc.h"

int addition(int xint y){
    return x+y;
}

int subtraction(int xint y){
    return x-y;
}


オブジェクトファイルの作成(.o)


ソースコードを用意したところでMinGwのG++コマンドを使ってDLLを作成していきましょう。

先ほどのソースコードにもコメントで書いているのですが、ソースコード名はCalc.cppです。

まず最初に-cオプションを使ってオブジェクトファイルを作成します。コマンドは以下のとおりです。

g++ -c .\Calc.cpp

このコマンドを実行するとCalc.cppと同じディレクトリにCalc.oというファイルが作成されます。


DLLの作成


DLLは-sharedオプションを使用することで作成することができます。コマンドは以下のとおりです。

g++ .\Calc.o -o Calc.dll -shared

上記のコマンドを実行するとCalc.dllというファイルが作成されます。

DLLの作成は以上で終了になります。
たったこれだけでいいの問う感じですが、やってみるとむちゃくちゃ簡単なんですよね。



作成したDLLを使う


次に先ほど作成したCalc.dllを呼び出す方法を紹介していきます。


DLLの呼び出し元のソースコード


// main.cpp
#include <iostream>
#include "Calc.h"

int main(void){
    int xy;

    std::cin >> x >> y;
    std::cout << "addition:" << addition(xy<< std::endl;
    std::cout << "subtraction:" << subtraction(xy<< std::endl;
    return 0;
}


コンパイルする


ソースコードができたらコンパイルしていきましょう。

コンパイルのコマンドは呼び出し元のコードをコンパイルするときにDLLの名前を教えてあげるだけです。

g++ -o .\main.exe .\main.cpp .\Calc.dll


実行結果


.\main.exe
3 5
addition:8
subtraction:-2


コンパイルして作成したmain.exeを実行してみると、DLLの関数が呼び出されていることが分かりますね。



まとめ


今回は、MinGwでDLLを作成して、その後作成したDLLを使う方法を紹介しました。

今回はコマンドを全て手打ちで実行していきましたが、ソースコードが増えるとすごく面倒です。

なのでそいうときはmakeファイルを作成してみましょう。

makeファイルの作り方については後日紹介させていただきます。




3753817_s

本記事では、C言語からC++で作成したDLLを呼びだす方法の失敗例と成功例を紹介していきます。

結果だけが知りたいという人はC言語からC++のDLLを呼び出すの章を読むだけで大丈夫です。



C++のDLLが呼び出せない例


まず最初にCからC++のDLLの呼び出しが失敗する例を紹介します。


C++のDLLのコード


ヘッダファイル

#ifndef H_SAMPLE
#define H_SAMPLE

void PrintSample();

#endif // H_SAMPLE


ソースファイル

#include "sample.h"
#include <iostream>

void PrintSample(){
    std::cout << "Hello CPP!!" << std::endl;
}


C言語のコード


#include "sample.h"

int main(void){
    PrintSample();
    return 0;
}


上記のコードをコンパイルすると下記のエラーコードがでてコンパイルすることができません。

undefined reference to `PrintSample' collect2.exe: error: ld returned 1 exit status




C言語からC++のDLLを呼び出す


上記のコードからC++のDLLのヘッダファイルを修正するだけで、C言語からC++の関数を呼びだせるようになります。

修正内容はヘッダファイルにextern "C"を追加するだけです。

修正したコードを以下にのせます。DLLのヘッダファイル以外は先ほどの失敗例で紹介したものと同じです。

ヘッダファイル

#ifndef H_SAMPLE
#define H_SAMPLE

#ifdef __cplusplus
extern "C"{
#endif /* __cplusplus */

    extern void PrintSample();

#ifdef __cplusplus
#endif /* __cplusplus */
#endif // H_SAMPLE


ソースファイル

#include "sample.h"
#include <iostream>

void PrintSample(){
    std::cout << "Hello CPP!!" << std::endl;
}


C言語のコード


#include "sample.h"

int main(void){
    PrintSample();
    return 0;
}


実行結果


Hello CPP!!




まとめ


extern "C"をC++のDLLのヘッダファイルに追加するだけで、簡単にC言語からC++のDLLを利用することができました。

ちなみに今回作成したDLLはC++でもそのまま利用することができるので、C言語用とC++用でDLLを2つ作成するひつようもありません。



3753817_s

静的メンバ変数を宣言した時に、コンパイルエラーが起きて
undefined reference to `Hogehoge::m_hogeNum'
といった感じに、宣言してたはずのメンバー変数が宣言していないとのエラーが発生しました。

本記事では、このエラーに対する対処方法をまとめていきます。




"undefined reference to"のエラーが出る場合のコード


以下のコードではをコンパイルすると冒頭で説明したように、「undefined reference to `Hogehoge::m_hogeNum'」というエラーメッセージが表示されます。


ソースコード

#include <iostream>

class Hogehoge{
public:
    Hogehoge(int num);
    ~Hogehoge();

private:
    static intm_hogeNum;
};

Hogehoge::Hogehoge(int num){
    m_hogeNum = &num;
}

Hogehoge::~Hogehoge(){}

int main(void){
    int x = 5;
    Hogehogehogehoge = new Hogehoge(5);

    return 0;
}



エラーメッセージ

undefined reference to `Hogehoge::m_hogeNum'
collect2.exe: error: ld returned 1 exit status



対策


調べてみると、staticメンバ変数も関数のように明示的にすることで"undefined reference"エラーを防ぐことができることが分かりました。

先ほど上記で紹介したコードを改造して、コンパイルがとおるようにしてみます。

#include <iostream>

class Hogehoge{
public:
    Hogehoge(int num);
    ~Hogehoge();

private:
    static intm_hogeNum;
};

int* Hogehoge::m_hogeNum = nullptr;

Hogehoge::Hogehoge(int num){
    m_hogeNum = &num;
}

Hogehoge::~Hogehoge(){}

int main(void){
    int x = 5;
    Hogehogehogehoge = new Hogehoge(5);

    return 0;
}

赤字にしている部分が先ほどのコードから追加した部分です。

intHogehoge::m_hogeNum = nullptr;

といった感じに明示的に宣言することで、"undefined reference"エラーを回避することができました。



まとめ


staticメンバ変数は、関数などとどうように明示的に定義しておく必要があることが分かりました。

久しぶりにC++を使ったりするとこういったくだらないミスで詰まってしまうことがありますので気をつけましょう。




↑このページのトップヘ