ทำความเข้าใจกับตัวอักษรในคอมพิวเตอร์

ดองบล็อคมานาน ได้หัวข้อเขียนซะที เป็นเรื่องเกี่ยวกับ ASCII, Unicode, UTF-8 และอื่นๆ เพื่อแก้ไขความสับสนของตัวเองด้วย อ่านมาหลายรอบ แต่จำไม่ได้ซะทีเหมือนกัน

ความแตกต่างระหว่าง Binary กับ Text

Binary ในที่นี้จะหมายถึง byte (8 bit-binary แต่ต่อไปนี้จะขอใช้ byte แทนที่ binary ทั้งหมด ยกเว้นเมื่อต้องการเน้นความแตกต่าง) ที่เรียงต่อกันอ่านเป็นภาษาคนไม่รู้เรื่อง ส่วน Text คือ character (ตัวอักษร) ที่เรียงต่อกัน อ่านเป็นภาษาคนรู้เรื่อง (ภาษาคนที่เขียนไม่รู้เรื่อง เช่น w,ji^hginjv’ ก็ถือว่าเป็น text เหมือนกันนะ) ตัวอย่าง เราจะเห็น Text ได้จากไฟล์ เช่น .txt และ Binary ได้จากไฟล์ .jpg (ต้องเปิดเป็น binary) เป็นต้น

Text จริงๆ แล้วก็คือ Byte

อย่างที่ทุกคนทราบ ข้อมูลที่คอมพิวเตอร์ที่เราใช้ในปัจจุบันรู้จักแต่เลข 0 กับ 1 คำถามคือ ตัวอักษรที่กำลังอ่านอยู่มาจากไหน?
คำตอบคือ มาจาก Byte เพียงแต่เรารู้วิธี Interpret (ตีความ) หรือ Decode (ถอดรหัส) (ต่อไปนี้จะขอใช้คำว่าถอดรหัส) มันออกมา ซึ่งถ้าเราไม่รู้วิธีถอดรหัส เราก็จะเห็นมันเป็นตัวอักษรมั่วอ่านไม่ออก
คำถามต่อไปคือ Byte ที่ว่าไปอยู่ในคอมได้อย่างไร คำตอบคือ เราป้อนข้อมูลภาษาคนเข้าไปให้คอม Encode (เข้ารหัส) (ต่อไปนี้จะขอใช้คำว่าเข้ารหัส) เพื่อให้อยู่ในคอมซึ่งรู้จักแต่ 0 กับ 1 ได้

ASCII Encoding

ASCII เป็นหนึ่งในมาตรฐานการเข้ารหัสตัวอักษรในยุคแรกๆ (คนเขียนเขียนเอง) ที่เข้ารหัสตัวอักษรภาษาอังกฤษทั้งหมดและตัวอักษรควบคุม (control character เช่น \n) ไว้ภายใน 7 bit แต่คอมสมัยนั้นใช้ Byte ที่มีขนาด 8 bit ดังนั้นบิตที่ 8 ที่เหลือจึงถูกนำไปใช้เพื่อแสดงตัวอักษรตามแต่ผู้ผลิตจะเพิ่ม หรือจะเอาบิตที่ 8 ไปทำอะไรก็ตามแต่ต้องการ
ปัญหาคือ โค้ดที่ถูกใส่เพิ่มเข้าไปนั้นแตกต่างกันตามแต่ละผู้ผลิต ถ้าหากเราส่งข้อความที่มีตัวอักษรซึ่งถูกเข้ารหัสตั้งแต่ 128-255 ไปหาคอมที่ใช้คนละมาตรฐานและมันไม่ compatible กัน (ไม่เข้ากัน) ก็จะเห็นตัวอักษรคนละอย่างกัน

Code Page Encoding

เป็น Set ของ character encoding อีกเช่นเคย แต่ทุกคนใช้โค้ดที่ต่ำกว่า 128 เหมือนกันหมด (ตรงกับ ASCII) ทำให้เข้ากันได้ เพียงแต่โค้ดตั้งแต่ 128 ขึ้นไปก็เป็นไปตามแต่ละผู้ผลิตและผู้ผลิตก็มีหลาย Code page หาดูได้ว่า Code page แต่ละอันมีโค้ดอะไรบ้างได้ที่ http://www.i18nguy.com/unicode/code… Windows ก็มี Code page ภาษาไทยชื่อ Windows CP 874
Code page ที่ว่านี่ไม่ได้ใช้แค่ 1 byte เพื่อเก็บโค้ดแล้ว แต่มีถึง 2 bytes ด้วยกัน แต่ปัญหาเดิมๆ ก็ยังคงอยู่ คือ มีมาตรฐานเยอะมาก จึงเกิดมาตรฐานใหม่ขึ้นมาอีกคือ

Unicode

จากปัญหาที่ ASCII Encoding รองรับแค่ภาษาอังกฤษเพียงภาษาเดียวและมี Code page ตามแต่ละผู้ผลิตจะออกมาเต็มไปหมด จึงมี Unicode Consortium (สมาคม Unicode) เกิดขึ้น โดยพยายามที่จะสร้างมาตรฐานเกี่ยวกับการจัดการตัวอักษรขึ้นมา
อย่างแรกที่ถูกกำหนดขึ้นมาคือเซ็ตของตัวอักษรที่ Unicode จะดูแล และให้ตัวเลขเพื่อแสดง/แทนตัวอักษรเหล่านี้เรียกว่า Code point ซึ่งปกติจะเขียนด้วย “U+” ตามด้วยตัวเลขฐาน 16 ซึ่งเป็น Code point นั้น เช่น U+0061 (code point ของ a) Code point ไม่ได้เกี่ยวอะไรกับการเก็บตัวอักษรนี้ในคอมตรงๆ แม้แต่น้อย สำหรับ Unicode มีได้สูงสุดถึง 1,114,112 ตัว แต่ก็ไม่ใช่ว่าทุก Code point จะถูกกำหนดไปแทนตัวษร
อย่างต่อมาที่ถูกกำหนดขึ้นคือ Encoding ซึ่งก็คือกำหนดวิธีการเก็บตัวอักษรที่ถูกระบุใน Unicode เอาไว้ในคอมพิวเตอร์

UTF-16

UTF ย่อมาจาก Unicode transformation format ถ้าแปลตรงตัวก็อาจจะเป็น “รูปแบบการเปลี่ยน Unicode” ซึ่งตรงกับสิ่งที่ทำก็คือเป็นมาตรฐานการเปลี่ยน Code point มาเป็นเป็นตัวเลข 16 bits หรือ 2 bytes เรียกว่า 1 unit code ซึ่งเก็บ Unicode ได้ตั้งแต่ช่วง U+0000 ถึง U+D7FF และ U+E000 ถึง U+FFFF โดยเรียก Code point ช่วงนี้ว่า Basic Multilingual Plane ภาษาญี่ปุ่น, จีนและไทยก็อยู่ในช่วงนี้ด้วย ตัวเลขของทั้ง Code point และตัวอักษรที่ถูกเข้ารหัสแบบ UTF-16 จะเท่ากันเป๊ะ เช่น ก มี Code point เป็น U+0E01 ตรงกับ UTF-16 นั่นคือ 0E01

UTF-16 ไม่ได้ใช้เพียงแค่ 16 bit หรือ 2 bytes เท่านั้น แต่ยังใช้ Surrogate pairs นั่นคือใช้ 2 unit codes (ทั้งหมดเป็น 32 bit หรือ 4 bytes) เพื่อแสดงตัวอักษร Unicode เพียงตัวเดียว ใช้เข้ารหัสตัวอักษรอยู่นอกเหนือจาก Basic Multilingual Plane ตัวอย่างเช่น Emoji เป็นต้น

BOM (อาจจะข้ามไปก็ได้)

UTF-16 ยังมีเรื่อง Byte order mark หรือ BOM เข้ามาเกี่ยวข้องด้วย โดยจะเป็นตัวระบุว่าเราเข้ารหัส UTF-16 แบบไหนระหว่าง Big-endian หรือ Little-endian (ต่อไปนี้ขอย่อเป็น BE และ LE) อธิบาย Endianness แบบง่ายๆ คือหมายถึงลำดับที่ใช้ในการแปลความหมายของ Byte ตัวอย่างง่ายๆ ในชีวิตประจำวันก็เช่น หนึ่งร้อยยี่สิบสาม จะถูกเขียนแทนด้วย 123 ซึ่งเป็นแบบ BE โดยเขียนตัวเลขหลักที่มีค่ามากที่สุดไว้ทางซ้ายมือและหลักที่มีค่าน้อยที่สุดไว้ทางขวามือ ในทางตรงกันข้าม ถ้าต้องการเขียนเลข  หนึ่งร้อยยี่สิบสาม เป็นแบบ LE จะเขียนว่า 321 ซึ่งถ้าไม่รู้ว่าตัวเลขนี้ถูกเขียนแบบไหน ก็จะตีความผิดไปทันที UTF-16 มีปัญหาตรงนี้เพราะใช้ 2 bytes เพื่อเก็บข้อมูล ตัวอย่างเช่น ตัวอักษร a (U+0061) ถ้าถูกเข้ารหัสเป็น UTF-16 BE จะได้ 00 61 แต่ถ้าเป็น UTF-16 LE ก็จะได้ 61 00 ซึ่งไม่มีอะไรผิดเพราะขึ้นอยู่กับสถาปัตยกรรม

BOM ที่ว่านี้คือค่า U+FEFF ซึ่งถูกใส่ไว้ข้างหน้าโค้ดของจริงตัวแรก ถ้าทั้งฝั่งเข้ารหัสและฝั่งถอดรหัสใช้ Endianness เหมือนกัน ก็จะอ่านได้เป็นตัวอักษรตัวเดียวกัน (U+FEFF) แต่กลับกันถ้าฝั่งเข้ารหัสและฝั่งถอดรหัสไม่ได้ใช้ Endianness เดียวกัน พอเวลาอ่านเจอ BOM จะอ่านได้เป็นอีกค่าหนึ่งคือ U+FFFE ซึ่งตัวอักษรตัวนี้ก็ถูกกันเอาไว้เพื่อเป็น BOM แล้ว แต่ถ้าไม่มี BOM มาตรฐาน  RFC 2781 ระบุว่าให้เดาว่าเป็น BE เสมอ แต่ในทางปฏิบัติ Windows ใช้ LE เป็นค่าเริ่มต้น (น่าจะเป็นเพราะ Intel ใช้ LE) และแอปพลิเคชันอื่นๆ ก็มีเดาไว้ว่าเป็น LE ด้วยเหมือนกัน

ตามมาตรฐานยังอนุญาติให้ระบุ Endianness เอาไว้ได้ จึงมี UTF-16BE และ UTF-16LE ด้วย และถ้าถูกระบุชัดเจนแล้ว ไม่จำเป็นต้องใส่ BOM ลงไปอีก แต่ถ้ายังมีอยู่ก็ให้จัดการเป็นตัวอักษร ZERO WIDTH NO-BREAK SPACE

นอกใช้ BOM เพื่อระบุ Endianness แล้ว Web browser ยังใช้ BOM เพื่อดู Encoding ด้วย

UTF-8

มาถึง UTF-8 ที่ทุกคนน่าจะได้ยินบ่อยที่สุดในตระกูล UTF เนื่องจากถูกนำไปกำหนดเป็นมาตรฐานการเข้ารหัสสำหรับมาตรฐานอื่นๆ; UTF-8 สามารถเข้ารหัส Unicode ได้ทุกตัวโดยไม่ต้องใช้ Surrogate pairs แล้ว เนื้อที่ที่ใช้ในการเก็บใช้หน่วย 8 bit = 1 code unit และใช้สูงสุด 4 code units (32 bit)

UTF-8 ถูกออกแบบมาให้ใช้ร่วมกับ ASCII ได้เพราะ UTF-8 ใช้จำนวนบิตน้อยที่สุดคือ 8 bit ตรงกันข้ามกับ UTF-16 ที่ใช้จำนวนบิตน้อยที่สุดคือ 16 bit จึงทำให้ใช้ร่วมกับ ASCII ไม่ได้ ตัวอย่างตัวอักษร a ที่เข้ารหัสเป็น ASCII จะได้(ทั้งหมดเป็นฐาน 16) 61, Unicode code Code point ของ a เท่ากับ U+0061 เมื่อเข้ารหัสเป็น UTF-16 จะได้ 0061 แต่ถ้าเข้ารหัสเป็น UTF-8 จะได้ 61 จะเห็นได้ว่า ถ้าเราเข้ารหัสด้วย ASCII และมาลองอ่านโดยถอดรหัสแบบ UTF-8 เราจะยังอ่านได้ถูกต้อง แต่ถ้ามาถอดรหัสเป็นแบบ UTF-16 เราจะอ่านไม่รู้เรื่อง

UTF-8 ยังถูกออกแบบมาให้แก้ปัญหาเรื่อง Endianness ไม่ตรงกันระหว่างผู้เข้ารหัสและถอดรหัส ทำให้ไม่จำเป็นต้องใช้ BOM อีกต่อไป แต่ก็ยังมีบางครั้งที่ใส่ BOM เข้าไปเพื่อให้สามารถระบุได้ว่าเป็น Data stream ของ UTF-8 แต่อาจจะมีปัญหากับเครื่องมือหรืออะไรก็ตามแต่ ที่อ่าน UTF-8 แต่ไม่ได้ต้องการจัดการกับ BOM ตัวอย่างแอปพลิเคชันที่ใส่ BOM เข้าไปใน UTF-8 คือ Notepad

UTF-32

UTF-32 ใช้ 32 bits = 1 code unit และใช้สูงสุดเพียงแค่ 1 code unit เท่านั้น ตัวเลขที่ได้จากการเข้ารหัส UTF-32 จะเท่ากับ Unicode Code point ทั้งหมด จบ…

(แถม) Binary to Text encoding

ข้างบนทั้งหมดเป็นการเก็บเอาตัวหนังสือไว้ในคอมด้วยการเข้ารหัสตัวอักษรไปเป็น binary แต่กลับกันก็มีการเข้ารหัส byte ไปเป็นตัวอักษรเพื่อใช้งานกับโปรโตคอลที่เป็น Text-based เช่น HTTP, JSON เป็นต้น และก็อยากที่กล่าวไว้ด้านบน ถ้าตัวอักษรที่ได้จากการเข้ารหัส Binary เหล่านี้จะเป็นต้องอยู่บนคอมพิวเตอร์ ก็ต้องถูกเข้ารหัสเป็น Binary อีกทีหนึ่งเสมอ (ยกเว้นจะพิมพ์ใส่กระดาษมาอ่าน แล้วลบทิ้ง – -*)

base64 encoding

เป็นมาตรฐานการเข้ารหัสจาก byte เป็น text ที่นิยมใช้สูงมาก ตรงตามชื่อคือมีตัวอักษรที่ถูกเลือกมาใช้ทั้งหมด 64 ตัว แต่ก็มีมาตรฐานย่อยๆ อีกซึ่งตัวอักษรทั้ง 64 ตัวอาจจะไม่เหมือนกันทุกตัว

คำถามที่อาจจะสงสัย

Q: ถ้า Unicode ไม่ใช่การเข้ารหัส แล้วทำไม Notepad บน Windows ถึงมี Unicode เป็นตัวเลือกเวลาเข้ารหัส
A: เพราะ UTF-16 เข้ารหัสตัวอักษรที่มีค่าเท่ากับ code point (เฉพาะ BMP) (คิดเอาเอง) Windows จึงเรียก Unicode

อ้างอิง