自動微分法も、複素数と同様に、付加情報を持った演算を行うことによって 勾配と言う有益な情報を得る。 区間演算も、丸め誤差を得たり、値域を得たりすることが出来る。
このような「インテリジェントな数値型」を使った演算を行うには、 演算子多重定義の活用が便利である。 C++においてそれを行う方法について解説する。
例題として、ごく普通の複素数型を実現するクラスの実装例を挙げる。 簡単のため、加算、減算、乗算のみ。 complexsample.hppがクラスの定義部で、 complexsample.ccがその使用例。
#ifndef COMPLEXSAMPLE_HPP #define COMPLEXSAMPLE_HPP ... #endif // COMPLEXSAMPLE_HPP |
class complexsample {
...
};
|
public:
double re;
double im;
|
complexsample() {
}
complexsample(const double& x) {
re = x;
im = 0.;
}
complexsample(const double& x, const double& y) {
re = x;
im = y;
}
|
complexsample x, y, z;
x = complexsample(1.);
y = complexsample(1., 2.);
z = 1.;
complexsample p(1.);
complexsample q(1., 2.);
complexsample r = complexsample(1.);
complexsample s = complexsample(1., 2.);
complexsample t = 1.;
|
引数がdouble1つのコンストラクタは、「変換コンストラクタ」と呼ばれ、 double型からcomplexsample型への型変換に用いられる。 使用例で単に数値(1.)を代入できているのは、この変換コンストラクタの 働きによる。
なお、コンストラクタの引数をconst double&にしているのは、
friend complexsample operator+(const complexsample& x, const complexsample& y) {
complexsample r;
r.re = x.re + y.re;
r.im = x.im + y.im;
return r;
}
|
complexsample x, y, z;
z = x + y;
|
次の、
friend complexsample operator+(const complexsample& x, const double& y) {
complexsample r;
r.re = x.re + y;
r.im = x.im;
return r;
}
friend complexsample operator+(const double& x, const complexsample& y) {
complexsample r;
r.re = x + y.re;
r.im = y.im;
return r;
}
|
z = x + 1.;
z = 2. + x;
|
friend complexsample& operator+=(complexsample& x, const complexsample& y) {
x = x + y;
return x;
}
friend complexsample& operator+=(complexsample& x, const double& y) {
x.re += y;
return x;
}
|
p = (z += 2.);
|
friend complexsample operator-(const complexsample& x, const complexsample& y) {
complexsample r;
r.re = x.re - y.re;
r.im = x.im - y.im;
return r;
}
friend complexsample operator-(const complexsample& x, const double& y) {
complexsample r;
r.re = x.re - y;
r.im = x.im;
return r;
}
friend complexsample operator-(const double& x, const complexsample& y) {
complexsample r;
r.re = x - y.re;
r.im = - y.im;
return r;
}
friend complexsample& operator-=(complexsample& x, const complexsample& y) {
x -= y;
return x;
}
friend complexsample& operator-=(complexsample& x, const double& y) {
x.re = x.re - y;
return x;
}
|
friend complexsample operator-(const complexsample& x) {
complexsample r;
r.re = - x.re;
r.im = - x.im;
return r;
}
|
friend complexsample operator*(const complexsample& x, const complexsample& y) {
complexsample r;
r.re = x.re * y.re - x.im * y.im;
r.im = x.re * y.im + x.im * y.re;
return r;
}
friend complexsample operator*(const complexsample& x, const double& y) {
complexsample r;
r.re = x.re * y;
r.im = x.im * y;
return r;
}
friend complexsample operator*(const double& x, const complexsample& y) {
complexsample r;
r.re = x * y.re;
r.im = x * y.im;
return r;
}
friend complexsample& operator*=(complexsample& x, const complexsample& y) {
x = x * y;
return x;
}
friend complexsample& operator*=(complexsample& x, const double& y) {
x.re *= y;
x.im *= y;
return x;
}
|
friend complexsample sqr(const complexsample& x) {
complexsample r;
r.re = x.re * x.re - x.im * x.im;
r.im = 2. * x.re * x.im;
return r;
}
|
friend std::ostream& operator<<(std::ostream& s, const complexsample& x) {
s << '(' << x.re << '+' << x.im << "i)";
return s;
}
|
std::cout << z << "\n";
|
テンプレートは、クラス定義時には型を空欄にしておき、 変数の使用時に空欄部の型を指定することで、自動的にその型に対応する クラスが生成されるという機能である。 コンパイル時に中に入れられた型の種類数に応じて、その数だけのクラスが生成される。
上の例をテンプレートを用いて書き直してみた。 complexsampleT.hppがクラスの定義部で、 complexsampleT.ccがその使用例。 非常に単純に、
そのため、サンプルとして、加算、減算、乗算のみ、丸めの向きの変更も行わない 簡単な区間演算のクラスを作成した。テンプレートで両端点の型は任意。 複素数の例とほとんど同じ作り方。 intervalsampleT.hppがクラスの定義部で、 intervalsampleT.ccがその使用例。
また、同じくサンプルとして、加算、減算、定数乗算のみ、大きさを2に限定した 簡単なベクトル計算のクラスを作成した。テンプレートで内部の型は任意。 vectorsampleT.hppがクラスの定義部で、 vectorsampleT.ccがその使用例。
#include <iostream>
#include "complexsampleT.hpp"
#include "intervalsampleT.hpp"
#include "vectorsampleT.hpp"
int main()
{
complexsample < intervalsample<double> > x;
x = complexsample< intervalsample<double> >(intervalsample<double>(1., 2.), intervalsample<double>(2. ,3.));
x = intervalsample<double>(1.);
x = complexsample< intervalsample<double> >(1.);
// error
// x = 1.;
x += 1.;
std::cout << x << "\n";
vectorsample< complexsample < intervalsample<double> > > y;
y(0) = intervalsample<double>(1.);
y(1) = complexsample< intervalsample<double> >(1.);
y *= x;
y *= intervalsample<double>(1.);
// error
// y *= 2.;
std::cout << y << "\n";
}
|
complexsample(const T& x) {
re = x;
im = 0.;
}
|
template <class C> complexsample(const C& x) {
re = x;
im = 0.;
}
|
friend complexsample operator+(const complexsample& x, const T& y) {
complexsample r;
r.re = x.re + y;
r.im = x.im;
return r;
}
|
template <class C> friend complexsample operator+(const complexsample& x, const C& y) {
complexsample r;
r.re = x.re + y;
r.im = x.im;
return r;
}
|
これらを組み合わせて使ってみる。
#include <iostream>
#include "complexsampleT2.hpp"
#include "intervalsampleT2.hpp"
#include "vectorsampleT2.hpp"
int main()
{
complexsample < intervalsample<double> > x;
x = complexsample< intervalsample<double> >(intervalsample<double>(1., 2.), intervalsample<double>(2. ,3.));
x = intervalsample<double>(1.);
x = complexsample< intervalsample<double> >(1.);
x = 1.;
x += 1.;
std::cout << x << "\n";
vectorsample< complexsample < intervalsample<double> > > y;
y(0) = intervalsample<double>(1.);
y(1) = complexsample< intervalsample<double> >(1.);
// error
// y *= x;
// error
// y *= intervalsample<double>(1.);
y *= 2.;
std::cout << y << "\n";
}
|
これは、boostに含まれる is_convertibleとenable_ifを組み合わせることによって実現できる。
is_convertible<A, B>は、AをBに変換可能なら真、そうでなければ偽。 「#include <boost/type_traits.hpp>」が必要。
enable_ifには2通りの使い方があり、
これらを使ってテンプレート化の制限を行う場合、2通りの方法がある。
fがコンストラクタの場合は戻り値が無いので前者を使うしかなく、 二項演算子の場合は引数の追加が出来ないので後者を使うしか無い。
以上を元に変更したものが、
である。これらを組み合わせて使ってみる。
#include <iostream>
#include "complexsampleT3.hpp"
#include "intervalsampleT3.hpp"
#include "vectorsampleT3.hpp"
int main()
{
complexsample < intervalsample<double> > x;
x = complexsample< intervalsample<double> >(intervalsample<double>(1., 2.), intervalsample<double>(2. ,3.));
x = intervalsample<double>(1.);
x = complexsample< intervalsample<double> >(1.);
x = 1.;
x += 1.;
std::cout << x << "\n";
vectorsample< complexsample < intervalsample<double> > > y;
y(0) = intervalsample<double>(1.);
y(1) = complexsample< intervalsample<double> >(1.);
y *= x;
y *= intervalsample<double>(1.);
y *= 2.;
std::cout << y << "\n";
}
|