φυβλαςのβλογ
phyblas的博客



javascript เบื้องต้น บทที่ ๔๕: เลขจำนวนเต็มที่มีค่าใหญ่มาก
เขียนเมื่อ 2020/05/09 08:05
แก้ไขล่าสุด 2021/09/28 16:42

ในบทนี้จะเขียนถึงข้อมูลชนิด bigint ซึ่งใช้กับตัวเลขที่มีขนาดใหญ่มากๆ ซึ่งเป็นข้อมูลชนิดใหม่ที่ถูกเพิ่มเข้ามาใน ES2020




ข้อจำกัดของจำนวนตัวเลขแบบดั้งเดิม

ในบทที่ ๓ ได้เขียนถึงชนิดของตัวแปรโดยพื้นฐานไปแล้ว

โดยในจาวาสคริปต์นั้นข้อมูลชนิดตัวเลขเดิมมีอยู่แค่ชนิดเดียวคือ number ไม่มีการแบ่งเป็นจำนวนเต็มหรือจำนวนเลขทศนิยม

อย่างไรก็ตาม ตัวเลขทั่วไปที่สร้างขึ้นมาแบบปกตินั้นมีข้อจำกัดอยู่ คือถ้าตัวเลขมีขนาดใหญ่มากเกินไปอาจทำให้การบันทึกข้อมูลและการคำนวณมีปัญหา

ขีดจำกัดตรงนั้นมีค่า เป็น 253-1 ซึ่งเป็นเลข ๑๖ หลัก

ตัวเลขขีดจำกัดสูงสุดนั้นมีการเก็บไว้ที่ Number.MAX_SAFE_INTEGER
alert(Number.MAX_SAFE_INTEGER); // ได้ 9007199254740991
alert(2**53-1); // ได้ 9007199254740991

หากตัวเลขเกินกว่าค่านี้ไปแล้วจะไม่สามารถรักษานัยสำคัญของตัวเลขไว้ทั้งหมดได้ และจะมีการปัดเศษเกิดขึ้น

เช่น
let n = 7777777777777777777;
alert(n); // ได้ 7777777777777778000

ในที่นี้ใส่ตัวเลข 7 ไปทั้งหมด ๑๙ หลัก แต่ผลที่ออกมาจริงๆ ๔ ตัวหลังกลับถูกปัดเป็น 8000 นั่นเพราะนัยสำคัญยาวเกิน ถูกปัดเหลือ ๑๖ หลัก

โอกาสที่จะต้องจัดการกับตัวเลขใหญ่ขนาดนี้อาจจะมีไม่มากนัก แต่สำหรับงานบางอย่างที่ละเอียดอ่อนมากก็อาจจำเป็นต้องทำการคำนวณที่อาศัยความแม่นยำสูงคลาดเคลื่อนไม่ได้

ด้วยเหตุนี้ ใน ES2020 จึงได้เพิ่มข้อมูลชนิด bigint ขึ้นมา เพื่อจะใช้เก็บตัวเลขขนาดใหญ่




การสร้างและใช้ bigint

ข้อมูลชนิด bigint สามารถสร้างขึ้นได้ด้วยวิธีที่เหมือนกับตัวเลข number ธรรมดา แต่เวลาเขียนให้ลงท้ายด้วย n เช่น
let n = 7777777777777777777n;
alert(n); // ได้ 7777777777777777777

ดูเผินๆก็ไม่ต่างจากตัวเลขธรรมดา แค่เติม n ลงท้าย แต่ข้อแตกต่างคือจะเห็นว่าเลข 7 ทั้งหมด ๑๙ หลักสามารถยังคงรักษาไว้ได้อย่างถูกต้อง ไม่ถูกปัด

เวลา bigint ถูกแปลงเป็นสายอักขระเพื่อแสดงผล เช่นเวลาใช้ alert แบบนี้จะไม่มี n ตามหลัง

ลองใช้ typeof เพื่อดูชนิดข้อมูลก็จะได้ bigint
let n = 7n;
alert(typeof n); // ได้ bigint

นอกจากนั้นยังสามารถสร้างได้ด้วยฟังก์ชัน BigInt() โดยแปลงจากข้อมูลชนิดอื่น เช่น
let n1 = BigInt(100); // สร้างจากจำนวนธรรมดา
let n2 = BigInt("100"); // สร้างจากสายอักขระ
let n3 = BigInt(true); // สร้างจาก true ได้ 1
let n4 = BigInt(false); // สร้างจาก false ได้ 0
let n5 = BigInt([]); // สร้างจากสายแถวลำดับเปล่า ได้ 0
let n6 = BigInt([500]); // ได้ 500
let n7 = BigInt([500,500]); // ผิดพลาด Uncaught SyntaxError: Cannot convert 500,500 to a BigInt
let n8 = BigInt(null); // ผิดพลาด Uncaught TypeError: Cannot convert null to a BigInt

bigint เป็นได้แค่จำนวนเต็มเท่านั้น ถ้าพยายามแปลงจากเลขทศนิยมก็จะเกิดข้อผิดพลาด
let n11 = 1.1n; // ผิดพลาด SyntaxError: identifier starts immediately after numeric literal
let n12 = BigInt(1.1); // ผิดพลาด RangeError: 1.1 can't be converted to BigInt because it isn't an integer




การคำนวณด้วย bigint

จำนวน bigint สามารถนำมาบวกลบคูณหาหรือยกกำลังได้เหมือนตัวเลขทั่วไป
alert(10n+2n); // ได้ 12
alert(10n-2n); // ได้ 8
alert(10n*2n); // ได้ 20
alert(10n/2n); // ได้ 5
alert(10n**2n); // ได้ 100

เพียงแต่เวลาหารเศษจะถูกปัดทิ้งเพราะเป็นได้แค่จำนวนเต็ม
alert(8n/9n); // ได้ 0
alert(100n/11n); // ได้ 9

จำนวน bigint เอามาคำนวณร่วมกับจำนวนธรรมดาไม่ได้
alert(20n+7); // ผิดพลาด TypeError: can't convert BigInt to number

ถ้าจะทำก็ต้องแปลงข้อมูลให้เป็นชนิดเดียวกันก่อน
alert(20n + BigInt(7)); // ได้ 27
alert(Number(20n) + 7); // ได้ 27

แต่ว่าบวกกับสายอักขระหรือออบเจ็กต์ได้
alert(1n+['23',4]); // ได้ 123,4
alert(15n+{}); // ได้ 15[object Object]

ถ้านำมาเปรียบเทียบกับจำนวนธรรมดาหรือเลขในสายอักขระโดยใช้ == จะเทียบเท่ากันได้ แต่ถ้าใช้ === จึงจะไม่เท่า
alert(1n==1); // ได้ true
alert(1n=='1'); // ได้ true
alert(1n===1); // ได้ false
alert(1n===BigInt(1)); // ได้ true

bigint สามารถนำมาใช้เปรียบเทียบหรือจัดเรียงพร้อมกับจำนวนธรรมดาได้
alert(88n<77); // ได้ false
alert(88n<88); // ได้ true
alert(88n<88); // ได้ false

let ar = [7,7n,99,7.1,-7,333n,-1,-6n,7]
alert(ar.sort()); // -7,-1,-6,333,7,7,7,7.1,99

พวกฟังก์ชันใน Math จะใช้กับ bigint ไม่ได้
alert(Math.max(1n,2n)); // ผิดพลาด TypeError: can't convert BigInt to number

ถ้าค่าเป็นตัวเลขยาวมาก เลขธรรมดาจะถูกเขียนในรูป e (คูณยกกำลัง 10) แต่ bigint จะแสดงตัวเลขทั้งหมดไม่ว่าจะยาวแค่ไหน
alert(2**128); // ได้ 3.402823669209385e+38
alert(2n**128n); // ได้ 340282366920938463463374607431768211456

ต่อให้เป็นเลขมหาศาลยาวเป็นหมื่นหลักเช่น 2218 ก็ยังแสดงตัวเลขนั่นออกมาทั้งหมด
alert(String(2n**(2n**18n)).length); // ได้ 78914




BigInt.asIntN และ BigInt.asUintN

ออบเจ็กต์ BigInt มีฟังก์ชันในตัวคือ BigInt.asIntN และ BigInt.asUintN ใช้กำหนดขอบเขตขนาดของ bigint ไม่ให้ใหญ่เกินจำนวนบิตที่กำหนด

วิธีใช้เขียนเป็น
BigInt.asIntN(จำนวนบิต, ตัวเลข bigint)

ขอบเขตของตัวเลขที่ใช้ BigInt.asIntN คือ -2จำนวนบิต-1 ไปจนถึง 2จำนวนบิต-1-1 หากเกินไปจากขอบเขตนั้นจะได้ค่าที่กลับด้านกัน

เช่น 24-1=8 ดังนั้นถ้าใส่จำนวนบิตเป็น 4 ก็จะใส่ค่าได้ไม่เกิน 7 พอเกินจะกลายไปเป็นค่าต่ำสุด และถ้าเกินกว่านั้นอีกก็ไล่ขึ้นมาเรื่อยๆ
alert(BigInt.asIntN(4, 7n)); // ได้ 7
alert(BigInt.asIntN(4, 8n)); // ได้ -8
alert(BigInt.asIntN(4, 9n)); // ได้ -7
alert(BigInt.asIntN(4, 16n)); // ได้ 0
alert(BigInt.asIntN(4, 17n)); // ได้ 1
alert(BigInt.asIntN(5, 9n)); // ได้ 9
alert(BigInt.asIntN(5, 16n)); // ได้ -16

ส่วน BigInt.asUintN นั้นจะต่างจาก BigInt.asIntN ตรงที่จะเป็นจำนวนบวกเท่านั้น และขีดจำกัดจะเป็น 0 ไปจนถึง 2จำนวนบิต-1
alert(BigInt.asUintN(4, -1n)); // ได้ 15
alert(BigInt.asUintN(4, 15n)); // ได้ 15
alert(BigInt.asUintN(4, 16n)); // ได้ 0




ขีดจำกัดของ bigint

แม้ bigint จะใช้จัดการจำนวนตัวเลขขนาดใหญ่มากๆได้ แต่ก็มีข้อจำกัดอยู่เช่นกัน เนื่องจากหน่วยความจำของคอมพิวเตอร์เป็นสิ่งที่มีจำกัด

ขีดจำกัดของ bigint นั้นไม่ได้ถูกกำหนดไว้แน่ชัด แต่ก็มีคนทดสอบมาว่าอยู่ในช่วงระหว่าง 2230-1 ถึง 2230 (อ้างอิง https://qiita.com/po_tau_feu/items/225f783784f68159c37d)

เมื่อลองใส่ตัวเลขใหญ่ขนาดนั้นไปก็เกิดข้อผิดพลาด
alert(String(2n**(2n**30n)).length); // ผิดพลาด RangeError: BigInt is too large to allocate

แต่จำนวนดังกล่าวนี้ถือเป็นจำนวนมหาศาลยาวเป็นล้านหลัก ซึ่งเกินกว่าที่คนทั่วไปต้องใช้ไปมาก

โดยปกติโอกาสที่จะได้ยุ่งกับจำนวนตัวเลขที่มีขนาดใหญ่ขนาดนั้นคงไม่น่าจะมี จึงอาจไม่จำเป็นต้องไปคิดถึงข้อจำกัดตรงนั้น








-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ