プログラマのための IT 教科書
第3部 コンピュータとネットワークの基礎

ビットとバイト

読了目安 35

この章を読むとできるようになること
  • 16進数とバイト列を相互に変換できる
  • 文字化けが起きる仕組みを説明できる
  • 0.1 + 0.2 が 0.3 にならない理由を説明できる

この章は、他の章に比べて地味です。すぐには役に立たないかもしれません。

しかし、次のような場面でこの章を読んでいるかどうかで差が出ます

  • 「文字化けした」の原因を説明できるか
  • 「金額の計算が1円ずれる」を再現・修正できるか
  • proto のバイト列(第13章)を読めるか
  • ログに出た 0x1F のような値の意味が分かるか

2進数と16進数

コンピュータは 0 と 1 しか扱いません。人間には読みづらいので、 4ビットをまとめて16進数で書きます。

10進2進16進
000000
910019
101010A
151111F
25511111111FF

16進数2桁 = 8ビット = 1バイトです。この対応が最も重要です。

0xFF   = 255      1バイトの最大値
0x00   = 0
0x41   = 65       ASCII の 'A'

プログラミングでは 0x を付けて16進数を表します。

なぜ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 で読む  → 意味不明な文字の並びになる(文字化け)
文字化けを見たら「どこで」を切り分ける

文字化けの原因は、次のどこかにあります。

  1. ファイルの保存時のエンコーディング
  2. DB のカラムの文字セット(MySQL の utf83バイトまでしか扱えず、 絵文字が入らない。utf8mb4 が必要)
  3. HTTP レスポンスの Content-Typecharset 指定
  4. HTML の <meta charset>
  5. 端末やエディタの表示設定

「化けている場所」ではなく「化け始めた場所」を探してください。 DB に正しく入っているかを直接確認するのが、切り分けの第一歩です。

MySQL の utf8 は UTF-8 ではない

歴史的な事情で、MySQL の utf8最大3バイトしか扱えません。 4バイト必要な絵文字が保存できず、エラーになるか切り捨てられます。

正しくは utf8mb4 を使います。 「絵文字を入れたらエラーになる」の原因はほぼこれです。

文字数の数え方

'あ'.length         // 1
'👨‍👩‍👧'.length      // 8   ← 見た目は1文字なのに

絵文字は、複数のコードポイントを結合して1つに見せていることがあります (家族の絵文字は「男+女+女の子」を結合子で繋いだもの)。

文字数制限のバグ

「名前は20文字まで」というバリデーションを length で書くと、 絵文字を含む名前が想定より短く弾かれます

逆に、DB のカラムを VARCHAR(20) にした時、 バイト数か文字数かは DB によって違います。

「20文字」の定義(バイト? コードポイント? 見た目の文字?)を、 仕様として決めておく必要があります。

数値の表現

整数とオーバーフロー

固定長で保存するため、上限があります

範囲
int32約 -21億 〜 21億
int64約 -922京 〜 922京
2038年問題

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

「日付だけずれる」「日をまたぐ集計が合わない」の原因は、ほぼこれです。

実務の落とし穴まとめ

  1. 金額を float で扱う — 集計で誤差が出る。整数か Decimal
  2. MySQL の utf8 — 3バイトまで。絵文字は utf8mb4
  3. length で文字数制限 — 絵文字で想定と変わる
  4. タイムゾーンなしの時刻を保存 — 意味が確定しない
  5. 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)に進みます。

読み終わったら記録しておくと、目次で進み具合が分かります。