【C言語】分数の四則計算・比較

#0. はじめに
C言語には分数ライブラリが無いので作ってみました。
なるべくコードが簡単になるように書きましたので、
各種エラー処理（0割り、オーバーフローなど）は豪快に省略しています。
また、ざっくりとしかテストしていません・・・。
帯分数には対応していませんが簡単な改修で実現できるかと思います。

今回は題材がシンプルなため、説明少なめ、コード多めです。

**※記事の更新履歴は「9. 更新履歴」をご参照ください。**

#1. 開発環境
OS  : Windows10 Home
IDE : Visual Studio 2019

#2.分数を表す構造体
特に言うこと無し！

```c:frac.h
// 分数構造体
typedef struct {
	int nume;	// 分子
	int deno;	// 分母
} frac_t;
```


#2. 最大公約数・最小公倍数
このあと出てくる約分・通分の処理に必要なため先に作り置きしておきます。
実装の形はいろいろあるかと思いますが、キュッとまとめれば
一行で書けるのでマクロにしてもいいかもです。

```c:frac.c
// ※非公開関数として定義

// 最大公約数を求める
static int gcd( int a, int b )
{
	return ( ( a % b ) == 0 ) ? b : gcd( b, ( a % b ) );
}

// 最小公倍数を求める
static int lcm( int a, int b )
{
	return  ( a * b ) / gcd( a, b );
}

```

#3. 通分・約分
このあと出てくる四則計算の仕上げのために約分用関数を、比較のために通分用関数を定義。
通分用関数は、比較元の分数を変更しないように読み取り専用で受け取るようにしています。

```c:frac.c
#include <math.h> // for abs()

// ※非公開関数として定義

// 約分する
static void frac_simplify( frac_t* frac )
{
	if ( frac->nume != 0 ) {
		int div = abs( gcd( frac->nume, frac->deno ) );
		frac->nume /= div;
		frac->deno /= div;
	}
}

// 通分する
static void frac_reduction(
	const frac_t* a,
	const frac_t* b,
	frac_t* an,
	frac_t* bn )
{
	int deno = lcm( a->deno, b->deno );

	// 分母がマイナスの場合の処置
	int sign = ( deno < 0 ) ? -1 : 1;

	an->nume = sign * a->nume * ( deno / a->deno );
	an->deno = sign * deno;

	bn->nume = sign * b->nume * ( deno / b->deno );
	bn->deno = sign * deno;
}
```
#4. 四則計算
足し算と引き算は当然、通分が必要になり、
さっき作った通分用関数`frac_reduction()`を使えばよさそうですが、
下の考えた方をまんまコードに落とした方が綺麗になる（？）ので採用。


**足し算の場合**

```math
\frac{a}{b} + \frac{c}{d} = \frac{da}{db} + \frac{cb}{db} = \frac{da + cb}{db}
```
いずれの関数も総仕上げに約分用関数`frac_simplify()`でシュッとしてからreturn!!

```c:frac.c

// ※公開関数として定義
// !!CAUTION!!　オーバーフロー未対策のため、呼び出し元でケアする必要あり。

// a + b
frac_t frac_add( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = ( b->deno * a->nume ) + ( b->nume * a->deno );
	frac.deno = ( b->deno * a->deno );
	frac_simplify( &frac );
	return frac;
}

// a - b
frac_t frac_sub( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = ( b->deno * a->nume ) - ( b->nume * a->deno );
	frac.deno = ( b->deno * a->deno );
	frac_simplify( &frac );
	return frac;
}

// a * b
frac_t frac_mul( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = a->nume * b->nume;
	frac.deno = a->deno * b->deno;
	frac_simplify( &frac );
	return frac;
}

// a / b
frac_t frac_div( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = a->nume * b->deno;
	frac.deno = a->deno * b->nume;
	frac_simplify( &frac );
	return frac;
}
```

#5. 比較
C++と違い演算子のオーバーロード不可のため、
`==`、`!=`、`<`、`<=`、`>`、`>=`相当の関数を定義。
またC#の`CompareTo()`チックな`frac_compare()`もついでに作りました。
比較方法はいろいろあるかと思いますが、今回は通分用関数`frac_reduction()`で
分母を揃えてから分子同士で比較しました。

```c:frac.c

// ※公開関数として定義

// a == b：真の場合は0以外、偽の場合は0を返す
int frac_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume == bn.nume;
}

// a != b：真の場合は0以外、偽の場合は0を返す
int frac_not_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume != bn.nume;
}

// a < b：真の場合は0以外、偽の場合は0を返す
int frac_less( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume < bn.nume;
}

// a <= b：真の場合は0以外、偽の場合は0を返す
int frac_less_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume <= bn.nume;
}

// a > b：真の場合は0以外、偽の場合は0を返す
int frac_greater( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume > bn.nume;
}

// a >= b：真の場合は0以外、偽の場合は0を返す
int frac_greater_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume >= bn.nume;
}

// aとbの比較：a > bの場合は1、a < bの場合は-1、a == bの場合は0を返す
int frac_compare( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return
		an.nume > bn.nume ? 1 :
		an.nume < bn.nume ? -1 : 0;
}
```

#6. 分数構造体の初期化、関数の使い方
特に言うこと無し！

```main.c
#include <stdio.h>
#include "frac.h"

int main( void )
{
	frac_t a = { 1, 2 };	// 分子=1、分母=2の分数
	frac_t b = { 2, 3 };	// 分子=2、分母=3の分数

	frac_t x = { 3, 0 };	// 分母=0はだめ！

	frac_t ab_add = frac_add( &a, &b );
	frac_t ab_sub = frac_sub( &a, &b );
	frac_t ab_mul = frac_mul( &a, &b );
	frac_t ab_div = frac_div( &a, &b );

	if ( frac_eq( &a, &b ) ) {
		// a == bの時の処理
		// ※他の比較関数も同様の使い方
	}

	int ab_cmp = frac_compare( &a, &b );
	if ( ab_cmp > 0 ) {
		// a > bの時の処理
	} else if ( ab_cmp < 0 ) {
		// a < bの時の処理
	} else {
		// a == bの時の処理
	}

	return 0;
}
```

#7. ソースコード全体
<details><summary>frac.h</summary><div>

```c:frac.h
#ifndef _FRAC_H_
#define _FRAC_H_

//===================================================================
// 構造体定義
//===================================================================
// 分数構造体
typedef struct {
	int nume;	// 分子
	int deno;	// 分母
} frac_t;

//===================================================================
// 公開関数のプロトタイプ
//===================================================================
// 四則計算
// !!CAUTION!!　オーバーフロー未対策のため、呼び出し元でケアする必要あり。
frac_t frac_add( const frac_t* a, const frac_t* b );		// a + b
frac_t frac_sub( const frac_t* a, const frac_t* b );		// a - b
frac_t frac_mul( const frac_t* a, const frac_t* b );		// a * b
frac_t frac_div( const frac_t* a, const frac_t* b );		// a / b

// 比較関数
int frac_eq( const frac_t* a, const frac_t* b );			// a == b
int frac_not_eq( const frac_t* a, const frac_t* b );		// a != b
int frac_less( const frac_t* a, const frac_t* b );			// a <  b
int frac_less_eq( const frac_t* a, const frac_t* b );		// a <= b
int frac_greater( const frac_t* a, const frac_t* b );		// a >  b
int frac_greater_eq( const frac_t* a, const frac_t* b );	// a >= b
int frac_compare( const frac_t* a, const frac_t* b );		// 大小比較

#endif

```
</div></details>

<details><summary>frac.c</summary><div>

```c:frac.c
//===================================================================
// インクルード
//===================================================================
#include <math.h>	// for abs()
#include "frac.h"

//===================================================================
// 非公開関数のプロトタイプ
//===================================================================
static int gcd( int a, int b );				// 最大公約数を求める
static int lcm( int a, int b );				// 最小公倍数を求める
static void frac_simplify( frac_t* frac );	// 約分する
static void frac_reduction(					// 通分する
	const frac_t* a,
	const frac_t* b,
	frac_t* an,
	frac_t* bn );

//===================================================================
// 公開関数の定義
//===================================================================
//===================================================================
// 四則計算
// !!CAUTION!!　オーバーフロー未対策のため、呼び出し元でケアする必要あり。
//===================================================================
//-------------------------------------------------------------------
// a + b
//-------------------------------------------------------------------
frac_t frac_add( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = ( b->deno * a->nume ) + ( b->nume * a->deno );
	frac.deno = ( b->deno * a->deno );
	frac_simplify( &frac );
	return frac;
}

//-------------------------------------------------------------------
// a - b
//-------------------------------------------------------------------
frac_t frac_sub( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = ( b->deno * a->nume ) - ( b->nume * a->deno );
	frac.deno = ( b->deno * a->deno );
	frac_simplify( &frac );
	return frac;
}

//-------------------------------------------------------------------
// a * b
//-------------------------------------------------------------------
frac_t frac_mul( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = a->nume * b->nume;
	frac.deno = a->deno * b->deno;
	frac_simplify( &frac );
	return frac;
}

//-------------------------------------------------------------------
// a / b
//-------------------------------------------------------------------
frac_t frac_div( const frac_t* a, const frac_t* b )
{
	frac_t frac;
	frac.nume = a->nume * b->deno;
	frac.deno = a->deno * b->nume;
	frac_simplify( &frac );
	return frac;
}

//===================================================================
// 比較関数
//===================================================================
//-------------------------------------------------------------------
// a == b
//-------------------------------------------------------------------
int frac_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume == bn.nume;
}

//-------------------------------------------------------------------
// a != b
//-------------------------------------------------------------------
int frac_not_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume != bn.nume;
}

//-------------------------------------------------------------------
// a < b
//-------------------------------------------------------------------
int frac_less( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume < bn.nume;
}

//-------------------------------------------------------------------
// a <= b
//-------------------------------------------------------------------
int frac_less_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume <= bn.nume;
}

//-------------------------------------------------------------------
// a > b
//-------------------------------------------------------------------
int frac_greater( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume > bn.nume;
}

//-------------------------------------------------------------------
// a >= b
//-------------------------------------------------------------------
int frac_greater_eq( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return an.nume >= bn.nume;
}

//-------------------------------------------------------------------
// 大小比較
// a >  b ->  1
// a == b ->  0
// a <  b -> -1
//-------------------------------------------------------------------
int frac_compare( const frac_t* a, const frac_t* b )
{
	frac_t an, bn;
	frac_reduction( a, b, &an, &bn );
	return
		an.nume > bn.nume ? 1 :
		an.nume < bn.nume ? -1 : 0;
}

//===================================================================
// 非公開関数の定義
//===================================================================
//-------------------------------------------------------------------
// 最大公約数を求める
//-------------------------------------------------------------------
static int gcd( int a, int b )
{
	return ( ( a % b ) == 0 ) ? b : gcd( b, ( a % b ) );
}

//-------------------------------------------------------------------
// 最小公倍数を求める
//-------------------------------------------------------------------
static int lcm( int a, int b )
{
	return  ( a * b ) / gcd( a, b );
}

//-------------------------------------------------------------------
// 約分する
//-------------------------------------------------------------------
static void frac_simplify( frac_t* frac )
{
	if ( frac->nume != 0 ) {
		int div = abs( gcd( frac->nume, frac->deno ) );
		frac->nume /= div;
		frac->deno /= div;
	}
}

//-------------------------------------------------------------------
// 通分する
//-------------------------------------------------------------------
static void frac_reduction(
	const frac_t* a,
	const frac_t* b,
	frac_t* an,
	frac_t* bn )
{
	int deno = lcm( a->deno, b->deno );

	// 分母がマイナスの場合の処置
	int sign = ( deno < 0 ) ? -1 : 1;

	an->nume = sign * a->nume * ( deno / a->deno );
	an->deno = sign * deno;

	bn->nume = sign * b->nume * ( deno / b->deno );
	bn->deno = sign * deno;
}
```
</div></details>

#8. おわりに
正直かなりえいやっ！で作りましたので、穴だらけ無駄だらけかと思いますが、
コードの見てくれだけはまぁまぁ綺麗にまとめられたと考えますので:o:としておきます。

お叱りツッコミお待ちしておりまーす:hotsprings:

#9. 更新履歴
**■2021/02/02**
コメント欄のご指摘を受け、以下を修正（@Nabetani様ありがとうございました）。
・分母が負数の場合の比較関数の結果が期待通りで無いため、`frac_reduction()`を修正。
・四則計算関数がオーバーフロー未対応のため、注意喚起のコメントを追記。