C言語で学ぶ大学の数学 準備編「有理数を有理数として扱う」

この記事では、C言語において有理数を整数比として厳密に扱い、四則演算をする方法について解説します。また分数の有理化のためにユークリッドの互除法についても解説します。



1.有理数を整数比で扱う意義


有理数を扱う際、何も意識しないのであれば float 型や double 型を使用すると思います。

基本的にはそれで問題ないと思いますが、何らかの方程式の解を求めるとき、


・解が有理数であることが分かっている、かつ

・解を厳密に計算したい


ときには有理数を整数比を使って扱うほうが適しています。

例えば有理数係数の連立一次方程式の解は、有理数の組です。その解を厳密に求めたいときなどは、以下で紹介する方法で四則演算を計算します。



2.整数比の格納


整数比をC言語で格納するには、int 型やshort 型など整数を格納する配列変数を二つ用意します。

例えば 3/5 という分数は


int a[2] = {3, 5};


のように分子、分母の順に格納します。また負の数の場合は分母は正と約束します。

例えば -12/5 は


int a[2] = {-12, 5};


とします。整数の場合は分母を 1 としておきます。例えば 3 は


int a[2] = {3, 1};


です。



3.ユークリッドの互除法


分数の四則演算の前に、分数を有理化するユーザ関数を定義します。

そのためにまずはユークリッドの互除法をユーザ関数で定義します。

ユークリッドの互除法とは、二つの整数 n, m の最大公約数を求めるアルゴリズムです。

n と m のうち大きいほうを小さいほうで割り、割られたほうの数を余りで置き換えます。これを一方が 0 になるまで繰り返したとき、最後に残った 0 でない方の数が n と m の最大公約数です。

これをプログラムとして実行するには次のようなソースコードを書きます。


int euclid(int n, int m){

 if(n<0) n = -n;

 if(m<0) m = -m;

 while(n!=0 && m!=0){

  if(n > m) n = n%m;

  else m = m%n;

 }

 return n + m;

}


二行目と三行目は入力した整数が負の場合に絶対値をとっています。有理化だけに使用するのであれば三行目は省略しても大丈夫です。

四行目から七行目までが計算のステップです。while の繰り返しが終わると n か m が0 になっているので、計算結果として n+m を返せば「0でない方の数」を返していることになります。

これを使えば分数の有理化は次のようにできます。


void rationaliz(int a[2]){

 int gcd = euclid(a[0], a[1]);

 a[0] = a[0] / gcd;

 a[1] = a[1] / gcd;

}


rationaliz の中にユークリッドの互除法を組み込んでしまうことももちろん可能です。


void rationaliz(int a[2]){

 int n = abs(a[0]);

 int m = abs(a[1]);

 while(n!=0 && m!=0){

  if(n > m) n = n%m;

  else m = m%n;

 }

 a[0] = a[0] / (n+m);

 a[1] = a[1] / (n+m);

}


ただし絶対値関数 abs を使うので stdlib.h をインクルードする必要があります。

上で説明した通り、最終的な n+m が元の n と m の最大公約数です。ちなみに n と m が同時に 0 になることは無いので安心してください。



4.加法と減法


それではまずは加法から計算方法を説明します。

a[] と b[] の表す分数の和をc[] に代入することを考えます。

a/b + c/d = (a*d + b*c)/(b*d) であることを用いて和を計算し、それを有理化した値を代入するユーザ関数を定義します。


void add_ratio(int a[2], int b[2], int c[2]){

 c[0] = a[0]*b[1] + a[1]*b[0];

 c[1] = a[1]*b[1];

 rationaliz(c);

}


減法についても同様です。a/b - c/d = (a*d - b*c)/(b*d) であることより、a[]の表す分数からb[] の表す分数を引いたものをc[]に代入するユーザ関数は


void sub_ratio(int a[2], int b[2], int c[2]){

 c[0] = a[0]*b[1] - a[1]*b[0];

 c[1] = a[1]*b[1];

 rationaliz(c);

}


と定義できます。sub は subtraction の意味です。



5.乗法と除法


乗法については、たんに分子どうしの積と分母どうしの積を計算して有理化するだけです。


void prod_ratio(int a[2], int b[2], int c[2]){

 c[0] = a[0]*b[0];

 c[1] = a[1]*b[1];

 rationaliz(c);

}


除法については注意すべきことがあります。a[]をb[]で割る際、b[]の表す有理数が負の値になる場合、c[]の分母が負になってしまうことに注意します。(b[]が0になる場合は別の処理が必要です)


void div_ratio(int a[2], int b[2], int c[2]){

 c[0] = a[0]*b[1];

 c[1] = a[1]*b[0];

 if(c[1]<0){

  c[0] = -c[0];

  c[1] = -c[1];

 }

 rationaliz(c);

}



6.使い方


最後に、上で定義したユーザ関数を使って簡単な分数の計算をしてみます。

(39/16 + 4/7)*1/3 を計算することとします。

sub_ratio と div_ratio は今回は使わないので省略します。



#include<stdio.h>

#include<stdlib.h>


void rationaliz(int a[2]){

  (省略)

}


void add_ratio(int a[2], int b[2], int c[2]){

  (省略)

}


void prod_ratio(int a[2], int b[2], int c[2]){

  (省略)

}


int main(){

 int a[2] = {39, 16};

 int b[2] = {4, 7};

 int c[2] = {1, 3};

 int answer[2];

 add_ratio(a, b, answer);

 prod_ratio(answer, c, answer);

 printf("%d/%d\n", answer[0], answer[1]); //ここで計算結果を表示

 return 0;

}


正しい答えは 337/336 です。


(ちなみに

prod_ratio(answer, c, answer);

という書き方をしていますが、仮にもしユーザ関数に


void prod_ratio(int a[2], int b[2], int c[2]){

 c[0] = a[0]*b[0];

 c[1] = a[1]*b[1];

 rationaliz(c);

 a[0] = 1;  //余計なもの

}


というふうに余計なものを書き足した場合、answer[0]の値を最後に変更するのがこの場所になるので、計算結果が間違ったものになってしまいます。

この仕様はvoidを返り値とするユーザ関数を使うときには気を付けたいところです。)


  • Twitterで共有
  • Facebookで共有
  • はてなブックマークでブックマーク

作者を応援しよう!

ハートをクリックで、簡単に応援の気持ちを伝えられます。(ログインが必要です)

応援したユーザー

応援すると応援コメントも書けます

新規登録で充実の読書を

マイページ
読書の状況から作品を自動で分類して簡単に管理できる
小説の未読話数がひと目でわかり前回の続きから読める
フォローしたユーザーの活動を追える
通知
小説の更新や作者の新作の情報を受け取れる
閲覧履歴
以前読んだ小説が一覧で見つけやすい
新規ユーザー登録無料

アカウントをお持ちの方はログイン

カクヨムで可能な読書体験をくわしく知る