ビットとバイト
読了目安 35 分
- 16進数とバイト列を相互に変換できる
- 文字化けが起きる仕組みを説明できる
- 0.1 + 0.2 が 0.3 にならない理由を説明できる
この章は、他の章に比べて地味です。すぐには役に立たないかもしれません。
しかし、次のような場面でこの章を読んでいるかどうかで差が出ます。
- 「文字化けした」の原因を説明できるか
- 「金額の計算が1円ずれる」を再現・修正できるか
- proto のバイト列(第13章)を読めるか
- ログに出た
0x1Fのような値の意味が分かるか
2進数と16進数
コンピュータは 0 と 1 しか扱いません。人間には読みづらいので、 4ビットをまとめて16進数で書きます。
| 10進 | 2進 | 16進 |
|---|---|---|
| 0 | 0000 | 0 |
| 9 | 1001 | 9 |
| 10 | 1010 | A |
| 15 | 1111 | F |
| 255 | 11111111 | FF |
16進数2桁 = 8ビット = 1バイトです。この対応が最も重要です。
0xFF = 255 1バイトの最大値
0x00 = 0
0x41 = 65 ASCII の 'A'
プログラミングでは 0x を付けて16進数を表します。
2進数は桁が多すぎ、10進数はビット境界と合いません。 16進数は1桁がちょうど4ビットなので、変換が暗算でできます。
0xA3 を見た瞬間に 1010 0011 と分かる、というのが便利さの正体です。
bit 演算
複数のオン/オフを1つの数値にまとめる時に使います。
| 演算 | 記号 | 意味 |
|---|---|---|
| AND | & | 両方 1 なら 1 |
| OR | | | どちらか 1 なら 1 |
| XOR | ^ | 違えば 1 |
| NOT | ~ | 反転 |
| 左シフト | << | 左にずらす(2倍) |
| 右シフト | >> | 右にずらす(半分) |
実務で見る場面
ファイルの権限(第2章)がまさにこれです。
rwx r-x r-x = 111 101 101 = 0o755
↑ 読み=4, 書き=2, 実行=1 の足し算
proto のワイヤフォーマット(第13章)も同じです。
0x0A = 0000 1010
└─┬─┘ └┬┘
フィールド番号1 型2
(0x0A >> 3 = 1) (0x0A & 0x07 = 2)
上位ビットに番号、下位3ビットに型を詰め込んでいます。 シフトと AND が読めれば、バイト列が読めます。
文字コード — 文字化けの正体
バイトと文字は違う
コンピュータはバイトしか保存できません。 「あ」という文字を保存するには、何らかのルールでバイトに変換する必要があります。 そのルールが文字コードです。
ASCII から Unicode へ
ASCII (1963) 英数字と記号だけ。1文字 = 1バイト(128種類)
↓
各国が独自拡張 Shift_JIS、EUC-JP、Big5 ...(互換性がない)
↓
Unicode 世界中の文字に一意の番号(コードポイント)を割り当てる
Unicode は番号を決めただけです。その番号を実際にどうバイトにするかは別の話で、 そのやり方が UTF-8 などのエンコーディングです。
UTF-8
現在の事実上の標準です。文字によってバイト数が変わります。
| 文字 | バイト数 | 例 |
|---|---|---|
| 英数字 | 1バイト | A = 41 |
| 記号など | 2バイト | |
| 日本語 | 3バイト | あ = E3 81 82 |
| 絵文字 | 4バイト | 😀 = F0 9F 98 80 |
英語圏では ASCII と完全互換で無駄がなく、日本語も扱える。これが普及した理由です。
文字化けはなぜ起きるか
保存した時と読んだ時で、違うルールを使ったからです。
「あ」を UTF-8 で保存 → E3 81 82
それを Shift_JIS で読む → 意味不明な文字の並びになる(文字化け)
文字化けの原因は、次のどこかにあります。
- ファイルの保存時のエンコーディング
- DB のカラムの文字セット(MySQL の
utf8は3バイトまでしか扱えず、 絵文字が入らない。utf8mb4が必要) - HTTP レスポンスの
Content-Typeのcharset指定 - HTML の
<meta charset> - 端末やエディタの表示設定
「化けている場所」ではなく「化け始めた場所」を探してください。 DB に正しく入っているかを直接確認するのが、切り分けの第一歩です。
歴史的な事情で、MySQL の utf8 は最大3バイトしか扱えません。
4バイト必要な絵文字が保存できず、エラーになるか切り捨てられます。
正しくは utf8mb4 を使います。
「絵文字を入れたらエラーになる」の原因はほぼこれです。
文字数の数え方
'あ'.length // 1
'👨👩👧'.length // 8 ← 見た目は1文字なのに絵文字は、複数のコードポイントを結合して1つに見せていることがあります (家族の絵文字は「男+女+女の子」を結合子で繋いだもの)。
「名前は20文字まで」というバリデーションを length で書くと、
絵文字を含む名前が想定より短く弾かれます。
逆に、DB のカラムを VARCHAR(20) にした時、
バイト数か文字数かは DB によって違います。
「20文字」の定義(バイト? コードポイント? 見た目の文字?)を、 仕様として決めておく必要があります。
数値の表現
整数とオーバーフロー
固定長で保存するため、上限があります。
| 型 | 範囲 |
|---|---|
int32 | 約 -21億 〜 21億 |
int64 | 約 -922京 〜 922京 |
Unix 時間を int32 の秒で持つと、2038年1月19日に溢れます。
古いシステムでは実際に問題になります。
新しく作る時は int64 を使ってください。
浮動小数点誤差
この章で最も実務に効く話です。
0.1 + 0.2 === 0.3 // false
0.1 + 0.2 // 0.30000000000000004バグではありません。2進数では 0.1 を正確に表せないからです。
10進数で 1/3 を書くと 0.3333... と無限に続くのと同じで、
2進数では 0.1 が無限小数になります。有限桁で切るので、わずかな誤差が残ります。
金額を扱う時
// 危険: 誤差が蓄積する
let total = 0;
for (const item of items) total += item.price * 1.1; // 消費税対策は2つです。
| 方法 | 内容 |
|---|---|
| 整数(最小単位)で持つ | 円なら「円」、ドルなら「セント」で整数計算する |
| Decimal 型を使う | 10進数で正確に計算するライブラリ・DB 型 |
// 安全: 整数で計算し、表示の時だけ割る
const totalYen = items.reduce((sum, item) => sum + item.priceYen, 0);
const withTax = Math.floor(totalYen * 110 / 100);DB でも同じです。金額に FLOAT / DOUBLE を使ってはいけません。
DECIMAL か整数型を使います。
これは業界共通のルールです。
1件あたり 0.000001 円の誤差でも、100万件集計すれば 1円ずれます。 会計は1円でも合わないと問題になります。
「お金 = 整数か Decimal」 と覚えてください。
商品の合計金額を計算する時、価格をどう保持すべきですか。
エンディアン
複数バイトの数値を、どの順で並べるかの流儀です。
数値 0x12345678 を4バイトで表す
ビッグエンディアン: 12 34 56 78 (人間が読む順)
リトルエンディアン: 78 56 34 12 (逆順)
- x86 / ARM の CPU はリトルエンディアン
- ネットワークプロトコルはビッグエンディアン(ネットワークバイトオーダー)
普段は意識しませんが、バイナリを直接読み書きする時に効いてきます。 バイナリファイルの解析で「値がおかしい」時は、これを疑ってください。
タイムゾーンと時刻
厳密にはビットの話ではありませんが、同じくらい事故が多いので触れておきます。
2026-07-28T15:00:00Z UTC(Z は「UTC」の意味)
2026-07-29T00:00:00+09:00 同じ瞬間を日本時間で表したもの
2026-07-29 00:00:00 ← タイムゾーンが無い。どこの時刻か分からない
3番目の形式は意味が確定しません。
原則はこうです。
- 保存は UTC(またはタイムゾーン付き)
- 表示の時だけユーザーのタイムゾーンに変換
- API のやり取りは ISO 8601 形式(
2026-07-28T15:00:00Z)
「日付だけずれる」「日をまたぐ集計が合わない」の原因は、ほぼこれです。
実務の落とし穴まとめ
- 金額を float で扱う — 集計で誤差が出る。整数か Decimal
- MySQL の
utf8— 3バイトまで。絵文字はutf8mb4 lengthで文字数制限 — 絵文字で想定と変わる- タイムゾーンなしの時刻を保存 — 意味が確定しない
- Unix 時間を
int32— 2038年に溢れる
まとめ
- 16進数2桁 = 1バイト。これが読めるとバイナリが読める
- bit 演算は権限(
755)や proto のヘッダで実際に使われている - UTF-8 は可変長。日本語3バイト、絵文字4バイト
- 文字化けは「保存時と読み込み時のルールの不一致」。
MySQL の
utf8は本物の UTF-8 ではない 0.1 + 0.2 !== 0.3。2進数で10進小数を正確に表せないため- 金額は整数か Decimal。float は使わない
- 時刻は UTC で保存、表示時に変換
章末問題
ユーザー名に絵文字を入れて保存しようとしたら、MySQL でエラーになりました。原因は?
注文明細100件の合計が、期待値と1円ずれます。最も可能性が高い原因は?
proto のバイト列の先頭が 0x0A でした。これは何を表していますか。
これで第3部は完了です。次は、実際に書く言語(TypeScript と Go)に進みます。