% (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;


コメント