としおの読書生活

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

タグ: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言語から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つ作成するひつようもありません。



code-1839406_640


C言語でfloat型やdouble型に対してビット演算を行おうとしたところコンパイラに「error: invalid operands to binary & (have ‘float’ and ‘int’)」(要するにそんなことできないぞ)と怒られてしまいました。

そこで解決策を調べたので忘備録として記録しておきます。




float型でbit演算を行うための解決策


まず結論からになりますが、共用体(union)を使用することでこの問題を解決することができました。

typedef union longfloat{
  long lnum;
  float fnum;
}longfloat;

上記のような共用体をしようすることでfloat型であるlongfloat.fnumに値を入れた後、longfloat.lnumに対してビット演算を行うことであたかもfloat型にbit演算を行ったかのように扱うことができます。

以下にサンプルコードを示します。


floatにビット演算を行うサンプルコード



#include typedef union longfloat{ long lnum; float fnum; }longfloat; int main(void){ longfloat lfnum = {0}; lfnum.fnum = 1.2345; printf("fnum:%08lx\n", lfnum.lnum); lfnum.lnum = lfnum.lnum >> 8; printf("fnum:%08lx\n", lfnum.lnum); lfnum.lnum &= 0xFFFF0000; printf("fnum:%08lx\n", lfnum.lnum); return 0; }


出力結果
fnum:3f9e0419
fnum:003f9e04
fnum:003f0000






double型にビット演算を行うサンプルコード


#include 

typedef union longlongdouble{
  long long llnum;
  double dnum;
}longlongdouble;

int main(void){
  longlongdouble lldnum = {0};
  lldnum.dnum = 1.2345;
  printf("fnum:%016llx\n", lldnum.llnum);

  lldnum.llnum = lldnum.llnum >> 8;
  printf("fnum:%016llx\n", lldnum.llnum);

  lldnum.llnum &= 0xFFFFFFFF00000000;
  printf("fnum:%016llx\n", lldnum.llnum);

  return 0;
}


出力結果
fnum:3ff3c083126e978d
fnum:003ff3c083126e97
fnum:003ff3c000000000


code-1839406_640


CやC++でコマンドを実行(サブプロセスを起動)する標準的な関数としてsystemとpopenがあります。

これらの関数を使っているコードを読んでいるとどういった基準で使い分けをしているのか分からなかったため、今回はsystemとpopenの使い分け方について調べてみました。





まずは結論

まずは結論です。

コマンドを実行したいだけなら「system」を使うべき。

コマンドの実行結果を取得したい場合は「popen」を使うのが有効。


以下にsystemとpopenを使用してコマンドを実行するサンプルコードとそれぞれのメリット、デメリットを記していきます。


system関数でコマンドを実行するサンプル


#include 
#include 

int main(void){
    int ret = 0;
    
    ret = system("rm -f test.txt");
    if (ret == -1){
        printf("ERROR\n");
    }

    return 0;
}



system関数のメリットとデメリット


メリット


  • popenと比べてコードが簡易的なため書き間違いを防ぎやすい

デメリット


  • 実行結果を取得するためには実行結果を一度ファイルに出力するなど工夫が必要
  • popen(コマンド, "w")のように標準入力にデータを与えることができない






popen関数でコマンドを実行するサンプル


#include 
#include 

#define BUF 256

int main(void){
    FILE *fp;
    char buf[BUF];

    fp = popen("pwd", "r");
    if (fp == NULL){
        printf("ERROR");
        return -1;
    }
    
    while(fgets(buf, BUF, fp) != NULL){
        fputs(buf, stdout);
    }
    pclose(fp);

    printf("buf = %s\n", buf);

    return 0;
}



popen関数のメリットとデメリット


メリット

  • 実行結果の取得がファイル読み込みの操作と同じ要領でできる
  • system関数と違い標準入力にデータを与えることが可能


デメリット

  • ploseの書き忘れによりメモリリークの恐れがある



最後に


もしこの記事を読んだ人の中でsystemとpopenに他にもメリットやデメリットがあるということを知っている方がいれば、コメントで教えていただければ幸いです。






code-1839406_640


C言語やC++でbit演算を使うときに最低限これだけは覚えておいた方が良いという基本的な使い方をまとめました。





AND演算子でマスクする


マスクは特定のbitを強制的に0にしたい場合に使用します。

以下に01010101に対して下位4bitをマスクするサンプルコードを示します。
#include 

#define EVENT 0b11110000

int main(void){
    unsigned char sample = 0b01010101;

    sample &=  EVENT;

	return 0;
}



OR演算子でセットする


セットはマスクとは逆の操作で特定のbitを強制的に1にしたい場合にしようします。

以下に01010101に対して上位4bitをセットするサンプルコードを示します。
#include 

#define EVENT 0b11110000

int main(void){
    unsigned char sample = 0b01010101;

    sample |=  EVENT;

	return 0;
}



XOR演算子でbitを反転する


bit操作を行っているとbitを反転したい場面によく遭遇します。

そういうときは、XOR演算子を使いましょう。

以下に01010101の全てのbitを反転するサンプルコードを示します。
#include 

#define EVENT 0b11111111

int main(void){
    unsigned char sample = 0b01010101;

    sample ^=  EVENT;

	return 0;
}



特定のbitの値を取り出す


"N"bit目を取り出したいというときは、シフト演算子を使えば楽に実装することができます。

以下に01010101の4bit目を取り出すサンプルコードを示します。
#include 

int main(void){
    unsigned char sample = 0b01010101;
    int N =4;
    int nBitNum;

    nBitNum = (sample >> N) & 1;
    printf("%d\n", nBitNum);

	return 0;
}



最後に


bit演算子の使い方について簡単にまとめましたが、随時便利な使い方を見つけ次第記事を更新していきます。




↑このページのトップヘ