- ใน ASCII ตัว
Z ถูกจัดวางไว้ที่ 90 และ a อยู่ที่ 97 โดยมีอักขระ 6 ตัวคั่นกลาง ทำให้ผลต่างของรหัสระหว่างตัวพิมพ์ใหญ่กับตัวพิมพ์เล็กลงตัวที่ 32
- 32 คือ
2^5 ดังนั้นตัวอักษรพิมพ์ใหญ่และพิมพ์เล็กที่เป็นคู่กัน เช่น A 65 และ a 97 จึงต่างกันเพียงบิตเดียวคือ 00100000
- การจัดวางแบบนี้ทำให้สามารถแปลงเป็นตัวพิมพ์ใหญ่ได้ด้วยการทำ AND กับค่ากลับบิตของ
32, แปลงเป็นตัวพิมพ์เล็กได้ด้วยการทำ OR กับ 32, และสลับตัวพิมพ์เล็ก-ใหญ่ได้ด้วยการทำ XOR กับ 32
- ลำดับของตัวอักษรสามารถหาได้โดยทำ AND ระหว่างรหัสอักขระกับ
31 เพื่อคงไว้เฉพาะ 5 บิตล่าง โดย A/a จะได้ 1 และ Z/z จะได้ 26
- ASCII เป็นชุดเข้ารหัสอักขระยุคแรกที่ใช้ 7 บิต จึงแทนได้เพียง 128 code points และปัจจุบัน 128 code points แรกของ Unicode ก็ยังเหมือนกับ ASCII
การจัดวางของ ASCII และผลต่าง 32
- ในตาราง ASCII ค่ารหัสของตัวพิมพ์ใหญ่
Z คือ 90 ส่วนตัวพิมพ์เล็ก a ไม่ได้อยู่ค่าถัดไปทันที แต่ถูกวางไว้ที่ 97
- ระหว่างนั้นมีอักขระ 6 ตัวคือ
[ \ ] ^ _ `
- ตัวอักษรภาษาอังกฤษมี 26 ตัว และเมื่อรวม 6 อักขระนี้เข้าไปจะได้
26 + 6 = 32
32 คือค่า 2^5 จึงทำให้ความสัมพันธ์ระหว่างตัวพิมพ์ใหญ่กับตัวพิมพ์เล็กถูกจัดเรียงให้ต่างกันเพียงบิตเฉพาะตำแหน่งเดียว
- ตัวอย่างเช่น
A คือ 65 หรือ 01000001 และ a คือ 97 หรือ 01100001 ซึ่งต่างกัน 32
ความสัมพันธ์ระหว่าง ASCII และ Unicode
- ASCII เป็นหนึ่งในรูปแบบการเข้ารหัสอักขระยุคแรก โดยใช้เพียง 7 บิต จึงแทน
2^7 = 128 code points ได้
- code points จำนวน 128 ตำแหน่งนั้นไม่เพียงพอสำหรับเก็บอักขระทั้งหมดที่มนุษย์ใช้ โดยเฉพาะภาษาอย่างภาษาจีนที่มีตัวอักษรนับหมื่นตัว
- ปัจจุบันชุดอักขระมาตรฐานที่ใช้คือ Unicode ซึ่งมีหลายรูปแบบการเข้ารหัส เช่น UTF-8 และ UTF-16
- 128 code points แรกของ Unicode เหมือนกับ ASCII ทุกประการ
บิตที่ 5 ซึ่งใช้แยกตัวพิมพ์เล็กและตัวพิมพ์ใหญ่
- เมื่อนำตัวพิมพ์ใหญ่และตัวพิมพ์เล็กที่เป็นคู่กันมาเทียบในรูปเลขฐานสอง จะพบว่ามีเพียงบิตที่ตรงกับค่า
32 เท่านั้นที่เปลี่ยนไป
65 = 01000001 = A
97 = 01100001 = a
66 = 01000010 = B
98 = 01100010 = b
67 = 01000011 = C
99 = 01100011 = c
32 ในเลขฐานสองคือ 00100000 และบิตนี้เองที่สร้างความต่างระหว่างตัวพิมพ์ใหญ่กับตัวพิมพ์เล็ก
- การจัดวางตัวอักษรใน ASCII จึงทำให้สามารถแปลงตัวพิมพ์เล็ก-ใหญ่ได้ง่ายด้วยการทำ bitwise operation
จัดการตัวพิมพ์เล็ก-ใหญ่ด้วย bitwise operation
-
แปลงเป็นตัวพิมพ์ใหญ่
- หากต้องการทำให้อักขระเป็นตัวพิมพ์ใหญ่ ให้ทำ bitwise AND กับค่ากลับบิตของ
32
0 1 1 0 0 0 0 1 (97 = 'a')
& 1 1 0 1 1 1 1 1 (mask)
-------------------
0 1 0 0 0 0 0 1 (65 = 'A')
- เมื่อนำไปใช้กับ
a ค่า 97 จะเปลี่ยนเป็น 65 กลายเป็น A
- ถ้านำการดำเนินการเดียวกันไปใช้กับ
A ที่เป็นตัวพิมพ์ใหญ่อยู่แล้ว ก็จะยังคงเป็น A เหมือนเดิม
-
แปลงเป็นตัวพิมพ์เล็ก
- หากต้องการทำให้อักขระเป็นตัวพิมพ์เล็ก ให้ทำ bitwise OR กับ
32
0 1 0 0 0 0 0 1 (65 = 'A')
| 0 0 1 0 0 0 0 0 (32)
-------------------
0 1 1 0 0 0 0 1 (97 = 'a')
- เมื่อนำไปใช้กับ
A ค่า 65 จะเปลี่ยนเป็น 97 กลายเป็น a
- ถ้านำการดำเนินการเดียวกันไปใช้กับ
a ที่เป็นตัวพิมพ์เล็กอยู่แล้ว ก็จะยังคงเป็น a เหมือนเดิม
-
สลับตัวพิมพ์เล็ก-ใหญ่
- หากต้องการสลับตัวพิมพ์เล็ก-ใหญ่ ให้ทำ bitwise XOR กับ
32
0 1 1 0 0 0 0 1 (97 = 'a')
^ 0 0 1 0 0 0 0 0 (32)
-------------------
0 1 0 0 0 0 0 1 (65 = 'A')
0 1 0 0 0 0 0 1 (65 = 'A')
^ 0 0 1 0 0 0 0 0 (32)
-------------------
0 1 1 0 0 0 0 1 (97 = 'a')
a จะเปลี่ยนเป็น A และ A จะเปลี่ยนเป็น a
หาลำดับตัวอักษรจาก 5 บิตล่าง
- ลำดับของตัวอักษรสามารถหาได้โดยทำ bitwise AND ระหว่างรหัสอักขระกับ
31
0 1 0 0 0 0 0 1 (65 = 'A')
& 0 0 0 1 1 1 1 1 (31)
-------------------
0 0 0 0 0 0 0 1 (1)
0 1 1 1 1 0 1 0 (122 = 'z')
& 0 0 0 1 1 1 1 1 (31)
-------------------
0 0 0 1 1 0 1 0 (26)
31 ในเลขฐานสองคือ 00011111 จึงลบบิตด้านหน้าออกและคงไว้เฉพาะ 5 บิตล่าง
- ใน ASCII ค่า 5 บิตล่างของตัวอักษรจะตรงกับตำแหน่งของตัวอักษรในลำดับพอดี
A/a ลงท้ายด้วย 00001 จึงได้ 1, B/b ลงท้ายด้วย 00010 จึงได้ 2, และ Z/z ลงท้ายด้วย 11010 จึงได้ 26
- ในรหัสอักขระ ASCII ค่า
c & 31 จะเท่ากับ c % 32
- เพราะ
32 เป็นกำลังของ 2 การ mask ด้วย 31 จึงเท่ากับการตัดกลุ่มทีละ 32 ออกไปและเก็บเฉพาะส่วนที่เหลือไว้
'A' = 65 → 65 % 32 = 1
'B' = 66 → 66 % 32 = 2
...
'Z' = 90 → 90 % 32 = 26
'a' = 97 → 97 % 32 = 1
'b' = 98 → 98 % 32 = 2
...
'z' = 122 → 122 % 32 = 26
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
คำอธิบายก็โอเค แต่รู้สึกว่าฝั่ง https://garbagecollected.org/2017/01/31/four-column-ascii/ อธิบายได้ดีกว่า
ไม่ใช่แค่เรื่องของ Shift อย่างเดียว แต่ Ctrl ก็เกี่ยวด้วย เช่น Tab คือ Ctrl-I โดยที่ I คือ
1001001และ Ctrl จะ mask บิตแรกออก เหลือเป็น Tab0001001ถ้าใช้ ตรรกะแบบเครื่องกลไฟฟ้า แทนตรรกะอิเล็กทรอนิกส์เหมือนผู้ผลิตในยุค 1960s bit paired keyboard จะทำได้ง่ายกว่ามาก
ปุ่ม Shift แค่ toggle บิตเดียวในอักขระ ASCII ก็พอ ทุกวันนี้ใส่ CPU เอนกประสงค์ลงในคีย์บอร์ดทุกตัวและตรรกะแทบจะฟรีอยู่แล้ว แต่ในยุคที่คอมพิวเตอร์เอนกประสงค์มีขนาดเต็มห้อง วิธีแก้แบบนั้นแพงกว่ามาก
บทความเสนอว่าแรงจูงใจของการจัดวาง ASCII คือทำให้การแปลงตัวพิมพ์เล็ก/ใหญ่มีประสิทธิภาพด้วย การดำเนินการระดับบิต แต่คุณค่าของมัน ต่อให้ในเชิงประวัติศาสตร์จะสำคัญ ก็ยังดูบางมากในยุคปัจจุบัน
การแปลง
a→Aใช้ได้แค่กับข้อความธรรมดาเท่านั้น และแม้ ISO-8859-1 หรือ Unicode จะขยายการจัดวางนี้ไปบางส่วนกับอักขระอย่างü,çการแปลงตัวพิมพ์เล็ก/ใหญ่ก็ยังขึ้นกับภูมิภาค บริบท และช่วงเวลา การจับคู่ตัวพิมพ์ใหญ่/เล็กที่ถูกต้องของßหรือïขึ้นอยู่กับภาษา หน่วยงานกำกับดูแล พื้นที่ใช้งาน และยุคสมัยUnicode มีตาราง mapping และ folding สำหรับตัวพิมพ์เล็ก/ใหญ่ เช่น https://www.unicode.org/charts/case/ ซึ่งให้คำตอบที่ใกล้เคียงได้ในกรณีทั่วไป แต่เพราะเป็นปัญหาที่มีนโยบายของมนุษย์เข้ามาเกี่ยว ความซับซ้อนจึงแทบไร้ขีดจำกัด และถ้าจะให้ถูกจริงอาจต้องใช้ซอฟต์แวร์ที่ปรับเฉพาะทาง
ทุกวันนี้ ถ้าไม่สามารถรับประกันได้ว่าข้อมูลจะถูกจำกัดอยู่แค่ 2⁷ code point แรกของ Unicode ค่าออฟเซ็ต
0b0010_0000ก็พังได้ง่าย และย่อมต้องมี code path ที่แพงกว่าการดำเนินการบิตเดียวเข้ามาอย่างหลีกเลี่ยงไม่ได้ แม้ใน CPython เอง''.upperจะผ่าน ASCII fast path ของunicode_upper{,_impl}แต่ในความเป็นจริงก็ยังต้องผ่าน branch, inline function, การเข้าถึง struct, ฟังก์ชัน และ ฟังก์ชัน หลายตัว, macro และทำ table lookupการติดตั้ง
''.upperในภาษาสมัยใหม่ก็น่าจะมีความซับซ้อนระดับใกล้เคียงกัน และต่อให้คอมไพเลอร์ช่วย optimize แนวทางสมัยใหม่ก็ดูจะเลี่ยงไม่ได้ที่จะมีค่าใช้จ่ายสูงกว่าการดำเนินการระดับบิตเพียงครั้งเดียว ข้อดีของการออกแบบนี้น่าจะเด่นชัดเฉพาะในซอฟต์แวร์เฉพาะทาง เช่น การทำ arithmetic กับnumpy.ndarrayที่เป็นdtype=uint8ซึ่งเก็บข้อมูลไบนารีดิบหรือข้อมูล Unicode ที่มี code point ไม่เกิน 2⁷สิ่งที่สงสัยคือ: ถ้าสมมติว่าแรงจูงใจเดียวของการเลือกออกแบบนี้คืออย่างที่บทความบอก ตอนนั้นก็ยังมีทางเลือกอย่าง ตาราง lookup ขนาด 128 ไบต์ อยู่แล้ว แล้วในประวัติศาสตร์คอมพิวเตอร์ มีช่วงไหนบ้างที่การดำเนินการบิตเดียวมีประสิทธิภาพหรือทำได้จริงมากกว่าการ lookup ตาราง 128 ไบต์?
การตัดสินใจออกแบบในช่วงเวลาหนึ่ง ไปยึดรูปร่างของบางอย่างไว้บนสมมติฐานที่ภายหลังจะใช้ไม่ได้ และทำให้การขยายต่อยากหรือแทบเป็นไปไม่ได้ บทความเรื่อง IPv6 ช่วงหลัง ๆ ก็สะท้อนให้เห็นว่าการเลือกของ IPv4 และ IPv6 ที่เคยถูกต้องในตอนนั้น กลายเป็นภาระมหาศาลในวันนี้
ถ้ามองในแง่ดี การเลือกแบบนี้คงเกิดจากการชั่งน้ำหนักระหว่างความเสี่ยงกับผลประโยชน์ ประมาณว่า “ถ้าเปลี่ยนอักขระหนึ่งตัวเป็นตัวพิมพ์ใหญ่ได้ด้วยการดำเนินการบิตเดียว มันก็เร็วกว่าเยอะ จึงคุ้มจะรับความเสี่ยงว่าสักวันหนึ่งพอมีผู้ใช้ภาษาญี่ปุ่นแล้วมันจะพัง”
จากมุมมองของคนปัจจุบัน การเลือกในปี 1976 ทำให้ชีวิตในปี 2026 ยากขึ้น แต่ถ้าในปี 1976 คุณไม่เคยมีคอมพิวเตอร์ใช้ คุณก็ไม่ได้รับประโยชน์นั้นเลยและเหลือแต่ข้อเสีย เมื่อพยายามวางความเห็นแก่ตัวแบบนี้ลง ผมก็เลยสงสัยว่า ถ้าจะสร้างซอฟต์แวร์ที่ยังได้รับความนิยมอีก 20 ปีข้างหน้า เราจะคาดการณ์ ความยั่งยืน ของการเลือกแบบนี้ให้ดีขึ้นได้อย่างไร
tolowerแบบเร็วระดับบิตก็ยังมีประโยชน์อยู่ https://dotat.at/@/2022-06-27-tolower-swar.htmlอย่างน้อยใน ASCII ตัวอักษรก็ยังเรียงต่อเนื่องกัน EBCDIC มีช่วงห่างเป็น
0x40(64) และเมื่อเทียบกับ ASCII มันเหมือนมีแถวละ 9 ตัวสองแถวกับแถวละ 8 ตัวอีกหนึ่งแถววางซ้อนขึ้นลงhttps://en.wikipedia.org/wiki/EBCDIC
ส่วนรหัส 5 บิต http://www.quadibloc.com/crypto/images/tele38.gif และอุปกรณ์ต่อพ่วง CDC บางตัวที่ถ้าจำไม่ผิดใช้รหัส 6 บิตซึ่งยัด control character สำหรับเปลี่ยนหน้าไว้เต็มไปหมดนั้น ขอละไว้ไม่พูดดีกว่า
ทำให้นึกถึงตอนที่ชื่อเล่น IRC เป็นแบบ ไม่สน ASCII ตัวพิมพ์เล็ก/ใหญ่ เลยทำให้
foo{เท่ากับfoo[และbar|เท่ากับbar\จะไม่แปลกใจเลยถ้ายังมีไคลเอนต์บางตัวสับสนเพราะเรื่องนี้อยู่