第2章:文字エンコーディングの世界

2.1 文字とコンピュータの出会い

2.1.1 電信からコンピュータへ:文字符号化の歴史

コンピュータが文字を扱う以前から、人類は遠距離通信のために文字を符号化する必要がありました。この歴史を理解することで、ASCIIコードの設計思想がより深く理解できます。

モールス信号(1838年)

サミュエル・モースが発明したモールス信号は、文字を「短点(・)」と「長点(−)」の組み合わせで表現する最初の実用的な電気通信符号でした。

頻出文字は短い符号、稀な文字は長い符号。これは「情報理論」の先駆けです。

| 文字 | モールス符号 | 頻度 | 備考 | |:---:|:---:|:---:|:---| | E | | 12.7% | 最頻出の母音 → 最短 | | T | | 9.1% | 高頻度の子音 | | A | ・− | 8.2% | | | O | −−− | 7.5% | | | I | ・・ | | | | N | −・ | | | | ... | ... | ... | ... | | Q | −−・− | | 低頻度 → 長い | | Z | −−・・ | | |

→ シャノンの情報理論(1948年)で数学的に正当化 「頻度の高い記号には短い符号を」= ハフマン符号化の原理

ボードー符号(1870年)

エミール・ボードーは、5ビットの固定長符号を開発しました。これにより、2^5 = 32種類の文字を表現できました。

| ビット列 | 文字 (Letters) | 文字 (Figures) | |:---:|:---:|:---:| | 00011 | A | - | | 11001 | B | ? | | 01110 | C | : | | 01001 | D | $ | | 00001 | E | 3 | | ... | ... | ... |

特徴:

  • 固定長5ビット(32通り)
  • シフトによる切り替え(Letters/Figures)
- 同じビット列で異なる文字を表現 - 事実上、64文字程度を表現可能

問題点:

  • シフト状態の追跡が必要
  • 送信エラーで後続の文字が全て化ける

テレタイプと7ビット符号の登場

電信の発展に伴い、より多くの文字を扱う必要が生じました。1960年代、アメリカでASCII(American Standard Code for Information Interchange)が策定されます。

2.1.2 ASCIIの誕生と設計思想

ASCII策定の背景(1963年)

1960年代の状況:

コンピュータメーカーごとに独自の文字コード
  IBM    → EBCDIC (Extended Binary Coded Decimal Interchange Code)
  DEC    → 独自の6ビットコード
  他多数 → 互換性なし

問題:
- データ交換が困難
- ソフトウェアの移植性なし
- 経済的損失

解決策:
1963年 ASA(現ANSI)がASCIIを策定
1968年 連邦政府が全コンピュータでASCII採用を義務化

ASCIIの設計原理

ASCIIは7ビットの文字コードで、128種類の文字を定義しています。その設計には深い考慮があります。

ASCIIは7ビットの文字コードで、128種類の文字を定義しています。

| 範囲 (Hex) | 範囲 (Dec) | 分類 | 内容 | |:---|:---|:---|:---| | 0x00-0x1F | 0-31 | 制御文字 | 33文字 (NUL, LF, CR, etc.) | | 0x20 | 32 | スペース | 空白 | | 0x21-0x2F | 33-47 | 記号 | ! " # $ % & ' ( ) + , - . / | | 0x30-0x39 | 48-57 | 数字 | '0'-'9' | | 0x3A-0x40 | 58-64 | 記号 | : ; < = > ? @ | | 0x41-0x5A | 65-90 | 大文字 | 'A'-'Z' | | 0x5B-0x60 | 91-96 | 記号 | [ \ ] ^ _ | | 0x61-0x7A | 97-122 | 小文字 | 'a'-'z' | | 0x7B-0x7E | 123-126 | 記号 | { | } ~ | | 0x7F | 127 | DEL | 削除 | 視覚的グリッド(16進数):

| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | 0x0_ | NUL | SOH | STX | ETX | EOT | ENQ | ACK | BEL | BS | HT | LF | VT | FF | CR | SO | SI | | 0x1_ | DLE | DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN | EM | SUB | ESC | FS | GS | RS | US | | 0x2_ | SP | ! | " | # | $ | % | & | ' | ( | ) | | + | , | - | . | / | | 0x3_ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? | | 0x4_ | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | | 0x5_ | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ | | 0x6_ | | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | | 0x7_ | p | q | r | s | t | u | v | w | x | y | z | { | \| | } | ~ | DEL |

ASCIIの設計における天才的配慮

1. 数字の連続配置 (0x30-0x39):

   '0' = 0x30 = 0011 0000
   '1' = 0x31 = 0011 0001
   '2' = 0x32 = 0011 0010
   ...
   '9' = 0x39 = 0011 1001

   文字 → 数値変換:
   digit = character - '0';
   // '5' - '0' = 0x35 - 0x30 = 5

   この設計により、ft_atoi の実装が単純になる


2. 大文字と小文字の関係:

   ![ASCII Bit Patterns](/Users/hackhike/.gemini/antigravity/brain/bc43455d-719e-4889-8da1-eb68d30dc6a7/ascii_bit_patterns_1764871035459.png)

   大文字→小文字: c | 0x20  または c + 32
   小文字→大文字: c & ~0x20 または c - 32

   この設計により、ft_toupper, ft_tolower が効率的に実装可能


3. アルファベットの連続配置:

   'A'(65) から 'Z'(90) まで連続
   'a'(97) から 'z'(122) まで連続

   アルファベット判定:
   is_alpha = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');

   この設計により、ft_isalpha が単純な範囲チェックで実装可能


4. 制御文字の配置 (0x00-0x1F):

   重要な制御文字:
   0x00 NUL (Null)         - 文字列終端
   0x07 BEL (Bell)         - 警告音
   0x08 BS  (Backspace)    - 後退
   0x09 HT  (Horizontal Tab) - タブ
   0x0A LF  (Line Feed)    - 改行
   0x0D CR  (Carriage Return) - 復帰
   0x1B ESC (Escape)       - エスケープシーケンス開始

   制御文字判定:
   is_control = (c >= 0 && c <= 31) || c == 127;

2.1.3 ASCIIの完全な一覧表

プログラマとして、ASCIIコードを暗記する必要はありませんが、主要な値は覚えておくと便利です。

ASCII コード表(完全版):

| Dec | Hex | Char | Description |
|:---:|:---:|:---:|:---|
| 0 | 0x00 | **NUL** | Null(文字列終端) |
| 1 | 0x01 | **SOH** | Start of Heading |
| 2 | 0x02 | **STX** | Start of Text |
| 3 | 0x03 | **ETX** | End of Text (Ctrl+C) |
| 4 | 0x04 | **EOT** | End of Transmission (Ctrl+D) |
| 5 | 0x05 | **ENQ** | Enquiry |
| 6 | 0x06 | **ACK** | Acknowledge |
| 7 | 0x07 | **BEL** | Bell (警告音) |
| 8 | 0x08 | **BS** | Backspace |
| 9 | 0x09 | **HT** | Horizontal Tab |
| 10 | 0x0A | **LF** | Line Feed (Unix改行) |
| 11 | 0x0B | **VT** | Vertical Tab |
| 12 | 0x0C | **FF** | Form Feed |
| 13 | 0x0D | **CR** | Carriage Return (Mac旧改行) |
| 14 | 0x0E | **SO** | Shift Out |
| 15 | 0x0F | **SI** | Shift In |
| 16 | 0x10 | **DLE** | Data Link Escape |
| 17 | 0x11 | **DC1** | Device Control 1 (XON) |
| 18 | 0x12 | **DC2** | Device Control 2 |
| 19 | 0x13 | **DC3** | Device Control 3 (XOFF) |
| 20 | 0x14 | **DC4** | Device Control 4 |
| 21 | 0x15 | **NAK** | Negative Acknowledge |
| 22 | 0x16 | **SYN** | Synchronous Idle |
| 23 | 0x17 | **ETB** | End of Transmission Block |
| 24 | 0x18 | **CAN** | Cancel |
| 25 | 0x19 | **EM** | End of Medium |
| 26 | 0x1A | **SUB** | Substitute (Ctrl+Z, Windows EOF) |
| 27 | 0x1B | **ESC** | Escape |
| 28 | 0x1C | **FS** | File Separator |
| 29 | 0x1D | **GS** | Group Separator |
| 30 | 0x1E | **RS** | Record Separator |
| 31 | 0x1F | **US** | Unit Separator |
| 32 | 0x20 | **(sp)** | Space(空白) |
| 33 | 0x21 | **!** | Exclamation mark |
| 34 | 0x22 | **"** | Double quote |
| 35 | 0x23 | **#** | Number sign / Hash |
| 36 | 0x24 | **$** | Dollar sign |
| 37 | 0x25 | **%** | Percent |
| 38 | 0x26 | **&** | Ampersand |
| 39 | 0x27 | **'** | Single quote |
| 40 | 0x28 | **(** | Left parenthesis |
| 41 | 0x29 | **)** | Right parenthesis |
| 42 | 0x2A | ***** | Asterisk |
| 43 | 0x2B | **+** | Plus |
| 44 | 0x2C | **,** | Comma |
| 45 | 0x2D | **-** | Hyphen / Minus |
| 46 | 0x2E | **.** | Period / Dot |
| 47 | 0x2F | **/** | Slash |
| 48 | 0x30 | **0** | Digit zero |
| 49 | 0x31 | **1** | Digit one |
| ... | ... | ... | ... |
| 57 | 0x39 | **9** | Digit nine |
| 58 | 0x3A | **:** | Colon |
| 59 | 0x3B | **;** | Semicolon |
| 60 | 0x3C | **<** | Less than |
| 61 | 0x3D | **=** | Equals |
| 62 | 0x3E | **>** | Greater than |
| 63 | 0x3F | **?** | Question mark |
| 64 | 0x40 | **@** | At sign |
| 65 | 0x41 | **A** | Uppercase A |
| ... | ... | ... | ... |
| 90 | 0x5A | **Z** | Uppercase Z |
| 91 | 0x5B | **[** | Left square bracket |
| 92 | 0x5C | **\** | Backslash |
| 93 | 0x5D | **]** | Right square bracket |
| 94 | 0x5E | **^** | Caret |
| 95 | 0x5F | **_** | Underscore |
| 96 | 0x60 | **`** | Grave accent / Backtick |
| 97 | 0x61 | **a** | Lowercase a |
| ... | ... | ... | ... |
| 122 | 0x7A | **z** | Lowercase z |
| 123 | 0x7B | **{** | Left curly brace |
| 124 | 0x7C | **\|** | Vertical bar / Pipe |
| 125 | 0x7D | **}** | Right curly brace |
| 126 | 0x7E | **~** | Tilde |
| 127 | 0x7F | **DEL** | Delete |

覚えておくと便利な値:
  '0' = 48 = 0x30    'A' = 65 = 0x41    'a' = 97 = 0x61
  '\n' = 10 = 0x0A   ' ' = 32 = 0x20    '\0' = 0 = 0x00

2.2 ASCIIを超えて:文字エンコーディングの進化

2.2.1 8ビット拡張と混乱の時代

ASCIIは7ビットで設計されましたが、コンピュータは8ビット(1バイト)を基本単位としています。残りの1ビットをどう使うかで、様々な拡張が生まれました。

8ビット拡張の乱立:

![8-bit Extended ASCII Structure](/Users/hackhike/.gemini/antigravity/brain/bc43455d-719e-4889-8da1-eb68d30dc6a7/ascii_8bit_extension_1764871058215.png)

代表的な拡張:

1. ISO 8859-1 (Latin-1) - 西欧
   0x80-0xFF: àáâ ñ ö ü など西欧文字

2. ISO 8859-5 - キリル文字
   0x80-0xFF: А Б В Г Д など

3. ISO 8859-15 (Latin-9)
   Latin-1 + ユーロ記号(€)

4. Windows-1252 (CP1252)
   Latin-1の制御文字領域に追加文字

5. Shift_JIS (日本)
   1バイト: ASCII + 半角カナ
   2バイト: 漢字、全角文字

6. EUC-JP (日本)
   Unix系で使用された日本語エンコーディング

7. Big5 (繁体字中国語)
8. GB2312 / GBK (簡体字中国語)
9. EUC-KR (韓国語)

文字化けの発生メカニズム

文字化け(mojibake)の例:

送信側 (Shift_JIS):
  "日本語" = 93 FA 96 7B 8C EA

受信側が ISO 8859-1 と解釈:
  93 → "ô" (Latin-1)
  FA → "ú" (Latin-1)
  96 → "û" (Latin-1)
  7B → "{" (ASCII)
  8C → "Œ" (Latin-1)
  EA → "ê" (Latin-1)

結果: "日本語" → "ôúû{Œê" ???

問題の本質:
- 同じバイト列でも、エンコーディングによって解釈が異なる
- 送信者と受信者のエンコーディングが一致しないと文字化け

2.2.2 Unicodeの登場:統一への道

Unicode の理念(1991年)

Unicode の目標:

「世界中の全ての文字に、唯一の番号(コードポイント)を割り当てる」

  日本語 "あ" = U+3042
  中国語 "中" = U+4E2D
  アラビア語 "ا" = U+0627
  絵文字 "😀" = U+1F600

Unicode の構成:

  コードポイント範囲: U+0000 〜 U+10FFFF
  理論上の文字数: 1,114,112 文字
  現在の定義済み: 約150,000文字(Unicode 15.0)

平面(Plane)の構成:
  Plane 0 (BMP): U+0000 - U+FFFF   基本多言語面(ほとんどの文字)
  Plane 1 (SMP): U+10000 - U+1FFFF 追加多言語面(古代文字、絵文字)
  Plane 2 (SIP): U+20000 - U+2FFFF 追加漢字面
  ...
  Plane 16:      U+100000 - U+10FFFF

UTF-8:可変長エンコーディングの傑作

UTF-8は、ケン・トンプソン(Unixの共同開発者)とロブ・パイクが1992年に設計した、可変長のUnicodeエンコーディングです。

UTF-8 のエンコーディング規則:

| コードポイント範囲 | UTF-8 バイト列 | 備考 |
|:---|:---|:---|
| `U+0000 - U+007F` | `0xxxxxxx` | 1バイト、ASCIIと完全互換 |
| `U+0080 - U+07FF` | `110xxxxx 10xxxxxx` | 2バイト (ラテン拡張等) |
| `U+0800 - U+FFFF` | `1110xxxx 10xxxxxx 10xxxxxx` | 3バイト (日本語、中国語等) |
| `U+10000 - U+10FFFF` | `11110xxx 10xxxxxx 10xxxxxx 10xxxxxx` | 4バイト (絵文字、古代文字) |

例: "A" (U+0041)
  0041 = 0100 0001
  → 0x41 (1バイト、ASCIIそのまま)

例: "é" (U+00E9)
  00E9 = 1110 1001
  → 110 00011  10 101001
  → 0xC3 0xA9 (2バイト)

例: "あ" (U+3042)
  3042 = 0011 0000 0100 0010
  → 1110 0011  10 000001  10 000010
  → 0xE3 0x81 0x82 (3バイト)

例: "😀" (U+1F600)
  1F600 = 0001 1111 0110 0000 0000
  → 11110 000  10 011111  10 011000  10 000000
  → 0xF0 0x9F 0x98 0x80 (4バイト)

UTF-8の優れた設計

UTF-8 の利点:

1. ASCII互換性
   ASCII文字はそのまま1バイト
   既存のASCIIデータはそのままUTF-8として有効
   C言語の文字列処理関数の多くがそのまま動作

2. 自己同期性
   どのバイトからでも、文字の境界を検出可能
   ・1バイト目: 0xxxxxxx, 110xxxxx, 1110xxxx, 11110xxx
   ・継続バイト: 10xxxxxx

   途中から読み始めても復帰可能:
   ... 10xxxxxx 10xxxxxx 1110xxxx 10xxxxxx 10xxxxxx ...
                          ↑
                     ここが文字の開始

3. バイト順序の問題なし
   UTF-16/32 と異なり、エンディアンに依存しない

4. NUL安全
   U+0000以外の文字に0x00バイトが現れない
   C言語の文字列(NUL終端)と互換

5. ソート順序の保持
   バイト列としてソートすると、コードポイント順になる

2.3 ビット演算の数学的基礎

2.3.1 ブール代数と論理演算

文字処理関数を効率的に実装するためには、ビット演算の理解が不可欠です。ビット演算は、19世紀の数学者ジョージ・ブールが確立した「ブール代数」に基づいています。

ブール代数の基本演算:

1. AND(論理積、∧、&)
   両方が1のときのみ1

   **真理値表**:

| A | B | A & B |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |

   ビット列での例:
     1010 1100
   & 1100 1010
   ──────────
     1000 1000

   用途: 特定ビットの抽出(マスク)
   x & 0x0F  → 下位4ビットを抽出


2. OR(論理和、∨、|)
   どちらかが1なら1

   **真理値表**:

| A | B | A \| B |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |

   ビット列での例:
     1010 1100
   | 1100 1010
   ──────────
     1110 1110

   用途: 特定ビットのセット
   x | 0x20  → 第5ビットを1にセット


3. XOR(排他的論理和、⊕、^)
   異なるときのみ1

   **真理値表**:

| A | B | A ^ B |
|:---:|:---:|:---:|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |

   ビット列での例:
     1010 1100
   ^ 1100 1010
   ──────────
     0110 0110

   重要な性質:
   A ^ A = 0       (自分自身とのXORは0)
   A ^ 0 = A       (0とのXORは変化なし)
   A ^ B ^ B = A   (2回XORすると元に戻る)

   用途: ビットの反転、暗号化、スワップ
   // 一時変数なしでスワップ
   a ^= b; b ^= a; a ^= b;


4. NOT(論理否定、¬、~)
   ビットの反転

   ~0 = 1,  ~1 = 0

   ビット列での例:
   ~ 1010 1100
   ──────────
     0101 0011

   用途: ビットのクリア
   x & ~0x20  → 第5ビットを0にクリア

2.3.2 シフト演算

シフト演算:

1. 左シフト (<<)
   ビットを左に移動、空いた右側は0で埋まる

   00001010 << 2 = 00101000
   (10 << 2 = 40)

   数学的意味: × 2^n
   x << n は x × 2^n と等価(オーバーフローしない場合)


2. 右シフト (>>)

   a) 論理右シフト(符号なし整数)
      空いた左側は0で埋まる
      10100000 >> 2 = 00101000

   b) 算術右シフト(符号付き整数、多くの実装)
      空いた左側は符号ビットで埋まる
      10100000 >> 2 = 11101000  (負の数の場合)

   数学的意味: ÷ 2^n(切り捨て)
   x >> n は x ÷ 2^n と等価


シフト演算の応用:

1. 2のべき乗の乗除算(高速)
   x * 8  →  x << 3   (8 = 2^3)
   x / 4  →  x >> 2   (4 = 2^2)

2. 特定ビットへのアクセス
   // 第nビットを1にする
   mask = 1 << n;

   // 第nビットを取得
   bit = (x >> n) & 1;

3. ビットフィールドの構築
   // RGB値を32ビット整数に格納
   color = (r << 16) | (g << 8) | b;

2.3.3 ビット演算のイディオム

文字処理で頻繁に使用されるビット演算のパターンを紹介します。

// 1. 特定ビットの操作

// 第nビットをセット
#define SET_BIT(x, n)    ((x) | (1 << (n)))

// 第nビットをクリア
#define CLEAR_BIT(x, n)  ((x) & ~(1 << (n)))

// 第nビットを反転
#define TOGGLE_BIT(x, n) ((x) ^ (1 << (n)))

// 第nビットを取得
#define GET_BIT(x, n)    (((x) >> (n)) & 1)


// 2. 大文字・小文字変換(ASCIIの性質を利用)

// 小文字→大文字: 第5ビットをクリア
#define TO_UPPER(c)  ((c) & ~0x20)
// 'a' (0x61) & ~0x20 = 'A' (0x41)

// 大文字→小文字: 第5ビットをセット
#define TO_LOWER(c)  ((c) | 0x20)
// 'A' (0x41) | 0x20 = 'a' (0x61)

// 大文字・小文字を反転
#define FLIP_CASE(c) ((c) ^ 0x20)


// 3. 数字文字と数値の変換

// '0'-'9' → 0-9
#define CHAR_TO_DIGIT(c)  ((c) - '0')
// '5' - '0' = 0x35 - 0x30 = 5

// 0-9 → '0'-'9'
#define DIGIT_TO_CHAR(n)  ((n) + '0')
// 5 + '0' = 5 + 0x30 = 0x35 = '5'


// 4. 2のべき乗判定
#define IS_POWER_OF_2(x)  ((x) && !((x) & ((x) - 1)))
// x = 8 (1000), x-1 = 7 (0111)
// 1000 & 0111 = 0000 → true

// x = 6 (0110), x-1 = 5 (0101)
// 0110 & 0101 = 0100 → false


// 5. 絶対値(分岐なし、整数のみ)
// 注意: INT_MIN では未定義動作
int abs_value(int x) {
    int mask = x >> 31;  // 負なら-1(全ビット1)、正なら0
    return (x + mask) ^ mask;
}

2.4 Libft文字処理関数の実装

ここまでの基礎知識を踏まえ、Libftの文字処理関数を実装します。

2.4.1 ft_isalpha - アルファベット判定

/*
 * ft_isalpha - 文字がアルファベットかどうかを判定
 *
 * 仕様:
 *   引数cがアルファベット(A-Z, a-z)なら0以外を返す
 *   それ以外は0を返す
 *
 * 引数がintである理由:
 *   EOF(-1)を含む全ての可能な入力を受け入れるため
 *   (charは通常-128〜127または0〜255の範囲)
 */

// 実装1: 標準的な範囲チェック
int ft_isalpha(int c)
{
    return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}

// 実装2: ビット演算を使った最適化
int ft_isalpha_optimized(int c)
{
    // 大文字・小文字を統一してからチェック
    // c | 0x20 で小文字に統一('A'→'a', 'a'→'a')
    unsigned int lower = c | 0x20;
    return (lower >= 'a' && lower <= 'z');
}

// 実装3: ルックアップテーブル(最高速だがメモリ使用)
static const unsigned char alpha_table[256] = {
    // 0-64: 0
    [0 ... 64] = 0,
    // 65-90 (A-Z): 1
    ['A' ... 'Z'] = 1,
    // 91-96: 0
    [91 ... 96] = 0,
    // 97-122 (a-z): 1
    ['a' ... 'z'] = 1,
    // 123-255: 0
    [123 ... 255] = 0
};

int ft_isalpha_table(int c)
{
    if (c < 0 || c > 255)
        return 0;
    return alpha_table[(unsigned char)c];
}

エッジケースと注意点

// テストケース
assert(ft_isalpha('A') != 0);   // 大文字
assert(ft_isalpha('Z') != 0);   // 大文字境界
assert(ft_isalpha('a') != 0);   // 小文字
assert(ft_isalpha('z') != 0);   // 小文字境界
assert(ft_isalpha('0') == 0);   // 数字
assert(ft_isalpha(' ') == 0);   // 空白
assert(ft_isalpha('\0') == 0);  // NUL
assert(ft_isalpha('@') == 0);   // 'A'の直前
assert(ft_isalpha('[') == 0);   // 'Z'の直後
assert(ft_isalpha('`') == 0);   // 'a'の直前
assert(ft_isalpha('{') == 0);   // 'z'の直後
assert(ft_isalpha(-1) == 0);    // EOF
assert(ft_isalpha(128) == 0);   // 非ASCII

2.4.2 ft_isdigit - 数字判定

/*
 * ft_isdigit - 文字が数字かどうかを判定
 *
 * ASCIIにおける数字:
 *   '0' = 48 = 0x30
 *   '1' = 49 = 0x31
 *   ...
 *   '9' = 57 = 0x39
 *
 * 連続した配置により、単純な範囲チェックで実装可能
 */

int ft_isdigit(int c)
{
    return (c >= '0' && c <= '9');
}

// 数値への変換と組み合わせた使用例
int ft_atoi_simple(const char *str)
{
    int result = 0;

    while (ft_isdigit(*str))
    {
        result = result * 10 + (*str - '0');
        str++;
    }
    return result;
}

2.4.3 ft_isalnum - 英数字判定

/*
 * ft_isalnum - 文字がアルファベットまたは数字かどうかを判定
 *
 * 実装方針:
 *   既に実装したft_isalphaとft_isdigitを組み合わせる
 *   → コードの再利用、保守性の向上
 */

int ft_isalnum(int c)
{
    return (ft_isalpha(c) || ft_isdigit(c));
}

// または、独立した実装
int ft_isalnum_standalone(int c)
{
    return ((c >= 'A' && c <= 'Z') ||
            (c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9'));
}

2.4.4 ft_isascii - ASCII判定

/*
 * ft_isascii - 文字がASCII文字かどうかを判定
 *
 * ASCII範囲: 0-127 (7ビット)
 * 0x00-0x7F
 *
 * 注意: 制御文字(0-31, 127)も含む
 */

int ft_isascii(int c)
{
    return (c >= 0 && c <= 127);
}

// ビット演算版(符号なし前提)
int ft_isascii_bit(int c)
{
    // 上位ビットが0かどうか
    return !(c & ~0x7F);
}

2.4.5 ft_isprint - 印字可能文字判定

/*
 * ft_isprint - 文字が印字可能かどうかを判定
 *
 * 印字可能文字: 32-126 (0x20-0x7E)
 *   スペース(32)から~(126)まで
 *
 * 含まれないもの:
 *   制御文字 (0-31)
 *   DEL (127)
 *   非ASCII (128-)
 */

int ft_isprint(int c)
{
    return (c >= 32 && c <= 126);
}

// 空白文字(isspace)との違いを理解する
// isprint: スペース(32)は印字可能として含む
// isspace: タブ、改行なども含む
//
// isspace が true となる文字:
//   ' ' (32), '\t' (9), '\n' (10), '\v' (11), '\f' (12), '\r' (13)

2.4.6 ft_toupper - 大文字変換

/*
 * ft_toupper - 小文字を大文字に変換
 *
 * ASCII設計の恩恵:
 *   'a' = 0x61 = 0110 0001
 *   'A' = 0x41 = 0100 0001
 *         差 = 0x20 = 0010 0000 (32)
 *
 *   小文字 - 32 = 大文字
 *   または
 *   小文字 & ~0x20 = 大文字(第5ビットをクリア)
 */

// 実装1: 条件分岐
int ft_toupper(int c)
{
    if (c >= 'a' && c <= 'z')
        return (c - 32);
    return (c);
}

// 実装2: ビット演算
int ft_toupper_bit(int c)
{
    if (c >= 'a' && c <= 'z')
        return (c & ~0x20);
    return (c);
}

// 実装3: 条件演算子
int ft_toupper_ternary(int c)
{
    return ((c >= 'a' && c <= 'z') ? c - 32 : c);
}

2.4.7 ft_tolower - 小文字変換

/*
 * ft_tolower - 大文字を小文字に変換
 *
 * 変換方法:
 *   大文字 + 32 = 小文字
 *   または
 *   大文字 | 0x20 = 小文字(第5ビットをセット)
 */

int ft_tolower(int c)
{
    if (c >= 'A' && c <= 'Z')
        return (c + 32);
    return (c);
}

// ビット演算版
int ft_tolower_bit(int c)
{
    if (c >= 'A' && c <= 'Z')
        return (c | 0x20);
    return (c);
}

2.5 標準ライブラリとの比較

2.5.1 ctype.hの関数との違い

#include <ctype.h>

// 標準ライブラリのisalpha等は、ロケールに依存する可能性がある
// 例: ドイツ語ロケールでは、'ä', 'ö', 'ü' もアルファベット扱いかもしれない

// Libftの関数は、純粋なASCII範囲のみを対象とする

// 戻り値の違い:
// 標準: 0以外の値(具体的な値は未定義)
// Libft: 1または0を返す実装が多い(仕様上は0以外で良い)

// 使用例での注意
void example(void)
{
    int c = getchar();

    // 良い使い方
    if (isalpha(c)) {  // 0以外かどうかをチェック
        // ...
    }

    // 悪い使い方
    if (isalpha(c) == 1) {  // 標準ではこれはダメ
        // isalphaが2や4を返すかもしれない
    }
}

2.5.2 パフォーマンスの比較

// 各実装のパフォーマンス比較

// 1. 単純な範囲チェック(最も一般的)
//    - 分岐: 2-4回の比較
//    - 現代のCPUでは十分高速

// 2. ビット演算(理論上は高速)
//    - 分岐: 1-2回の比較
//    - 実際は分岐予測により差が出にくい

// 3. ルックアップテーブル(大量処理向け)
//    - 分岐: なし(テーブル参照のみ)
//    - キャッシュに載っていれば最速
//    - メモリを256バイト消費

// ベンチマーク例(100万回の呼び出し)
// 環境: Intel Core i7, GCC -O2

// ft_isalpha(範囲チェック):    約 2ms
// ft_isalpha_bit(ビット演算):  約 2ms
// ft_isalpha_table(テーブル):  約 1.5ms
// 標準 isalpha:                 約 2ms

// 結論: 大量処理でなければ、可読性を優先すべき

2.6 文字処理の実践的応用

2.6.1 大文字小文字を無視した文字列比較

/*
 * ft_strcasecmp - 大文字小文字を無視した文字列比較
 *
 * libftには含まれないが、実務でよく使う関数
 */
int ft_strcasecmp(const char *s1, const char *s2)
{
    while (*s1 && *s2)
    {
        int diff = ft_tolower((unsigned char)*s1) -
                   ft_tolower((unsigned char)*s2);
        if (diff != 0)
            return diff;
        s1++;
        s2++;
    }
    return ft_tolower((unsigned char)*s1) -
           ft_tolower((unsigned char)*s2);
}

// 使用例
// ft_strcasecmp("Hello", "HELLO") == 0
// ft_strcasecmp("abc", "abd") < 0

2.6.2 文字列の正規化

/*
 * 文字列を小文字に正規化(検索、比較の前処理)
 */
void ft_str_to_lower(char *str)
{
    while (*str)
    {
        *str = ft_tolower((unsigned char)*str);
        str++;
    }
}

/*
 * 識別子の正規化(C言語の変数名など)
 * アルファベット、数字、アンダースコア以外を除去
 */
void ft_normalize_identifier(char *dst, const char *src)
{
    while (*src)
    {
        if (ft_isalnum((unsigned char)*src) || *src == '_')
            *dst++ = *src;
        src++;
    }
    *dst = '\0';
}

2.6.3 簡易トークナイザ

/*
 * 文字種に基づくトークン分類
 */
typedef enum e_token_type
{
    TOKEN_WORD,     // アルファベット列
    TOKEN_NUMBER,   // 数字列
    TOKEN_SYMBOL,   // 記号
    TOKEN_SPACE,    // 空白
    TOKEN_END       // 終端
}   t_token_type;

t_token_type get_token_type(int c)
{
    if (c == '\0')
        return TOKEN_END;
    if (ft_isalpha(c))
        return TOKEN_WORD;
    if (ft_isdigit(c))
        return TOKEN_NUMBER;
    if (c == ' ' || c == '\t' || c == '\n')
        return TOKEN_SPACE;
    return TOKEN_SYMBOL;
}

// 使用例: 入力を分類しながら処理
void process_input(const char *input)
{
    while (*input)
    {
        t_token_type type = get_token_type(*input);
        switch (type)
        {
            case TOKEN_WORD:
                // 単語の処理
                break;
            case TOKEN_NUMBER:
                // 数値の処理
                break;
            // ...
        }
        input++;
    }
}

2.7 よくある間違いとデバッグ

2.7.1 符号付き文字の問題

// 問題のあるコード
int ft_isalpha_buggy(char c)  // charは符号付きの可能性
{
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

// 問題: charが符号付きの場合、128-255の値は負の数として扱われる
// 例: 0x80 = 128 (unsigned) = -128 (signed)

// 解決策: unsigned charにキャストするか、intで受け取る
int ft_isalpha_fixed(int c)
{
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

// または呼び出し側でキャスト
// ft_isalpha((unsigned char)str[i])

2.7.2 ロケールの影響

// Libftの文字関数はロケールに依存しない
// 標準のctype.h関数は依存する可能性がある

#include <locale.h>
#include <ctype.h>

void locale_example(void)
{
    // デフォルトロケール("C"ロケール)
    setlocale(LC_ALL, "C");
    printf("isalpha('ä') = %d\n", isalpha((unsigned char)'ä'));  // 0

    // ドイツ語ロケール
    setlocale(LC_ALL, "de_DE.UTF-8");
    printf("isalpha('ä') = %d\n", isalpha((unsigned char)'ä'));  // 1かも

    // Libftは常に同じ結果
    printf("ft_isalpha('ä') = %d\n", ft_isalpha((unsigned char)'ä'));  // 0
}

2.8 まとめと次章への展望

本章では、以下のことを学びました:

  • 文字符号化の歴史: モールス信号からASCII、そしてUnicodeへの進化
  • ASCIIの設計思想: なぜ文字がこのように配置されているか
  • UTF-8の仕組み: 可変長エンコーディングの原理と利点
  • ビット演算の数学: ブール代数、シフト演算、実用的なイディオム
  • Libft文字関数の実装: isalpha, isdigit, toupper, tolowerなど

次の第3章では、メモリの深淵を探検します。コンピュータのメモリ階層(レジスタ、キャッシュ、RAM、ストレージ)、仮想メモリの仕組み、そしてポインタの本質を深く理解した上で、memsetmemcpymemmoveなどのメモリ操作関数の実装に進みます。

メモリを理解することは、C言語プログラミングの核心です。ポインタが「なぜそう動くのか」を根本から理解することで、バグの少ない、効率的なコードが書けるようになります。