プログラミング演習1 課題3 数値解析法

1. Cによる数値解析のための準備1

数学関数

Cには標準ライブラリの中に数学関数が用意されており、ヘッダ <math.h> をインクルードすることで使用可能となる。ヘッダ <math.h>内には下記の関数などが具体的に宣言されている。

sin(x) 三角関数 \( sin(x) \)を計算する。
cos(x) 三角関数 \( cos(x) \)を計算する。
tan(x) 三角関数 \( tan(x) \)を計算する。
asin(x) 逆三角関数 \( sin^{-1}(x) \) を計算する。\( (-1 \le x \le 1) \)
acos(x) 逆三角関数 \( cos^{-1}(x) \)を計算する。\( (-1 \le x \le 1) \)
atan(x) 逆三角関数 \( tan^{-1}(x) \)を計算する。\( (-\infty \le x \le \infty) \)
atan2(y,x)逆三角関数 \( tan^{-1}(y/x) \)を計算する。
sinh(x) 双曲線関数 \( sinh(x) \)を計算する。
cosh(x) 双曲線関数 \( cosh(x) \)を計算する。
tanh(x) 双曲線関数 \( tanh(x) \)を計算する。
exp(x) 指数関数 \( e^{x} \) を計算する。
log(x) 自然対数 \( \ln x \) を計算する。\( (x \gt 0) \)
log10(x) 10 を底とする対数 \( \log_{10} x \) を計算する。\( (x \gt 0) \)
pow(x,y) べき乗 \( x^{y} \) を計算する。
sqrt(x) 実数\( x \)の平方根を求める。\( (x \gt 0) \)
ceil(x) 実数\( x \)の小数部を切り上げた整数を返す。
floor(x) 実数\( x \)の小数部を切り捨てた整数を返す。
fabs(x) 絶対値 \( |x| \) を求める。
ldexp(x,n)\(x・2^{n} \) を計算する。
frexp(x,&exp)実数\( x \)を仮数部と指数部(2のべき乗)\( exp \)に分解する。
modf(x,&ip)実数\( x \)を整数部\( ip \)と小数部に分ける。
fmod(x,y) 実数の剰余( x % y に相当)を求める。

練習1: 数学関数による様々な計算

以下のコードの(1)と(2)に値を入力して数学関数を使った計算を実行しよう。

#include <stdio.h>
#include <math.h>

int main(void)
{
    double x=__(1)__ ,y=__(2)__ ;

    printf("x = %f, y = %f\n", x, y);
    printf("x^y = %f\n", pow(x,y));
    printf("atan2(y/x) = %f\n", atan2(y, x));
    printf("sqrt(x*x+y*y) = %f\n", sqrt(pow(x,2) + pow(y,2)));
    return 0;
}
[実行結果(例)]
-----------------------
x = 3.000000, y = 4.000000
x^y = 81.000000
atan2(y/x) = 0.927295
sqrt(x*x+y*y) = 5.000000
-----------------------
x = 5.000000, y = 12.000000
x^y = 244140625.000000
atan2(y/x) = 1.176005
sqrt(x*x+y*y) = 13.000000   
\(sqrt(x^2+y^2)\)で計算される値は何でしょうか?また、\( tan^{-1}(y/x) \)の値をラジアンから度数にしてみよう。

数に関するユーティリティ

ヘッダ<stdlib.h>には、いくつかの有用な数値変換関数や整数乱数発生関数が宣言されている。

atof(s) 文字列 s を double型浮動小数点数に変換する。
atoi(s) 文字列 s を int型整数に変換する。
atol(s) 文字列 s を long型整数に変換する。
rand() 0~RAND_MAXの範囲の整数乱数を発生する。
srand(seed)rand() の種として seed をセットする。
abs(n) int型整数 \(n\) の絶対値 \(|n|\) を返す。
labs(n) long型整数 \(n\) の絶対値 \(|n|\) を返す。

浮動小数点数に関する定数

ヘッダ <float.h>には浮動小数点数に関する様々な定数が定義されている。

単精度
_FLT_RADIX 指数表現における基数
_FLT_ROUNDS 丸めのモード
FLT_DIG 精度の10進けた数
FLT_EPSILON 計算機イプシロン
FLT_MANT_DIG浮動小数点数の有効数字部(_FLT_RADIXを基数とする)のけた数
FLT_MAX 表現可能な最大の有限浮動小数点数
FLT_MAX_EXP 表現可能な数の指数の最大値
FLT_MIN 最小の正規化された正の浮動小数点数
FLT_MIN_EXP 最小正規化数の指数の値
倍精度
_DBL_RADIX 指数表現における基数
_DBL_ROUNDS 丸めのモード
DBL_DIG 精度の10進けた数
DBL_EPSILON 計算機イプシロン
DBL_MANT_DIG浮動小数点数の有効数字部(_DBL_RADIXを基数とする)のけた数
DBL_MAX 表現可能な最大の有限浮動小数点数
DBL_MAX_EXP 表現可能な数の指数部の最大値
DBL_MIN 最小の正規化された正の浮動小数点数
DBL_MIN_EXP 最小正規化数の指数部の値

練習2: 浮動小数点数のビットサイズと制限の確認

基本的に、数値計算には倍精度のdouble型を用いる。以下のコードを実行して、float型とdouble型の違いを確認しよう。

#include <stdio.h>
#include <float.h>

int main(void)
{
    printf("float: %u bit, double: %u bit\n", sizeof(float)*8, sizeof(double)*8);
    printf("FLT_MAX =     % e\n", FLT_MAX);
    printf("FLT_MIN =     % e\n", FLT_MIN);
    printf("DBL_MAX =     % e\n", DBL_MAX);
    printf("DBL_MIN =     % e\n", DBL_MIN);

    float f1  = 3.14159265358979323846;  // float
    double d1 = 3.14159265358979323846; // double
    printf("Value of f1 (float): %.20f\n", f1);
    printf("Value of d1 (double): %.20lf\n", d1);

    return 0;
}
[実行結果]
-----------------------
float: 32 bit, double: 64 bit
FLT_MAX =      3.402823e+038
FLT_MIN =      1.175494e-038
DBL_MAX =      1.797693e+308
DBL_MIN =      2.225074e-308
Value of f1 (float): 3.14159274101257324219
Value of d1 (double): 3.14159265358979311600

プログラムで間違いやすい分数式

(1) プログラムでは分数式のまま記述することができないので、割り算で代用することになる。その場合に分子・分母に括弧をつけなければならなくなるので注意すること。

たとえば、\(\displaystyle \frac{a}{b}\) は a/b でよいが、\(\displaystyle \frac{a+b}{c}\) は a+b/c ではなく、(a+b)/c のようにカッコを付ける。 同様に、\(\displaystyle \frac{a}{b+c}\) は a/b+c ではなく a/(b+c)、\(\displaystyle \frac{a}{bc}\) は a/b*c ではなく a/(b*c) となる。

(2) 整数どうしの割り算は整数除算を行うので、実数除算にするためには分子か分母を実数化する必要がある(※プログラミング入門Iでも説明)。整数値を実数化するには小数点をつければよい。

#include <stdio.h>

int main(void)
{
    double x1, x2, x3, x4;

    x1 = 1 / 2;   /* 整数除算 */
    x2 = 1./ 2;   /* 分子を実数化 */
    x3 = 1 / 2.;  /* 分母を実数化 */
    x4 = 1./ 2.;  /* 分子・分母を実数化 */

    printf("x1 = %f, x2 = %f, x3 = %f, x4 = %f\n", x1, x2, x3, x4);
    return 0;
}
[実行結果]
-----------------------
x1 = 0.000000, x2 = 0.500000, x3 = 0.500000, x4 = 0.500000
-----------------------

練習3: 型キャスト

上記の他に整数変数を実数化するための方法としては、キャスト演算子(double)などを用いた型変換がある。以下のコードを実行して、計算結果を確認してみよう。

#include <stdio.h>

int main(void)
{
    int a=1, b=2;
    double x1, x2, x3, x4;

    x1 = a / b;                  /* 整数除算 */
    x2 = (double)a / b;          /* 分子を実数化 */
    x3 = a / (double)b;          /* 分母を実数化 */
    x4 = (double)a / (double)b;  /* 分子・分母を実数化 */

    printf("a = %d, b = %d\n", a, b); 
    printf("x1 = %f, x2 = %f, x3 = %f, x4 = %f\n", x1, x2, x3, x4);
    return 0;
}
[実行結果]
-----------------------
a = 1, b = 2
x1 = 0.000000, x2 = 0.500000, x3 = 0.500000, x4 = 0.500000
-----------------------

for文による等間隔実数列の生成(復習)

関数をグラフで表すと、xとyの関係を理解する大きな手助けとなる。このため、数値解析では一定間隔に刻んだ数列を用いて計算することが多い。以下は、柴田望洋「新版 明解C言語 入門編」の第7章にある、 0 から1 まで 0.01 間隔の実数列を生成するプログラム List 7-9 を少し手直ししたものである。

/*
   0 から1 まで 0.01 間隔の実数列を生成する。
*/
#include <stdio.h>

int main(void)
{
    float x;

    for (x=0; x<=1; x+=0.01) printf(" x = %g\n", x);

    return 0;
}
[実行結果]
-----------------------
 x = 0
 x = 0.01
 x = 0.02
 ...
 x = 0.979999
 x = 0.989999
 x = 0.999999
-----------------------

このように、実数(単精度浮動小数点数)を直接、for文で増減させると、刻み幅(上記の場合は0.01)を2進数にしたときの丸め誤差(表現誤差)が累積されて次第に値を正確に刻んでくれなくなる場合が多い。このようなことを避けるためにも、for文は必ず整数で制御するべきである。

/*
   0 から1 まで 0.01 間隔の実数列を生成する。(ループを整数で制御)
*/
#include <stdio.h>

int main(void)
{
    int i;
    float x;

    for (i=0; i<=100; i++) {
        x = i / 100;
        printf(" x = %g\n", x);
    }
    return 0;
}

練習4: 等間隔実数列の生成

上記のプログラムは、前述の考え方を適用した「明解C言語」の第7章 List 7-10とほぼ同じものであるが、整数除算になっているため0しか出力されない。以下の実行結果になるように修正しよう。

[実行結果]
-----------------------
 x =  0
 x =  0.01
 x =  0.02
 ...
 x =  0.98
 x =  0.99
 x =  1
-----------------------

【目次】 | 【2.】| 【3.】