HOWTO · C

C 言語での MIN および MAX 関数

マクロまたはループを使用して、C 言語で MIN および MAX 関数を実装できます。

このページの内容

Cで2つの値の小さい方または大きい方を求めるには、MIN または MAX マクロを定義するか、関数を作成します。標準Cには、汎用の組み込み MIN 関数や MAX 関数はありません。配列の場合は、最初の要素で結果を初期化し、残りの要素を順に調べます。

CにはMINとMAXが組み込まれている?

Cの標準ライブラリには、汎用の MIN 関数や MAX 関数は定義されていません。これらの名前は、プロジェクトでマクロまたは通常の関数として用意されることがよくあります。引数に副作用がある場合は関数の方が扱いやすく、評価しても安全な値には単純なマクロが簡潔です。

#define ディレクティブはプリプロセッサマクロを作成します。プリプロセッサはマクロ呼び出しを見つけると、コンパイラがプログラムを処理する前に置換テキストへ展開します。一方、関数呼び出しではコンパイル済みのコードが実行され、引数はCの関数呼び出し規則に従って評価されます。

2つの値を扱うMINとMAXマクロを定義する

副作用のない2つの整数式を扱う場合は、各引数と置換後の式全体を括弧で囲みます。これにより、呼び出し側が a + b のような式を渡しても、意図した式のまとまりが保たれます。

#include <stdio.h>

#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))

int main(void) {
  printf("Min = %d\n", MIN(0, 10));
  printf("Max = %d\n", MAX(0, 10));
  return 0;
}
Min = 0
Max = 10

条件演算子 ?: は、比較が真なら ? の後の式を選び、偽なら : の後の式を選びます。括弧で演算子の優先順位には対処できますが、引数の繰り返し評価は防げません。MIN(a, b) では、選ばれた引数が置換テキスト内でもう一度現れます。i++ のような式や、状態を変更する関数呼び出しを引数に使うのは避けてください。この形式のマクロは単純な値に適しています。引数に副作用がある場合は関数を使いましょう。

引数を一度だけ評価するGNU Cマクロ

GNU Cは文式と typeof(この例では __typeof__ と表記)をサポートしています。次のマクロは各引数を一時変数に保存してから、その値を比較します。各引数の式は初期化時に一度だけ評価されます。これはGNU C拡張であり、ISO Cでは移植できません。また、GNU拡張を有効にするコンパイラオプションが必要な場合があります。

#include <stdio.h>

#define MAX(x, y)                    \
  ({                                 \
    __typeof__(x) _max_x = (x);      \
    __typeof__(y) _max_y = (y);      \
    _max_x > _max_y ? _max_x : _max_y; \
  })

#define MIN(x, y)                    \
  ({                                 \
    __typeof__(x) _min_x = (x);      \
    __typeof__(y) _min_y = (y);      \
    _min_x < _min_y ? _min_x : _min_y; \
  })

int main(void) {
  int a = 4;
  int b = 9;
  printf("Min = %d\n", MIN(a++, b));
  printf("Max = %d\n", MAX(a, b));
  return 0;
}
Min = 4
Max = 9

この方法は、型が < または > で比較できる一般的なスカラー式に役立ちます。あらゆる型に対応する汎用関数ではありません。互換性のない型、意味のある順序を定義できないポインター、これらの演算子をサポートしない型には、別の設計が必要です。また、条件式にはCの通常の型変換規則が適用されます。移植性が必要なコードでは、型を指定した関数を使うか、対応する型ごとに関数を用意してください。

配列の最小値と最大値を求める

配列を走査しながら現在の最小値と最大値を保持し、必要に応じて更新します。実際に配列に含まれる値を基準にできるよう、両方とも最初の要素で初期化します。この例では元の配列例と同じ値を使い、配列を一度だけ走査します。

#include <stdio.h>
#include <stddef.h>

void find_min_max(const int values[], size_t length, int *minimum, int *maximum) {
  *minimum = values[0];
  *maximum = values[0];

  for (size_t i = 1; i < length; ++i) {
    if (values[i] < *minimum) {
      *minimum = values[i];
    }
    if (values[i] > *maximum) {
      *maximum = values[i];
    }
  }
}

int main(void) {
  int values[] = {6, 3, 15, 9, 10, 50, 99, 11};
  size_t length = sizeof(values) / sizeof(values[0]);
  int minimum;
  int maximum;

  find_min_max(values, length, &minimum, &maximum);
  printf("Max number = %d\n", maximum);
  printf("Min number = %d\n", minimum);
  return 0;
}
Max number = 99
Min number = 3

この関数には、空でない配列と有効な結果ポインターが必要です。配列引数はポインターに調整されるため、呼び出し側から長さを渡します。find_min_max の中で sizeof(values) を使うと、元の配列ではなくポインターのサイズが測定されます。この例では、values がまだ配列である main の中で sizeof を使い、配列全体のサイズを要素1つのサイズで割って要素数を求めます。

浮動小数点データを扱う場合は、配列、最小値と最大値の変数、関数の引数で同じ型を使います。printf の変換指定子は、昇格後の引数の型に合わせてください。double には %f を使います。複数の数値型を受け付ける関数が必要なら、int を float に置き換えるだけではなく、型に応じた関数を用意するか、慎重に設計した抽象化を使ってください。