interval_add, interval_sub, interval_mul, interval_divが加減乗除を行う関数、interval_sqrtが平方根、 interval_minusが符号反転、interval_init, interval_init2は初期化、 interval_printが表示。
乗除算はちゃんと場合分けせず全種類計算して最大・最小を取る手抜き実装に なっている。 また、除算と平方根でエラーが起きたときはその場でexitするようになっている。
#include "interval.h"
interval interval_init(double x)
{
interval z;
z.inf = x;
z.sup = x;
return z;
}
interval interval_init2(double x, double y)
{
interval z;
z.inf = x;
z.sup = y;
return z;
}
interval interval_add(interval x, interval y)
{
interval z;
fesetround(FE_DOWNWARD);
z.inf = x.inf + y.inf;
fesetround(FE_UPWARD);
z.sup = x.sup + y.sup;
fesetround(FE_TONEAREST);
return z;
}
interval interval_sub(interval x, interval y)
{
interval z;
fesetround(FE_DOWNWARD);
z.inf = x.inf - y.sup;
fesetround(FE_UPWARD);
z.sup = x.sup - y.inf;
fesetround(FE_TONEAREST);
return z;
}
interval interval_minus(interval x)
{
interval z;
z.inf = - x.sup;
z.sup = - x.inf;
return z;
}
interval interval_mul(interval x, interval y)
{
interval z;
double tmp;
fesetround(FE_DOWNWARD);
z.inf = x.inf * y.inf;
tmp = x.inf * y.sup;
if (tmp < z.inf) z.inf = tmp;
tmp = x.sup * y.inf;
if (tmp < z.inf) z.inf = tmp;
tmp = x.sup * y.sup;
if (tmp < z.inf) z.inf = tmp;
fesetround(FE_UPWARD);
z.sup = x.inf * y.inf;
tmp = x.inf * y.sup;
if (tmp > z.sup) z.sup = tmp;
tmp = x.sup * y.inf;
if (tmp > z.sup) z.sup = tmp;
tmp = x.sup * y.sup;
if (tmp > z.sup) z.sup = tmp;
fesetround(FE_TONEAREST);
return z;
}
interval interval_div(interval x, interval y)
{
interval z;
double tmp;
if (y.inf <= 0 && y.sup >= 0) {
printf("zero divide!\n");
exit(1);
}
fesetround(FE_DOWNWARD);
z.inf = x.inf / y.inf;
tmp = x.inf / y.sup;
if (tmp < z.inf) z.inf = tmp;
tmp = x.sup / y.inf;
if (tmp < z.inf) z.inf = tmp;
tmp = x.sup / y.sup;
if (tmp < z.inf) z.inf = tmp;
fesetround(FE_UPWARD);
z.sup = x.inf / y.inf;
tmp = x.inf / y.sup;
if (tmp > z.sup) z.sup = tmp;
tmp = x.sup / y.inf;
if (tmp > z.sup) z.sup = tmp;
tmp = x.sup / y.sup;
if (tmp > z.sup) z.sup = tmp;
fesetround(FE_TONEAREST);
return z;
}
interval interval_sqrt(interval x)
{
interval z;
if (x.inf < 0) {
printf("sqrt of negative value!\n");
exit(1);
}
fesetround(FE_DOWNWARD);
z.inf = sqrt(x.inf);
fesetround(FE_UPWARD);
z.sup = sqrt(x.sup);
fesetround(FE_TONEAREST);
return z;
}
void interval_print(interval x)
{
printf("[%.17g,%.17g]\n", x.inf, x.sup);
}
以下のinterval.hは、肝心の構造体intervalの宣言と、上のinterval.cで定義された
関数のプロトタイプ宣言を集めたもの。構造体の宣言ではtypedefを使って
使用時にstructと書かなくていいようにしている。
#ifdef -- #define -- #endifはよくあるインクルードガードで、
二度以上includeした場合に最初の一回しか読み込まれないための工夫。
#ifndef INTERVAL_H
#define INTERVAL_H
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <fenv.h>
typedef struct {
double inf;
double sup;
} interval;
interval interval_init(double x);
interval interval_init2(double x, double y);
interval interval_add(interval x, interval y);
interval interval_sub(interval x, interval y);
interval interval_minus(interval x);
interval interval_mul(interval x, interval y);
interval interval_div(interval x, interval y);
interval interval_sqrt(interval x);
void interval_print(interval x);
#endif /* INTERVAL_H */
以下のtest-interval.cは、これらの関数の使用例。
#include "interval.h"
int main()
{
interval x, y, z;
x = interval_init(1);
y = interval_init(10);
z = interval_div(x, y);
interval_print(z);
x = interval_init2(1, 2);
y = interval_init2(3, 4);
/* basic four operations */
z = interval_add(x, y);
interval_print(z);
z = interval_sub(x, y);
interval_print(z);
z = interval_mul(x, y);
interval_print(z);
z = interval_div(x, y);
interval_print(z);
/* square root */
z = interval_sqrt(x);
interval_print(z);
/* negate */
z = interval_minus(x);
interval_print(z);
}
このように、区間演算を使用するプログラムではinterval.hをincludeし、
compile時に本体interval.cと一緒に使う。以下は実行結果。
$ cc test-interval.c interval.c -lm
$ ./a.out
[0.099999999999999992,0.10000000000000001]
[4,6]
[-3,-1]
[3,8]
[0.25,0.66666666666666674]
[1,1.4142135623730951]
[-2,-1]
$
#include <stdio.h>
#include <math.h>
int main()
{
double a, b, c, r;
a = 1.;
b = 1e15;
c = 1e14;
r = (-b + sqrt(b * b - 4 * a * c)) / (2 * a);
printf("%.17g\n", r);
r = 2 * c / (-b - sqrt(b * b - 4 * a * c));
printf("%.17g\n", r);
}
これを実行すると、
$ cc quad-double.c -lm
$ ./a.out
-0.125
-0.10000000000000002
$
のように、数学的には同じ値になるはずのものが大きく異なってしまう。
どちらの値がおかしいかは、区間演算を行うと分かる。 次のプログラムは、上のプログラムの計算を区間演算に置き換えたものである。
#include "interval.h"
int main()
{
interval a, b, c, d, r, t1, t2;
a = interval_init(1);
b = interval_init(1e15);
c = interval_init(1e14);
/* r = (-b + sqrt(b * b - 4 * a * c)) / (2. * a); */
t1 = interval_mul(b, b);
t2 = interval_mul(interval_mul(interval_init(4), a), c);
d = interval_sub(t1, t2);
d = interval_sqrt(d);
t1 = interval_sub(d, b);
t2 = interval_mul(interval_init(2), a);
r = interval_div(t1, t2);
interval_print(r);
/* r = 2 * c / (-b - sqrt(b * b - 4 * a * c)); */
t1 = interval_mul(b, b);
t2 = interval_mul(interval_mul(interval_init(4), a), c);
d = interval_sub(t1, t2);
d = interval_sqrt(d);
t1 = interval_mul(interval_init(2), c);
t2 = interval_minus(interval_add(b, d));
r = interval_div(t1, t2);
interval_print(r);
}
これを実行すると、
$ cc quad-interval.c interval.c -lm
$ ./a.out
[-0.1875,-0.0625]
[-0.10000000000000003,-0.099999999999999992]
$
となる。これを見ると、前者は極端に区間幅が広くなっていて計算結果が信頼できず、
後者は区間幅が狭く信頼できる計算であったことが分かる。
ただ、このプログラムを見ると、C言語では自分で定義した構造体intervalに 対して加減乗除の演算子が使えないためそれらを関数呼び出しに 置き換えており、プログラムを大きく書き換えることを強いられてしまっている。 C言語の拡張であるC++には演算子多重定義の機能があり、自分で定義した 型に対して演算子を用いたときの動作を定義することができる。
#ifndef INTERVAL_HPP
#define INTERVAL_HPP
#include <iostream>
#include <cmath>
#include <stdexcept>
#include <fenv.h>
struct interval {
double inf;
double sup;
interval() {
inf = 0;
sup = 0;
}
interval(double x) {
inf = x;
sup = x;
}
interval(double x, double y) {
inf = x;
sup = y;
}
friend interval operator+(interval x, interval y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x.inf + y.inf;
fesetround(FE_UPWARD);
r.sup = x.sup + y.sup;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator-(interval x, interval y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x.inf - y.sup;
fesetround(FE_UPWARD);
r.sup = x.sup - y.inf;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator-(interval x) {
interval r;
r.sup = - x.inf;
r.inf = - x.sup;
return r;
}
friend interval operator*(interval x, interval y) {
interval r;
double tmp;
fesetround(FE_DOWNWARD);
r.inf = x.inf * y.inf;
tmp = x.inf * y.sup;
if (tmp < r.inf) r.inf = tmp;
tmp = x.sup * y.inf;
if (tmp < r.inf) r.inf = tmp;
tmp = x.sup * y.sup;
if (tmp < r.inf) r.inf = tmp;
fesetround(FE_UPWARD);
r.sup = x.inf * y.inf;
tmp = x.inf * y.sup;
if (tmp > r.sup) r.sup = tmp;
tmp = x.sup * y.inf;
if (tmp > r.sup) r.sup = tmp;
tmp = x.sup * y.sup;
if (tmp > r.sup) r.sup = tmp;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator/(interval x, interval y) {
interval r;
double tmp;
if (y.inf <= 0. && y.sup >= 0.) {
throw std::domain_error("interval: division by 0");
}
fesetround(FE_DOWNWARD);
r.inf = x.inf / y.inf;
tmp = x.inf / y.sup;
if (tmp < r.inf) r.inf = tmp;
tmp = x.sup / y.inf;
if (tmp < r.inf) r.inf = tmp;
tmp = x.sup / y.sup;
if (tmp < r.inf) r.inf = tmp;
fesetround(FE_UPWARD);
r.sup = x.inf / y.inf;
tmp = x.inf / y.sup;
if (tmp > r.sup) r.sup = tmp;
tmp = x.sup / y.inf;
if (tmp > r.sup) r.sup = tmp;
tmp = x.sup / y.sup;
if (tmp > r.sup) r.sup = tmp;
fesetround(FE_TONEAREST);
return r;
}
friend interval sqrt(interval x) {
interval r;
if (x.inf < 0.) {
throw std::domain_error("interval: sqrt of negative value");
}
fesetround(FE_DOWNWARD);
r.inf = sqrt(x.inf);
fesetround(FE_UPWARD);
r.sup = sqrt(x.sup);
fesetround(FE_TONEAREST);
return r;
}
friend std::ostream& operator<<(std::ostream& s, interval x) {
s << '[' << x.inf << ',' << x.sup << ']';
return s;
}
};
#endif // INTERVAL_HPP
使用例は以下の通り。
最後の行の「x+1」がうまくコンパイル出来ているのが少し不思議ではあるが、
自動的に引数が1つのコンストラクタを呼び出して、
「operator+(x, interval(1))」のように処理されている。
#include "interval.hpp"
int main()
{
std::cout.precision(17);
// constructors
interval x;
interval y(1);
interval z(2, 3);
x = 1;
y = 10;
std::cout << x / y << "\n";
x = interval(1, 2);
y = interval(3, 4);
// basic four operations
std::cout << x + y << "\n";
std::cout << x - y << "\n";
std::cout << x * y << "\n";
std::cout << x / y << "\n";
// square root
std::cout << sqrt(x) << "\n";
// negate
std::cout << -x << "\n";
// operation with constant
std::cout << x + 1 << "\n";
}
以下は実行結果。
$ c++ test-interval.cc
$ ./a.out
[0.099999999999999992,0.10000000000000001]
[4,6]
[-3,-1]
[3,8]
[0.25,0.66666666666666674]
[1,1.4142135623730951]
[-2,-1]
[2,3]
$
このC++のライブラリ使うと、2次方程式の例題の区間演算化は、ほぼ変数宣言の
型doubleをintervalに書き換えるだけで済むようになる。
#include <iostream>
#include <cmath>
#include "interval.hpp"
int main()
{
interval a, b, c, r;
std::cout.precision(17);
a = 1.;
b = 1e15;
c = 1e14;
r = (-b + sqrt(b * b - 4 * a * c)) / (2 * a);
std::cout << r << "\n";
r = 2 * c / (-b - sqrt(b * b - 4 * a * c));
std::cout << r << "\n";
}
この実行結果は以下の通り。
$ c++ quad-interval.cc
$ ./a.out
[-0.1875,-0.0625]
[-0.10000000000000003,-0.099999999999999992]
$
#ifndef INTERVAL_HPP
#define INTERVAL_HPP
#include <iostream>
#include <cmath>
#include <stdexcept>
#include <fenv.h>
class interval {
double inf;
double sup;
public:
interval() {
inf = 0;
sup = 0;
}
interval(const double& x) {
inf = x;
sup = x;
}
interval(const double& x, const double& y) {
inf = x;
sup = y;
}
friend interval operator+(const interval& x, const interval& y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x.inf + y.inf;
fesetround(FE_UPWARD);
r.sup = x.sup + y.sup;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator+(const interval& x, const double& y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x.inf + y;
fesetround(FE_UPWARD);
r.sup = x.sup + y;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator+(const double& x, const interval& y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x + y.inf;
fesetround(FE_UPWARD);
r.sup = x + y.sup;
fesetround(FE_TONEAREST);
return r;
}
friend interval& operator+=(interval& x, const interval& y) {
x = x + y;
return x;
}
friend interval& operator+=(interval& x, const double& y) {
fesetround(FE_DOWNWARD);
x.inf = x.inf + y;
fesetround(FE_UPWARD);
x.sup = x.sup + y;
fesetround(FE_TONEAREST);
return x;
}
friend interval operator-(const interval& x, const interval& y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x.inf - y.sup;
fesetround(FE_UPWARD);
r.sup = x.sup - y.inf;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator-(const interval& x, const double& y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x.inf - y;
fesetround(FE_UPWARD);
r.sup = x.sup - y;
fesetround(FE_TONEAREST);
return r;
}
friend interval operator-(const double& x, const interval& y) {
interval r;
fesetround(FE_DOWNWARD);
r.inf = x - y.sup;
fesetround(FE_UPWARD);
r.sup = x - y.inf;
fesetround(FE_TONEAREST);
return r;
}
friend interval& operator-=(interval& x, const interval& y) {
x = x - y;
return x;
}
friend interval& operator-=(interval& x, const double& y) {
fesetround(FE_DOWNWARD);
x.inf = x.inf - y;
fesetround(FE_UPWARD);
x.sup = x.sup - y;
fesetround(FE_TONEAREST);
return x;
}
friend interval operator-(const interval& x) {
interval r;
r.sup = - x.inf;
r.inf = - x.sup;
return r;
}
friend interval operator*(const interval& x, const interval& y) {
interval r;
double tmp;
if (x.inf >= 0) {
if (y.inf >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf * y.inf;
fesetround(FE_UPWARD);
r.sup = x.sup * y.sup;
} else if (y.sup <= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.sup * y.inf;
fesetround(FE_UPWARD);
r.sup = x.inf * y.sup;
} else {
fesetround(FE_DOWNWARD);
r.inf = x.sup * y.inf;
fesetround(FE_UPWARD);
r.sup = x.sup * y.sup;
}
} else if (x.sup <= 0) {
if (y.inf >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf * y.sup;
fesetround(FE_UPWARD);
r.sup = x.sup * y.inf;
} else if (y.sup <= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.sup * y.sup;
fesetround(FE_UPWARD);
r.sup = x.inf * y.inf;
} else {
fesetround(FE_DOWNWARD);
r.inf = x.inf * y.sup;
fesetround(FE_UPWARD);
r.sup = x.inf * y.inf;
}
} else {
if (y.inf >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf * y.sup;
fesetround(FE_UPWARD);
r.sup = x.sup * y.sup;
} else if (y.sup <= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.sup * y.inf;
fesetround(FE_UPWARD);
r.sup = x.inf * y.inf;
} else {
fesetround(FE_DOWNWARD);
r.inf = x.inf * y.sup;
tmp = x.sup * y.inf;
if (tmp < r.inf) r.inf = tmp;
fesetround(FE_UPWARD);
r.sup = x.inf * y.inf;
tmp = x.sup * y.sup;
if (tmp > r.sup) r.sup = tmp;
}
}
fesetround(FE_TONEAREST);
return r;
}
friend interval operator*(const interval& x, const double& y) {
interval r;
if (y >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf * y;
fesetround(FE_UPWARD);
r.sup = x.sup * y;
} else {
fesetround(FE_DOWNWARD);
r.inf = x.sup * y;
fesetround(FE_UPWARD);
r.sup = x.inf * y;
}
fesetround(FE_TONEAREST);
return r;
}
friend interval operator*(const double& x, const interval& y) {
interval r;
if (x >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x * y.inf;
fesetround(FE_UPWARD);
r.sup = x * y.sup;
} else {
fesetround(FE_DOWNWARD);
r.inf = x * y.sup;
fesetround(FE_UPWARD);
r.sup = x * y.inf;
}
fesetround(FE_TONEAREST);
return r;
}
friend interval& operator*=(interval& x, const interval& y) {
x = x * y;
return x;
}
friend interval& operator*=(interval& x, const double& y) {
x = x * y;
return x;
}
friend interval operator/(const interval& x, const interval& y) {
interval r;
if (y.inf > 0) {
if (x.inf >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf / y.sup;
fesetround(FE_UPWARD);
r.sup = x.sup / y.inf;
} else if (x.sup <= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf / y.inf;
fesetround(FE_UPWARD);
r.sup = x.sup / y.sup;
} else {
fesetround(FE_DOWNWARD);
r.inf = x.inf / y.inf;
fesetround(FE_UPWARD);
r.sup = x.sup / y.inf;
}
} else if (y.sup < 0) {
if (x.inf >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.sup / y.sup;
fesetround(FE_UPWARD);
r.sup = x.inf / y.inf;
} else if (x.sup <= 0) {
fesetround(FE_DOWNWARD);
r.inf = x.sup / y.inf;
fesetround(FE_UPWARD);
r.sup = x.inf / y.sup;
} else {
fesetround(FE_DOWNWARD);
r.inf = x.sup / y.sup;
fesetround(FE_UPWARD);
r.sup = x.inf / y.sup;
}
} else {
fesetround(FE_TONEAREST);
throw std::domain_error("interval: division by 0");
}
fesetround(FE_TONEAREST);
return r;
}
friend interval operator/(const interval& x, const double& y) {
interval r;
if (y > 0) {
fesetround(FE_DOWNWARD);
r.inf = x.inf / y;
fesetround(FE_UPWARD);
r.sup = x.sup / y;
} else if (y < 0) {
fesetround(FE_DOWNWARD);
r.inf = x.sup / y;
fesetround(FE_UPWARD);
r.sup = x.inf / y;
} else {
fesetround(FE_TONEAREST);
throw std::domain_error("interval: division by 0");
}
fesetround(FE_TONEAREST);
return r;
}
friend interval operator/(const double& x, const interval& y) {
interval r;
if (y.inf > 0 || y.sup < 0) {
if (x >= 0) {
fesetround(FE_DOWNWARD);
r.inf = x / y.sup;
fesetround(FE_UPWARD);
r.sup = x / y.inf;
} else {
fesetround(FE_DOWNWARD);
r.inf = x / y.inf;
fesetround(FE_UPWARD);
r.sup = x / y.sup;
}
} else {
fesetround(FE_TONEAREST);
throw std::domain_error("interval: division by 0");
}
fesetround(FE_TONEAREST);
return r;
}
friend interval& operator/=(interval& x, const interval& y) {
x = x / y;
return x;
}
friend interval& operator/=(interval& x, const double& y) {
x = x / y;
return x;
}
friend interval sqrt(const interval& x) {
interval r;
if (x.inf < 0) {
throw std::domain_error("interval: sqrt of negative value");
}
fesetround(FE_DOWNWARD);
r.inf = sqrt(x.inf);
fesetround(FE_UPWARD);
r.sup = sqrt(x.sup);
fesetround(FE_TONEAREST);
return r;
}
friend std::ostream& operator<<(std::ostream& s, const interval& x) {
s << '[' << x.inf << ',' << x.sup << ']';
return s;
}
const double& lower() const {
return inf;
}
const double& upper() const {
return sup;
}
double& lower() {
return inf;
}
double& upper() {
return sup;
}
};
#endif // INTERVAL_HPP
使用例は以下の通り。
#include "interval.hpp"
int main()
{
std::cout.precision(17);
// constructors
interval x;
interval y(1);
interval z(2, 3);
x = 1;
y = 10;
std::cout << x / y << "\n";
x = interval(1, 2);
y = interval(3, 4);
// basic four operations
std::cout << x + y << "\n";
std::cout << x - y << "\n";
std::cout << x * y << "\n";
std::cout << x / y << "\n";
// square root
std::cout << sqrt(x) << "\n";
// negate
std::cout << -x << "\n";
// operation with constant
std::cout << x + 1 << "\n";
// compound assignment operator
z += x;
z += 1;
std::cout << z << "\n";
z = interval(3, 4);
// access to endpoints
std::cout << z.lower() << "\n";
std::cout << z.upper() << "\n";
z.lower() = 3.5;
std::cout << z << "\n";
}
実行結果は以下の通り。
$ c++ test-interval.cc
$ ./a.out
[0.099999999999999992,0.10000000000000001]
[4,6]
[-3,-1]
[3,8]
[0.25,0.66666666666666674]
[1,1.4142135623730951]
[-2,-1]
[2,3]
[4,6]
3
4
[3.5,4]
$