ในบทนี้จะเขียนถึงข้อมูลชนิด bigint ซึ่งใช้กับตัวเลขที่มีขนาดใหญ่มากๆ ซึ่งเป็นข้อมูลชนิดใหม่ที่ถูกเพิ่มเข้ามาใน ES2020
ข้อจำกัดของจำนวนตัวเลขแบบดั้งเดิม
ใน
บทที่ ๓ ได้เขียนถึงชนิดของตัวแปรโดยพื้นฐานไปแล้ว
โดยในจาวาสคริปต์นั้นข้อมูลชนิดตัวเลขเดิมมีอยู่แค่ชนิดเดียวคือ number ไม่มีการแบ่งเป็นจำนวนเต็มหรือจำนวนเลขทศนิยม
อย่างไรก็ตาม ตัวเลขทั่วไปที่สร้างขึ้นมาแบบปกตินั้นมีข้อจำกัดอยู่ คือถ้าตัวเลขมีขนาดใหญ่มากเกินไปอาจทำให้การบันทึกข้อมูลและการคำนวณมีปัญหา
ขีดจำกัดตรงนั้นมีค่า เป็น 2
53-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
ต่อให้เป็นเลขมหาศาลยาวเป็นหมื่นหลักเช่น 2
218 ก็ยังแสดงตัวเลขนั่นออกมาทั้งหมด
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 หากเกินไปจากขอบเขตนั้นจะได้ค่าที่กลับด้านกัน
เช่น 2
4-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 นั้นไม่ได้ถูกกำหนดไว้แน่ชัด แต่ก็มีคนทดสอบมาว่าอยู่ในช่วงระหว่าง 2
230-1 ถึง 2
230 (อ้างอิง
https://qiita.com/po_tau_feu/items/225f783784f68159c37d)
เมื่อลองใส่ตัวเลขใหญ่ขนาดนั้นไปก็เกิดข้อผิดพลาด
alert(String(2n**(2n**30n)).length); // ผิดพลาด RangeError: BigInt is too large to allocate
แต่จำนวนดังกล่าวนี้ถือเป็นจำนวนมหาศาลยาวเป็นล้านหลัก ซึ่งเกินกว่าที่คนทั่วไปต้องใช้ไปมาก
โดยปกติโอกาสที่จะได้ยุ่งกับจำนวนตัวเลขที่มีขนาดใหญ่ขนาดนั้นคงไม่น่าจะมี จึงอาจไม่จำเป็นต้องไปคิดถึงข้อจำกัดตรงนั้น