グレイコードは、隣り合う値で必ず1ビットだけが変わる特別な並べ方です。
機械の位置や角度を安全に読み取るためによく使われ、ノイズや途中読み取りの誤りに強いことが特徴です。
この記事では、基礎、作り方、2進数との変換、そしてPython・JavaScript・Cでの実装までを、初心者向けにやさしく順を追って解説します。
グレイコードとは?基礎と仕組み
グレイコード(Gray code)の特徴
グレイコードは、連続する値が1つのビットだけ異なるように並べたコードです。
通常の2進数では1を足すと複数ビットが同時に変わる場合がありますが、グレイコードでは隣り合う2つのコードのハミング距離が常に1になります。
これにより、中途半端な読み取りの瞬間に複数ビットが食い違っても誤差が小さく済みます。
グレイコードは反射型バイナリコードとも呼ばれ、配列全体が左右反転のような規則で構成されています。
さらに多くの場合、列の最後と最初も1ビット差になるように並べた循環の性質も持ちます。
この規則性が、ハードウェアでの確実な読み取りに役立ちます。
2進数との違い
2進数は桁上がりのときに複数のビットが一度に変わることがあります。
たとえば3ビットの2進数では、011(3)から100(4)に進むと、3ビットすべてが変化します。
一方のグレイコードでは隣同士の変化が常に1ビットなので、読み取り途中の不整合の影響が小さくなります。
下の表は3ビットの対応例です。
2進数とグレイコードの並びを比べると、グレイコードの隣同士は常に1ビット差でつながっています。
| 10進 | 2進数(3bit) | グレイコード(3bit) |
|---|---|---|
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |
| 5 | 101 | 111 |
| 6 | 110 | 101 |
| 7 | 111 | 100 |
この並びでは上から下へ順に見ても、1行ごとに変わるビットは常に1つだけです。
1ビットだけ変わる仕組み
グレイコードが1ビットだけ変わるのは、並べ方に工夫があるからです。
基本の考え方は反射と接頭辞の付与です。
まず短い長さのグレイ列を作り、それを反転して並べ、前半に0、後半に1を頭に付けます。
この操作により、前半の最後と後半の最初の境目でも1ビット差が保たれます。
ハミング距離とは
ハミング距離とは、2つの同じ長さのビット列を比べたときに異なる桁の数のことです。
グレイコードでは隣り合うコード間のハミング距離が必ず1です。
この距離1という制約が誤読リスクを小さくします。
グレイコードの作り方と変換
1ビット→2ビット→3ビットの作り方
最初に1ビットのグレイコードを考えると、列はとても単純です。
0と1の2つだけで、この2つは1ビット差です。
これを元に2ビット、3ビットへと増やしていきます。
手順のイメージ
0, 1 2ビット: 00, 01, 11, 10 3ビット: 000, 001, 011, 010, 110, 111, 101, 100
このように、ビット数を1つ増やすたびに、前の列を利用して新しい列が作れます。
反射で増やす作り方
反射の作り方は次のとおりです。
まず、既存のグレイ列をそのまま書き写し、その各要素の頭に0を付けます。
次に、同じ列を逆順にして、その各要素の頭に1を付けて後ろに続けます。
これで新しいビット数のグレイ列が完成します。
前半の最後と後半の最初がちょうど1ビット差になるように接続されるのがポイントです。
桁数を増やすときは、元の列の各要素に必ず同じ長さの接頭辞(0または1)を付け、桁数をそろえること。
桁がずれると1ビット差の性質が崩れてしまいます。
2進数→グレイコードの変換
2進数からグレイコードへの変換はとても簡単で、g = b ^ (b >> 1)という式で計算できます。
ここで^は排他的論理和(XOR)、>>は右シフトです。
直感的には、各ビットをその1桁左のビットとXORすると覚えると理解しやすいです。
2進数110(6)をグレイコードにします。
右に1ビットシフトすると011になります。
110 XOR 011 = 101 なので、答えは101です。
したがって6のグレイコードは101です。
ビット幅を決めて桁をそろえてから計算しましょう。
桁が足りないと見た目が合わず、混乱の元になります。
グレイコード→2進数の変換
逆方向は、少しだけ手順が増えます。
ただしやることは単純です。
最上位ビット(MSB)は同じで、以降は直前までの2進数のビットとグレイコードのビットをXORしながら求めていきます。
手順のイメージ
グレイ g2 g1 g0 から2進 b2 b1 b0 を作る場合: b2 = g2 b1 = b2 XOR g1 b0 = b1 XOR g0
グレイ101を2進数に戻します。
b2 = 1 b1 = 1 XOR 0 = 1 b0 = 1 XOR 1 = 0 よって2進数は110で、グレイ101は2進110になります。
実装では想定ビット幅を超える上位のゴミ値に注意し、必要に応じてマスクを使うと安全です。
グレイコードのメリットと応用
誤読を防ぐメリット
センサの読み取りは、物理現象の変化中に信号を読むことがあり、複数ビットが同時に変わる2進数では誤読が起きやすくなります。
グレイコードは隣接値で1ビットしか変わらないため、途中で一部のビットだけ先に切り替わっても、ズレが最小限に抑えられます。
これが、機械の位置や角度を正確に扱う用途での大きな利点です。
ロータリーエンコーダでの利用
絶対位置ロータリーエンコーダでは、円盤に複数のトラックがあり、各トラックのON/OFFの組み合わせが角度を表します。
もし2進数の並びにすると、一度に複数トラックが切り替わる位置があり、読み取りの瞬間に誤差が生じます。
グレイコードにしておけば、どの角度間の境目でも1トラックしか変わらないため、安定した読み取りが可能です。
位置/角度センサの読み取り
直線エンコーダや多回転エンコーダなどでも、取り込んだビット列をまずグレイ→2進に変換してから計算するのが一般的です。
これにより、センサの生データをそのまま数値として扱えるようになります。
ソフト側で補正やフィルタをかける前処理としても相性が良い方法です。
組込みプログラミングでの使い方
マイコンではGPIOの複数ピンでグレイコードの状態を読み取り、即座に2進数へ変換して位置や角度の数値に直すのが基本です。
ピン入力のデバウンスや簡単な過去値チェックを組み合わせると、より安定します。
符号なし型を使い、ビット幅に合わせてマスクすることも忘れないようにしましょう。
プログラミング実装のポイント
Pythonでグレイコードを生成
Pythonでは、列挙も変換もシンプルに書けます。
b ^ (b >> 1)でグレイ化、グレイ→2進は前から順にXORを畳み込むのがコツです。
生成と変換の例
def gray_list(n_bits: int):
"""n_bitsビットのグレイコードを0から順に整数で返す"""
size = 1 << n_bits
return [i ^ (i >> 1) for i in range(size)]
def bin_to_gray(b: int) -> int:
"""2進数(整数)をグレイコード(整数)に変換"""
return b ^ (b >> 1)
def gray_to_bin(g: int) -> int:
"""グレイコード(整数)を2進数(整数)に変換"""
b = g
# 上位から順にXORを畳み込む
shift = 1
while (g >> shift) > 0:
b ^= (g >> shift)
shift += 1
return b
def to_bits(n: int, width: int) -> str:
return format(n, f"0{width}b")
if __name__ == "__main__":
n_bits = 3
for i in range(1 << n_bits):
g = bin_to_gray(i)
b = gray_to_bin(g)
print(f"{i:>2} bin={to_bits(i, n_bits)} -> gray={to_bits(g, n_bits)} -> back={to_bits(b, n_bits)}")
このコードでは、gray_listで列挙し、bin_to_grayとgray_to_binで相互変換しています。
formatで桁をそろえることで、画面表示が見やすくなります。
JavaScriptでグレイコードを列挙
JavaScriptでもビット演算が使えます。
配列を作って、n ^ (n >> 1)で順番に求めます。
実用的な列挙関数
function listGray(nBits) {
const size = 1 << nBits;
const list = [];
for (let i = 0; i < size; i++) {
list.push(i ^ (i >> 1));
}
return list;
}
function toBits(n, width) {
return n.toString(2).padStart(width, "0");
}
// デモ
const nBits = 3;
const gray = listGray(nBits);
for (let i = 0; i < gray.length; i++) {
console.log(`${i} bin=${toBits(i, nBits)} -> gray=${toBits(gray[i], nBits)}`);
}
文字列化のときはpadStartでゼロ埋めをすると視認性が上がります。
ブラウザでもNode.jsでも同じコードで動きます。
C言語で変換関数を作る
Cでは型の幅がはっきりしているため、符号なし整数を使うと安全です。
2進→グレイは1行、グレイ→2進はXORの畳み込みで書けます。
変換関数とテスト
#include <stdio.h>
#include <stdint.h>
static inline uint32_t bin_to_gray(uint32_t b) {
return b ^ (b >> 1);
}
static uint32_t gray_to_bin(uint32_t g) {
// 前から順にXORを畳み込む(ループでも可)
uint32_t b = g;
for (uint32_t shift = 1; (g >> shift) != 0; ++shift) {
b ^= (g >> shift);
}
return b;
}
static void print_bits(uint32_t x, unsigned width) {
for (int i = (int)width - 1; i >= 0; --i) {
putchar((x & (1u << i)) ? '1' : '0');
}
}
int main(void) {
unsigned n_bits = 3;
uint32_t size = 1u << n_bits;
for (uint32_t i = 0; i < size; ++i) {
uint32_t g = bin_to_gray(i);
uint32_t b = gray_to_bin(g);
printf("%2u bin=", i);
print_bits(i, n_bits);
printf(" -> gray=");
print_bits(g, n_bits);
printf(" -> back=");
print_bits(b, n_bits);
printf("\n");
}
return 0;
}
左シフトや右シフトは符号付き型だと未定義動作になる可能性があるため、uint32_tなどの符号なし型を使うのが安全です。
必要ならマスク(例: x &= (1u << n_bits) – 1)でビット幅を合わせましょう。
ビット演算子の簡単メモ
| 演算子 | 読み方 | 例 | 意味 | ||
|---|---|---|---|---|---|
| ^ | XOR | a ^ b | 異なるビットなら1 | ||
| & | AND | a & b | 両方1なら1 | ||
| OR | a | b | どちらか1なら1 | ||
| >> | 右シフト | a >> 1 | 右に1ビット詰める | ||
| << | 左シフト | a << 1 | 左に1ビット詰める |
グレイコードでは主にXORと右シフトを使います。
演算の前に桁合わせを意識すると混乱が減ります。
まとめ
グレイコードは、隣り合う値で1ビットだけが変わるという性質により、誤読に強く、センサやエンコーダで広く使われています。
作り方は反射と接頭辞付けで理解しやすく、2進→グレイは b ^ (b >> 1)、グレイ→2進は前からXORを畳み込むという手順で相互変換できます。
Python・JavaScript・Cのいずれでも短いコードで実装でき、初心者でも再現しやすい題材です。
ビット幅の桁合わせと符号なし型の利用を意識すれば、読み取りの堅牢性を高めたシステム設計にすぐ役立てられます。
