単純チェックサム
ケーブルの上を流れている途中で 1 ビットが反転しても、受け取った側には普通のデータに見えます。壊れたことに気づく仕組みが要ります。いちばん軽いのが、全部足して 1 バイトに丸めた値を、一緒に送る方法です。
化けても、見た目では分からない
送る側が中身から短い値を計算して末尾に付け、受け取る側が同じ計算をして見比べます。値が違えば、途中で何かが変わったと分かります。あとは送り直してもらえば済みます。この短い値がチェックサムです。
プレーンテキスト
送る側 本文 と 検査値 を並べて送る
受け取る側 本文から検査値を作り直して見比べる
違った -> 送り直してもらう
同じ -> 先へ進む計算は 1 回の走査で終わるので、小さな機器の側で作るのも簡単です。シリアル通信や、バーコードの末尾に付いている検査桁など、細い経路では今も広く使われています。
ファイルをダウンロードしたあとに SHA-256 の値を照らし合わせる、あの作業と目的は同じです。違うのは強さで、こちらは 1 バイトしか使わないぶん、見逃す壊れ方がずっと多くなります。
足して、1 バイトに丸める
全部のバイトを足すだけだと、データが長くなるにつれて値が際限なく大きくなります。1 バイトに収めたいので、256 で割った余りを取ります。余りを取る計算は、あふれたぶんを巻き戻す動きをします。
Python
today = 3 # 水曜を 3 とする
print((today + 10) % 7) # 610 日後の曜日を出す計算と同じです。7 を超えたぶんは 1 週間ぶん巻き戻って、必ず 0 から 6 に収まります。256 で割れば、必ず 0 から 255 に収まります。
足し合わせる側は、合計を入れる変数を 0 から始めて 1 つずつ足していきます。中身が空のときは 0 のまま返ることになります。空の入力で落ちないのは、初期値を 0 にしておく利点です。
受け取る値の範囲にも気をつけます。1 バイトとして扱うなら、中身は 0 から 255 のはずです。ここに負の数や 256 以上が混ざると、余りの結果が想定から外れます。とくに、割られる数が負になったときの余りの符号は言語ごとに違います。
同じ値になる壊れ方がある
ここが一番大事なところです。チェックサムが一致しても、壊れていないことの証明にはなりません。値が一致したまま中身が変わる壊れ方が、実際に存在するからです。
- 並び順が入れ替わっただけなら、和は変わりません
- どこかが 5 増え、別のどこかが 5 減れば、和は元のままです
- ちょうど 256 ぶんずれた変化は、余りを取った時点で消えます
プレーンテキスト
[10, 60] -> 70
[60, 10] -> 70 並びが違っても同じ値
[15, 55] -> 70 中身が違っても同じ値つまりこの計算で分かるのは、違うということだけです。同じであることは分かりません。並び順まで見たいなら Fletcher や Adler-32、通信路が荒れる場所なら CRC-32、誰かが意図して書き換える可能性があるなら SHA-256 のような暗号学的ハッシュを使います。
送る側と受け取る側で同じ計算をする、という約束が前提なので、桁の扱いや順番の決め方が食い違うと、壊れていないのに一致しないという逆の事故も起きます。
単純な和は、軽さと引き換えに見逃しを受け入れた仕組みです。どこまで見逃すのかを知ったうえで選ぶなら、いまでも十分使えます。
要件
- 全バイトを合計する
- 合計を 256 で割った余りを返す
- 空配列のときは 0 を返す
入出力例
checksumSum([]) → 0
checksumSum([0]) → 0
checksumSum([1,2,3]) → 6
checksumSum([100,100,100]) → 44
checksumSum([255,1]) → 0
checksumSum([255,255,255,255]) → 252
checksumSum([10,20,30,40,50]) → 150
checksumSum([128,128]) → 0