0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

16進数(hex)を説明できる人へ

0
Posted at

はじめに

#FF0000 というカラーコード、0xE38182 というバイト列、%E3%81%82 というURLエンコード。これらに共通する「16進数(hex)」は、Web開発・組み込み・暗号・文字コードと、エンジニアの仕事のあらゆる場所に顔を出します。

ただ、なんとなく使えてはいるものの、

  • 「なぜカラーコードは2ケタずつ区切られているのか」
  • 「なぜ文字化けが起きるのか」
  • escapeencodeURIComponent は何が違うのか」

を人に説明できるかと聞かれると、意外と詰まる人も多いはずです。

本記事では、16進数の基本から、文字コード・URLエンコード・カラーコードまで横断的に整理し、 「とりあえず動く」から「説明できる」 にステップアップすることを目指します。

想定読者

  • #FF0000 とか 0xE38182 って何?」をふんわりわかってる人
  • バイナリ・文字コード・URLエンコードで一度はハマったことがある人
  • 「なんとなく動いてるけど、仕組みは説明できない」状態を脱したい人

1. 前提知識「そもそも16進数(hex)とは?」

数の数え方は「何個でケタが上がるか」で決まる

  • 10進数 ... 0〜9 の10個でケタが上がる(普段使う数字)
  • 2進数 ... 0, 1 の2個でケタが上がる(コンピュータ内部)
  • 16進数 ... 0〜9 + A〜F の16個でケタが上がる ← これがhex
10進数 16進数
0 0
9 9
10 A
15 F
16 10
255 FF

A=10, B=11, …, F=15。1ケタで0〜15を表せます。

なぜ開発で16進数が便利なのか

コンピュータは内部で 0と1(ビット) を扱います。8ビット = 1バイトで、表せる数は0〜255の256通り。

ここで重要なのが、

1バイト(0〜255) は、ちょうど16進数2ケタ(00〜FF) で表せる

ということ。2進数で 11100011 と書くより、16進数で E3 と書く方が圧倒的に短く、人間にも読みやすい。だからメモリ・色・文字コードなど、 バイトを扱う場面ではhexが標準 なのです。

2進数の4bit(1ニブル)が16進数の1ケタに対応します。8bit/16bit/32bitといった長いビット列も、16進数なら短く表現できるのがポイントです1

#0x の意味

10 と書かれていたら、10進数の「十」か16進数の「十六」か区別できません。そのため、 「これはhexですよ」と示す印 を付けます。

記法 よく使われる場面
0x 0xFF C / Java / JavaScript / Python
# #FF0000 HTML / CSSの色指定
% %E3%81%82 URLエンコード
$ / h $FF / 0FFh アセンブラ

2. hexは「数」というより「バイトの並び」

ここから少し踏み込みます。

開発で出てくるhexは、計算するための「数値」というより、 バイトの並びを文字で書き表したもの と捉えると理解が早いです。

例えば「あ」は UTF-8 という規則で表すと3バイトで、それをhexで書くと

E3 81 82

このとき大事なのは、

hexからもとの文字に戻すには、「どの規則(エンコーディング)で並んでいるか」を知らないとダメ

hex文字列 ──分割──▶ バイト列 ──デコード(UTF-8など)──▶ 文字列
                             ▲
                    ここで規則を間違えると文字化け

UTF-8だと思って読めば「あ」、Shift_JISだと思って読めばまったく別物。 hex単体には「意味」がなく、エンコーディングとセットで初めて文字になる のです。

3. ブラウザのコンソールで試す

一番手軽な実験場はブラウザの開発者ツールです。F12(Macは Cmd + Option + I)を押して「Console」タブを開いてください。

hex → 日本語

const hex = "E38182E38184E38186";
// 2文字ずつに分けて、それぞれを数値(バイト)に変換
const bytes = new Uint8Array(hex.match(/.{2}/g).map(b => parseInt(b, 16)));
// バイト列をUTF-8として文字に戻す
new TextDecoder("utf-8").decode(bytes);
// → "あいう"

Uint8ArrayU nsigned int eger 8 -bit Array の略。Unit8Array のようにタイポすると is not defined エラーになります。(よくやってた)

日本語 → hex

const text = "あいう";
[...new TextEncoder().encode(text)]
  .map(b => b.toString(16).padStart(2, "0").toUpperCase())
  .join(" ");
// → "E3 81 82 E3 81 84 E3 81 86"

URLの %E3%81%82 を戻す

URLに含まれる日本語はパーセントエンコーディングという形式(hexの前に % を付けたもの)。これは専用関数があるので一発です。

decodeURIComponent("%E3%81%82%E3%81%84%E3%81%86");
// → "あいう"

encodeURIComponent("あいう");
// → "%E3%81%82%E3%81%84%E3%81%86"

Node.jsなどのインストール不要で、ブラウザだけで完結します。業務データを変換するときも、外部のオンラインツールに貼らずに済むので安全です。
参考 ... MDN『TextDecoder』 / MDN『encodeURIComponent()』

4. 初心者がハマりやすい落とし穴 5選

❶ Java の getBytes() は環境依存

"あ".getBytes();                          // 環境次第でUTF-8だったりShift_JISだったり
"あ".getBytes(StandardCharsets.UTF_8);    // ← 必ず文字コードを指定

「自分のPCでは動いたのに、本番(Linux)で文字化け」の典型パターン。文字列⇄バイト変換では必ずエンコーディングを明示しましょう。
参考 ... Oracle『String#getBytes()』

❷ JavaScript の escape / unescape は使わない

escape("");             // → "%u3042"  ← 独自仕様、非推奨
encodeURIComponent(""); // → "%E3%81%82" ← 標準(UTF-8)

escape は古い関数で非推奨。URLエンコードは encodeURIComponent 一択です2

❸ Python の open() も環境依存

"".encode().hex()                      # 'e38182' (encode()のデフォルトはUTF-8)
open("a.txt", "w").write("")           # OS設定次第で挙動が変わる
open("a.txt", "w", encoding="utf-8")     # ← 明示するのが安全

参考 ... Python公式『open()』

+ は空白?プラス記号?

URLの + には2つの解釈があります。

  • フォーム送信(application/x-www-form-urlencoded) → 空白
  • URLパス・フラグメント → ただのプラス記号

URLSearchParamsdecodeURIComponent で挙動が違うのはこれが原因。

new URLSearchParams("q=a+b").get("q");  // → "a b"  (空白扱い)
decodeURIComponent("a+b");              // → "a+b"  (そのまま)

参考 ... MDN『URLSearchParams』

❺ ハッシュの大文字小文字

SHA-256などのhexは A1B2...a1b2... で同じ値です。比較する前に必ず大文字 or 小文字に揃えましょう。

hash1.toLowerCase() === hash2.toLowerCase();

5. 文字コードの補足

UTF-8 と UTF-16 でバイト数が違う

文字 UTF-8(hex) UTF-16(hex)
A 41 (1バイト) 00 41 (2バイト)
E3 81 82 (3バイト) 30 42 (2バイト)
𩸽 (ホッケ) F0 A9 B8 BD (4バイト) D8 67 DE 3D (4バイト)

同じ文字でも、どの規則で書くかでバイト数が違う

絵文字や一部の漢字は「1文字 ≠ 1コード」

"𩸽".length;       // 2  ← JavaScriptは内部UTF-16なので、4バイト文字は2カウント
[..."𩸽"].length;  // 1  ← スプレッド構文ならコードポイント単位で1

家族絵文字(👨‍👩‍👧‍👦)のように複数のコードポイントが結合した「書記素クラスタ」は、スプレッド構文でも1にならないことがあります。「文字数を数える」処理を書くときは、何の単位で数えているかを意識しないとバグります。
参考 ... MDN『String.prototype.length』

BOM(バイトオーダーマーク)について

ファイル先頭に付くおまじないバイト ...

  • UTF-8 ... EF BB BF
  • UTF-16 BE ... FE FF
  • UTF-16 LE ... FF FE

CSVをExcelで開くと文字化けする問題は、UTF-8のCSVにBOMが付いていないことが原因のことが多いです。

6. URLエンコードの「微妙な仕様差」

encodeURIComponent は便利ですが、実は RFC 3986(URLの仕様書) と微妙に違う部分があります。

記号 encodeURIComponent RFC 3986 厳密
! ' ( ) * エンコードしない エンコードすべき

普段は気にしなくてOKですが、 OAuth 1.0 や AWS の署名計算 ではここがズレると認証エラーになります。

そのときは厳密版を自作します。

const strictEncode = s => encodeURIComponent(s).replace(
  /[!'()*]/g,
  c => '%' + c.charCodeAt(0).toString(16).toUpperCase()
);

参考 ... RFC 3986 §2.3 / MDN『encodeURIComponent()』

7. カラーコード #FF0000 の意味

#RRGGBB は赤(R)・緑(G)・青(B)を、それぞれ16進数2ケタ(00〜FF = 0〜255)で表したもの。

  • #FF0000 = R:255, G:0, B:0 → 赤
  • #00FF00 = R:0, G:255, B:0 → 緑
  • #FFFFFF = 全部最大 → 白
  • #000000 = 全部0 → 黒

CSSでは #RGB (3ケタ短縮) や #RRGGBBAA (アルファ値付き8ケタ)もサポートされています。#F09#FF0099 と同じ意味、#FF009980 は約50%の透明度になります3

色を「単純に平均する」と知覚的にズレる

// NG ... 単純平均は人間の目には不自然な色になることがある
const avg = (a, b) => (a + b) / 2;

これは sRGB という色空間が、ガンマ補正された非線形な値だから。RGB値の中央(128, 128, 128)は、人間の目には「明るさの中間」には見えません。色を正確に混ぜたいときは、CSS の新機能 color-mix(in oklch, ...) のように知覚的に均等な色空間で計算するのが一番きれいです。
参考 ... MDN『color-mix()』

8. 知っておくと便利なツール

Chrome DevTools の Memory Inspector

Sources パネルでバイナリを右クリック → Reveal in Memory Inspector。hex / ASCII / 整数を切り替え表示でき、エンディアン(バイトの並び順)も切り替え可能です4

コマンドライン xxd / hexdump

echo -n "あ" | xxd        # 00000000: e381 82
echo -n "あ" | xxd -r -p  # 逆変換 (hex→文字)

ファイルの中身をhexで覗くときに便利。

WebCryptoでハッシュをhex化(よく使う定型)
const buf = await crypto.subtle.digest(
  "SHA-256",
  new TextEncoder().encode("hello")
);
[...new Uint8Array(buf)]
  .map(b => b.toString(16).padStart(2, "0"))
  .join("");
// → "2cf24dba5fb0a30e..."

参考 ... MDN『SubtleCrypto.digest()』

まとめ

  • hexは「数」というより「バイトの並びを書き表す形式」と捉えると理解が早い
  • 文字に戻すには、エンコーディング(UTF-8など)が必須
  • ブラウザのコンソール + TextDecoder / TextEncoder / decodeURIComponent で、インストール不要で安全に変換できる
  • getBytes() / open() / escape など、環境依存・非推奨APIには要注意
  • カラーコードもURLエンコードも文字コードも、根っこは同じ「バイトをhexで書いている」だけ

地味だけど、文字コード・URL・暗号・色…と幅広い領域に顔を出すのが16進数。 「とりあえず動く」から「説明できる」 に進むと、デバッグ速度が一段上がります。

参考サイト

UTF-8の符号化アルゴリズムを知りたい

URLエンコードの言語間差異を知りたい

文字コードとバイト数を体系的に整理したい

  1. @IT「16進数とは」

  2. MDN『escape() (非推奨)』

  3. MDN『』

  4. Chrome DevTools『Memory Inspector』

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?