% (remainder) の解説と実装リファレンス

記事内には広告が含まれています。

% (remainder) の解説と実装リファレンス

【概要】

Arduino(C/C++)における % 演算子は、整数の除算における剰余(Remainder)を求める演算子です。ある数(被除数)を別の数(除数)で割った際に、計算の最後に残る余りを算出します。

この演算子は、数学的な「モジュロ演算(Modulo)」と混同されやすいですが、C言語の仕様に基づいているため、「符号の挙動」において重要な違いがあります。

【仕様】

  • 演算子: % (Remainder operator)
  • 引数: 2つの整数型(int, long, short, byte, char 等)
  • 戻り値: 被除数と同じ型での剰余値
  • 制約: 除数(右辺)に 0 を指定した場合は、実行時にゼロ除算(Division by zero)となり、システムがハングアップまたは異常終了する可能性があるため、厳禁です。
  • データ型: 整数型にのみ適用可能です。float や double を対象とする場合は fmod() 関数を使用する必要があります。

【技術的特徴】

1. 符号の挙動(Remainder vs Modulo)

C/C++の % 演算子の仕様では、「剰余の符号は被除数(左辺)の符号に従う」というルールがあります。
– 7 % 3 = 1
– -7 % 3 = -1(数学的なモジュロでは 2 となる場合がある)
– 7 % -3 = 1
– -7 % -3 = -1

2. コンパイラによる最適化

除数が 2の累乗(2, 4, 8, 16…) である場合、多くのコンパイラ(AVR, ESP32, ARM等)は % 演算子を内部的にビット演算(AND演算)へと最適化します。
例: x % 8 は x & 7 と等価に処理されるため、極めて高速に実行されます。

3. 浮動小数点数との比較

float 型に対して % を使用しようとするとコンパイルエラーとなります。浮動小数点数の剰余が必要な場合は、数学ライブラリの fmod(x, y) を使用します。

【注意点】

  • ゼロ除算の回避:
    動的な計算結果を右辺に置く場合、除数が 0 にならないことを保証するか、事前にチェックするロジックが必要です。
    cpp
    if (divisor != 0) {
    remainder = dividend % divisor;
    }
  • リソース制約と計算コスト:
    AVR(Arduino Uno等)のような8ビットマイコンでは、除数が2の累乗でない場合、剰余の計算は比較的重い処理となることがあります。可能な限り除数を2の累乗に設計することが推奨されます。
  • 符号の扱い:
    周期的なインデックス計算(例:配列の循環参照)において、負の数を含む可能性がある場合は、単に % を使うのではなく、正の範囲に補正する処理が必要です。
    index = (i % size + size) % size;

【サンプルコード】

void setup() {
  Serial.begin(9600);

  int a = 10;
  int b = 3;

  // 基本的な動作
  int res1 = a % b; // 10 % 3 = 1
  Serial.print("10 % 3 = ");
  Serial.println(res1);

  // 負の数の挙動
  int c = -10;
  int res2 = c % b; // -10 % 3 = -1
  Serial.print("-10 % 3 = ");
  Serial.println(res2);

  // 浮動小数点数の場合 (fmodを使用)
  float x = 10.5;
  float y = 3.0;
  float res3 = fmod(x, y); // 10.5 % 3.0 = 1.5
  Serial.print("10.5 fmod 3.0 = ");
  Serial.println(res3);
}

void loop() {}

【活用例】

  • 周期的なLED点滅:
    millis() を使用して、特定の周期(例:1000ms)ごとに処理を繰り返す際、現在のミリ秒を周期で割った剰余を利用します。
    if (millis() % 1000 < 50) { digitalWrite(LED_PIN, HIGH); }
  • 円形バッファ(Circular Buffer):
    データの書き込み位置を管理する際、バッファサイズで剰余を取ることで、インデックスを常に範囲内に収めます。
    buffer_index = (buffer_index + 1) % BUFFER_SIZE;
  • 単位変換:
    「秒」を「分」と「秒」に分解する処理。
    int minutes = totalSeconds / 60;
    int remainingSeconds = totalSeconds % 60;

コメント

タイトルとURLをコピーしました