ใน JavaScript สมัยใหม่ ตัวเลขมีสองประเภท:
ตัวเลขปกติใน JavaScript จะถูกจัดเก็บในรูปแบบ 64 บิต IEEE-754 หรือที่เรียกว่า "ตัวเลขทศนิยมที่มีความแม่นยำสองเท่า" ตัวเลขเหล่านี้เป็นตัวเลขที่เราใช้บ่อยที่สุด และเราจะพูดถึงตัวเลขเหล่านี้ในบทนี้
ตัวเลข BigInt แสดงถึงจำนวนเต็มที่มีความยาวตามใจชอบ บางครั้งจำเป็นต้องใช้เนื่องจากจำนวนเต็มปกติต้องไม่เกิน (2 53 -1)
หรือน้อยกว่า -(2 53 -1)
ได้อย่างปลอดภัย ดังที่เราได้กล่าวไว้ก่อนหน้าในบท Data types เนื่องจากมีการใช้ bigints ในพื้นที่พิเศษบางพื้นที่ เราจึงทุ่มเทให้กับ BigInt บทพิเศษ
ตรงนี้เราจะพูดถึงตัวเลขปกติ. มาขยายความรู้ของเราเกี่ยวกับพวกเขากันดีกว่า
ลองนึกภาพเราต้องเขียน 1 พันล้าน วิธีที่ชัดเจนคือ:
ให้พันล้าน = 1000000000;
เรายังสามารถใช้ขีดล่าง _
เป็นตัวคั่นได้:
ให้พันล้าน = 1_000_000_000;
ในที่นี้ขีดล่าง _
มีบทบาทเป็น "น้ำตาลเชิงวากยสัมพันธ์" ซึ่งทำให้ตัวเลขอ่านง่ายขึ้น เอ็นจิ้น JavaScript จะละเว้น _
ระหว่างหลัก ดังนั้นจึงมีค่าเท่ากับหนึ่งพันล้านเหมือนกับด้านบนทุกประการ
แต่ในชีวิตจริง เราพยายามหลีกเลี่ยงการเขียนเลขศูนย์ที่เรียงกันยาวๆ เราขี้เกียจเกินไปสำหรับเรื่องนั้น เราจะพยายามเขียนประมาณว่า "1bn"
สำหรับหนึ่งพันล้าน หรือ "7.3bn"
สำหรับ 7 พันล้าน 300 ล้าน เช่นเดียวกับตัวเลขที่มีขนาดใหญ่ที่สุด
ใน JavaScript เราสามารถย่อตัวเลขได้โดยการต่อท้ายตัวอักษร "e"
และระบุจำนวนศูนย์:
ให้พันล้าน = 1e9; // 1 พันล้านตามตัวอักษร: 1 และ 9 ศูนย์ การแจ้งเตือน (7.3e9 ); // 7.3 พันล้าน (เท่ากับ 7300000000 หรือ 7_300_000_000)
กล่าวอีกนัยหนึ่ง e
คูณตัวเลขด้วย 1
โดยนับเลขศูนย์ที่กำหนด
1e3 === 1 * 1,000; // e3 หมายถึง *1,000 1.23e6 === 1.23 * 1000000; // e6 หมายถึง *1000000
ทีนี้ลองเขียนเรื่องเล็กๆ น้อยๆ กัน สมมติว่า 1 ไมโครวินาที (หนึ่งในล้านของวินาที):
ให้ mсs = 0.000001;
เหมือนแต่ก่อน การใช้ "e"
สามารถช่วยได้ หากเราต้องการหลีกเลี่ยงการเขียนเลขศูนย์อย่างชัดเจน เราสามารถเขียนได้ดังนี้:
ให้ mcs = 1e-6; // ห้าศูนย์ทางซ้ายจาก 1
หากเรานับศูนย์ใน 0.000001
จะมี 6 ตัว โดยธรรมชาติแล้วมันคือ 1e-6
กล่าวอีกนัยหนึ่ง จำนวนลบหลัง "e"
หมายถึงการหารด้วย 1 และจำนวนศูนย์ที่กำหนด:
// -3 หารด้วย 1 โดยมีศูนย์ 3 ตัว 1e-3 === 1/1,000; // 0.001 // -6 หารด้วย 1 โดยมีศูนย์ 6 ตัว 1.23e-6 === 1.23 / 1000000; // 0.00000123 // ตัวอย่างที่มีจำนวนมากกว่า 1234e-2 === 1234/100; // 12.34 จุดทศนิยมเคลื่อนที่ 2 ครั้ง
เลขฐานสิบหกถูกนำมาใช้กันอย่างแพร่หลายใน JavaScript เพื่อแสดงสี เข้ารหัสอักขระ และสำหรับสิ่งอื่นๆ อีกมากมาย แน่นอนว่ามีวิธีเขียนที่สั้นกว่า: 0x
แล้วตามด้วยตัวเลข
ตัวอย่างเช่น:
การแจ้งเตือน (0xff); // 255 การแจ้งเตือน ( 0xFF ); // 255 (เหมือนกัน case ไม่สำคัญ)
ระบบเลขฐานสองและฐานแปดไม่ค่อยได้ใช้ แต่ก็รองรับการใช้คำนำหน้า 0b
และ 0o
ด้วย:
ให้ a = 0b11111111; // รูปแบบไบนารี่ของ 255 ให้ข = 0o377; // รูปฐานแปดของ 255 การแจ้งเตือน (a == b); // จริงเลข 255 เหมือนกันทั้งสองข้าง
มีระบบตัวเลขเพียง 3 ระบบที่รองรับเช่นนี้ สำหรับระบบตัวเลขอื่นๆ เราควรใช้ฟังก์ชัน parseInt
(ซึ่งเราจะดูในบทนี้ต่อไป)
วิธีการ num.toString(base)
ส่งคืนการแสดงสตริงของ num
ในระบบตัวเลขด้วย base
ที่กำหนด
ตัวอย่างเช่น:
ให้หมายเลข = 255; การแจ้งเตือน( num.toString(16) ); // ฟฟ การแจ้งเตือน( num.toString(2) ); // 11111111
base
อาจแตกต่างกันตั้งแต่ 2
ถึง 36
. โดยค่าเริ่มต้นคือ 10
กรณีการใช้งานทั่วไปสำหรับสิ่งนี้คือ:
base=16 ใช้สำหรับสีฐานสิบหก การเข้ารหัสอักขระ ฯลฯ ตัวเลขอาจเป็น 0..9
หรือ A..F
base=2 ส่วนใหญ่ใช้สำหรับการดีบักการดำเนินการระดับบิต ตัวเลขอาจเป็น 0
หรือ 1
base=36 คือค่าสูงสุด ตัวเลขอาจเป็น 0..9
หรือ A..Z
ตัวอักษรละตินทั้งหมดใช้แทนตัวเลข กรณีที่ตลกแต่มีประโยชน์สำหรับ 36
คือเมื่อเราจำเป็นต้องเปลี่ยนตัวระบุตัวเลขแบบยาวให้เป็นสิ่งที่สั้นลง เช่น เพื่อสร้าง URL แบบสั้น สามารถแสดงมันในระบบตัวเลขด้วยฐาน 36
:
การแจ้งเตือน ( 123456..toString(36) ); // 2n9c
จุดสองจุดเพื่อเรียกวิธีการ
โปรดทราบว่าจุดสองจุดใน 123456..toString(36)
ไม่ใช่ตัวพิมพ์ผิด หากเราต้องการเรียกใช้เมธอดบนตัวเลขโดยตรง เช่น toString
ในตัวอย่างด้านบน เราจะต้องวางจุดสองจุด ..
ไว้ข้างหลัง
หากเราวางจุดเดียว: 123456.toString(36)
ก็จะมีข้อผิดพลาดเกิดขึ้น เนื่องจากไวยากรณ์ JavaScript แสดงถึงส่วนทศนิยมหลังจุดแรก และถ้าเราวางอีกหนึ่งจุด JavaScript จะรู้ว่าส่วนทศนิยมนั้นว่างเปล่า และตอนนี้ก็เข้าสู่เมธอด
(123456).toString(36)
ยังสามารถเขียนได้
หนึ่งในการดำเนินการที่ใช้มากที่สุดเมื่อทำงานกับตัวเลขคือการปัดเศษ
มีฟังก์ชันในตัวมากมายสำหรับการปัดเศษ:
Math.floor
ปัดเศษลง: 3.1
กลายเป็น 3
และ -1.1
กลายเป็น -2
Math.ceil
ปัดเศษขึ้น: 3.1
กลายเป็น 4
และ -1.1
กลายเป็น -1
Math.round
ปัดเศษเป็นจำนวนเต็มที่ใกล้ที่สุด: 3.1
กลายเป็น 3
, 3.6
กลายเป็น 4
ในกรณีตรงกลาง 3.5
ปัดขึ้นเป็น 4
และ -3.5
ปัดขึ้นเป็น -3
Math.trunc
(ไม่รองรับโดย Internet Explorer)
ลบสิ่งใดๆ ที่อยู่หลังจุดทศนิยมโดยไม่ต้องปัดเศษ: 3.1
กลายเป็น 3
, -1.1
กลายเป็น -1
นี่คือตารางเพื่อสรุปความแตกต่างระหว่างพวกเขา:
Math.floor | Math.ceil | Math.round | Math.trunc | |
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.5 | 3 | 4 | 4 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.5 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
ฟังก์ชันเหล่านี้ครอบคลุมวิธีที่เป็นไปได้ทั้งหมดในการจัดการกับส่วนทศนิยมของตัวเลข แต่ถ้าเราอยากจะปัดเศษตัวเลขให้เป็นหลักที่ n-th
หลังทศนิยมล่ะ?
เช่น เรามี 1.2345
และต้องการปัดเศษให้เป็น 2 หลัก จะได้เพียง 1.23
เท่านั้น
มีสองวิธีในการดำเนินการดังกล่าว:
คูณและหาร
ตัวอย่างเช่น หากต้องการปัดเศษตัวเลขให้เป็นหลักที่ 2 หลังทศนิยม เราสามารถคูณตัวเลขด้วย 100
เรียกใช้ฟังก์ชันการปัดเศษแล้วหารกลับ
ให้ num = 1.23456; การแจ้งเตือน( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
วิธีการ toFixed(n) ปัดเศษตัวเลขเป็น n
หลักหลังจุด และส่งกลับการแสดงสตริงของผลลัพธ์
ให้ num = 12.34; การแจ้งเตือน( num.toFixed(1) ); // "12.3"
ซึ่งจะปัดเศษขึ้นหรือลงให้เป็นค่าที่ใกล้ที่สุด คล้ายกับ Math.round
:
ให้ num = 12.36; การแจ้งเตือน( num.toFixed(1) ); // "12.4"
โปรดทราบว่าผลลัพธ์ของ toFixed
คือสตริง หากส่วนทศนิยมสั้นกว่าที่กำหนด เลขศูนย์จะถูกต่อท้าย:
ให้ num = 12.34; การแจ้งเตือน ( num.toFixed(5) ); // "12.34000" เพิ่มศูนย์เพื่อให้ได้ตัวเลข 5 หลักพอดี
เราสามารถแปลงมันเป็นตัวเลขได้โดยใช้ unary plus หรือการโทร Number()
เช่น write +num.toFixed(5)
ภายใน ตัวเลขจะแสดงในรูปแบบ 64 บิต IEEE-754 ดังนั้นจึงมี 64 บิตสำหรับจัดเก็บตัวเลข โดย 52 บิตใช้สำหรับเก็บตัวเลข 11 บิตสำหรับเก็บตำแหน่งของจุดทศนิยม และ 1 บิต สำหรับป้าย
หากตัวเลขมีขนาดใหญ่มาก อาจล้นพื้นที่เก็บข้อมูล 64 บิต และกลายเป็นค่าตัวเลขพิเศษ Infinity
:
การแจ้งเตือน (1e500); //อินฟินิตี้
สิ่งที่อาจชัดเจนน้อยกว่าเล็กน้อยแต่เกิดขึ้นค่อนข้างบ่อยคือการสูญเสียความแม่นยำ
พิจารณาการทดสอบความเท่าเทียมกัน (เท็จ!) นี้:
การแจ้งเตือน ( 0.1 + 0.2 == 0.3 ); // เท็จ
ถูกต้อง ถ้าเราตรวจสอบว่าผลรวมของ 0.1
และ 0.2
เป็น 0.3
หรือไม่ เราจะได้ false
แปลก! แล้วถ้าไม่ใช่ 0.3
จะเป็นอย่างไร?
การแจ้งเตือน (0.1 + 0.2 ); // 0.30000000000000004
อุ๊ย! ลองนึกภาพว่าคุณกำลังสร้างเว็บไซต์ช้อปปิ้งอิเล็กทรอนิกส์ และผู้เข้าชมนำสินค้า $0.10
และ $0.20
ลงในรถเข็นของพวกเขา ยอดสั่งซื้อทั้งหมดจะเท่ากับ $0.30000000000000004
นั่นจะทำให้ทุกคนประหลาดใจ
แต่ทำไมสิ่งนี้ถึงเกิดขึ้น?
ตัวเลขจะถูกเก็บไว้ในหน่วยความจำในรูปแบบไบนารี่ ซึ่งเป็นลำดับของบิต - หนึ่งและศูนย์ แต่เศษส่วนเช่น 0.1
, 0.2
ที่ดูง่ายในระบบเลขฐานสิบ จริงๆ แล้วเป็นเศษส่วนไม่สิ้นสุดในรูปแบบไบนารี่
การแจ้งเตือน (0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 การแจ้งเตือน (0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 การแจ้งเตือน ((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101
0.1
คืออะไร? เป็นหนึ่งหารด้วยสิบ 1/10
หนึ่งในสิบ. ในระบบเลขทศนิยม ตัวเลขดังกล่าวสามารถแทนได้ง่าย เปรียบเทียบกับหนึ่งในสาม: 1/3
. มันกลายเป็นเศษส่วนไม่สิ้นสุด 0.33333(3)
.
ดังนั้นการหารด้วยเลขยกกำลัง 10
จึงรับประกันว่าจะทำงานได้ดีในระบบทศนิยม แต่การหารด้วย 3
ไม่ได้ผล ด้วยเหตุผลเดียวกัน ในระบบเลขฐานสอง การหารด้วยยกกำลัง 2
รับประกันว่าจะได้ผล แต่ 1/10
จะกลายเป็นเศษส่วนไบนารี่ไม่สิ้นสุด
ไม่มีทางที่จะเก็บ 0.1 หรือ 0.2 เป๊ะๆ ได้เป๊ะๆ โดยใช้ระบบไบนารี่ เหมือนๆ กับที่ไม่มีวิธีเก็บ 1 ใน 3 เป็นเศษส่วนทศนิยม
รูปแบบตัวเลข IEEE-754 แก้ปัญหานี้โดยการปัดเศษให้เป็นตัวเลขที่ใกล้ที่สุดที่เป็นไปได้ กฎการปัดเศษเหล่านี้โดยปกติไม่อนุญาตให้เราเห็นว่า "การสูญเสียความแม่นยำเพียงเล็กน้อย" แต่มันก็มีอยู่จริง
เราเห็นสิ่งนี้ได้ในการดำเนินการ:
การแจ้งเตือน ( 0.1.toFixed(20) ); // 0.10000000000000000555
และเมื่อเรารวมตัวเลขสองตัวเข้าด้วยกัน "การสูญเสียที่แม่นยำ" ของพวกมันจะรวมกัน
นั่นเป็นเหตุผลว่าทำไม 0.1 + 0.2
ถึงไม่ตรง 0.3
ไม่ใช่แค่จาวาสคริปต์เท่านั้น
ปัญหาเดียวกันนี้มีอยู่ในภาษาการเขียนโปรแกรมอื่นๆ หลายภาษา
PHP, Java, C, Perl และ Ruby ให้ผลลัพธ์ที่เหมือนกันทุกประการ เนื่องจากใช้รูปแบบตัวเลขที่เหมือนกัน
เราสามารถแก้ไขปัญหาได้หรือไม่? แน่นอนว่าวิธีที่น่าเชื่อถือที่สุดคือการปัดเศษผลลัพธ์โดยใช้วิธี toFixed(n):
ให้ผลรวม = 0.1 + 0.2; การแจ้งเตือน( sum.toFixed(2) ); // "0.30"
โปรดทราบว่า toFixed
จะส่งกลับสตริงเสมอ ทำให้มั่นใจว่ามีตัวเลข 2 หลักหลังจุดทศนิยม จะสะดวกมากหากเรามี e-shopping และต้องแสดง $0.30
สำหรับกรณีอื่นๆ เราสามารถใช้เอกนารีบวกเพื่อบังคับให้เป็นตัวเลขได้:
ให้ผลรวม = 0.1 + 0.2; การแจ้งเตือน ( +sum.toFixed(2) ); // 0.3
นอกจากนี้เรายังสามารถคูณตัวเลขชั่วคราวด้วย 100 (หรือจำนวนที่มากกว่า) เพื่อแปลงให้เป็นจำนวนเต็ม คิดเลข แล้วหารกลับ จากนั้น ขณะที่เรากำลังคำนวณเลขจำนวนเต็ม ข้อผิดพลาดก็จะลดลงบ้าง แต่เรายังคงสามารถหารได้:
การแจ้งเตือน ( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 การแจ้งเตือน ( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
ดังนั้นวิธีการคูณ/หารจะช่วยลดข้อผิดพลาด แต่ไม่ได้ลบออกทั้งหมด
บางครั้งเราอาจพยายามเลี่ยงเศษส่วนเลยก็ได้ เช่นเดียวกับถ้าเราติดต่อกับร้านค้า เราก็สามารถจัดเก็บราคาเป็นเซนต์แทนดอลลาร์ได้ แต่ถ้าเราใช้ส่วนลด 30% ล่ะ? ในทางปฏิบัติ การหลบเลี่ยงเศษส่วนโดยสิ้นเชิงนั้นแทบจะเป็นไปไม่ได้เลย เพียงปัดเศษเพื่อตัด “หาง” เมื่อจำเป็น
เรื่องตลก
ลองเรียกใช้สิ่งนี้:
// สวัสดี! ฉันเป็นคนเพิ่มจำนวนเอง! การแจ้งเตือน ( 9999999999999999 ); // แสดง 10000000000000000
สิ่งนี้ประสบปัญหาเดียวกัน: การสูญเสียความแม่นยำ ตัวเลขมี 64 บิต โดย 52 บิตสามารถใช้เก็บตัวเลขได้ แต่นั่นยังไม่เพียงพอ ดังนั้นเลขนัยสำคัญน้อยที่สุดจึงหายไป
JavaScript ไม่ก่อให้เกิดข้อผิดพลาดในเหตุการณ์ดังกล่าว ทางที่ดีที่สุดคือปรับตัวเลขให้พอดีกับรูปแบบที่ต้องการ แต่น่าเสียดายที่รูปแบบนี้ไม่ใหญ่พอ
ศูนย์สองตัว
ผลที่ตามมาที่น่าตลกอีกประการหนึ่งของการแสดงตัวเลขภายในคือการมีศูนย์สองตัว: 0
และ -0
นั่นเป็นเพราะว่าเครื่องหมายถูกแสดงด้วยบิตเดียว จึงสามารถตั้งค่าหรือไม่ตั้งค่าสำหรับตัวเลขใดๆ รวมทั้งศูนย์ด้วย
ในกรณีส่วนใหญ่ ความแตกต่างนี้ไม่สามารถสังเกตได้ เนื่องจากผู้ปฏิบัติงานเหมาะสมที่จะปฏิบัติต่อสิ่งเหล่านั้นเหมือนกัน
จำค่าตัวเลขพิเศษสองค่านี้ได้ไหม
Infinity
(และ -Infinity
) คือค่าตัวเลขพิเศษที่มากกว่า (น้อยกว่า) กว่าสิ่งใดๆ
NaN
แสดงถึงข้อผิดพลาด
เป็นของ number
ประเภท แต่ไม่ใช่ตัวเลข "ปกติ" ดังนั้นจึงมีฟังก์ชันพิเศษให้ตรวจสอบ:
isNaN(value)
แปลงอาร์กิวเมนต์เป็นตัวเลขแล้วทดสอบว่าเป็น NaN
:
การแจ้งเตือน( isNaN(NaN) ); // จริง alert( isNaN("str") ); // จริง
แต่เราจำเป็นต้องมีฟังก์ชั่นนี้หรือไม่? เราไม่สามารถใช้การเปรียบเทียบได้ === NaN
ใช่ไหม น่าเสียดายที่ไม่ใช่ ค่า NaN
มีลักษณะเฉพาะตรงที่ไม่เท่ากับสิ่งใดๆ รวมถึงตัวมันเองด้วย:
การแจ้งเตือน (น่าน === น่าน); // เท็จ
isFinite(value)
แปลงอาร์กิวเมนต์เป็นตัวเลขและส่งคืนค่า true
หากเป็นตัวเลขปกติ ไม่ใช่ NaN/Infinity/-Infinity
:
การแจ้งเตือน( isFinite("15") ); // จริง การแจ้งเตือน( isFinite("str") ); // เท็จเนื่องจากค่าพิเศษ: NaN การแจ้งเตือน ( isFinite (อินฟินิตี้) ); // เท็จ เนื่องจากมีค่าพิเศษ: อนันต์
บางครั้ง isFinite
ใช้เพื่อตรวจสอบว่าค่าสตริงเป็นตัวเลขปกติหรือไม่:
ให้ num = +prompt("ใส่ตัวเลข", ''); // จะเป็นจริงเว้นแต่คุณจะป้อนค่า Infinity, -Infinity หรือไม่ใช่ตัวเลข การแจ้งเตือน ( isFinite (หมายเลข) );
โปรดทราบว่าสตริงว่างหรือเว้นวรรคเท่านั้นจะถือเป็น 0
ในฟังก์ชันตัวเลขทั้งหมด รวมถึง isFinite
Number.isNaN
และ Number.isFinite
เมธอด Number.isNaN และ Number.isFinite เป็นฟังก์ชัน isNaN
และ isFinite
ในเวอร์ชันที่ "เข้มงวด" มากกว่า พวกเขาไม่แปลงอาร์กิวเมนต์เป็นตัวเลขโดยอัตโนมัติ แต่ตรวจสอบว่าเป็นประเภท number
แทนหรือไม่
Number.isNaN(value)
จะคืนค่า true
หากอาร์กิวเมนต์เป็นของประเภท number
และเป็น NaN
ในกรณีอื่นจะส่งคืนค่า false
การแจ้งเตือน ( Number.isNaN(NaN) ); // จริง การแจ้งเตือน( Number.isNaN("str" / 2) ); // จริง // สังเกตความแตกต่าง: alert( Number.isNaN("str") ); // false เนื่องจาก "str" เป็นของประเภทสตริง ไม่ใช่ประเภทตัวเลข alert( isNaN("str") ); // จริง เนื่องจาก isNaN แปลงสตริง "str" เป็นตัวเลขและรับ NaN อันเป็นผลมาจากการแปลงนี้
Number.isFinite(value)
คืนค่า true
หากอาร์กิวเมนต์เป็นของประเภท number
และไม่ใช่ NaN/Infinity/-Infinity
ในกรณีอื่นจะส่งคืนค่า false
การแจ้งเตือน( Number.isFinite(123) ); // จริง การแจ้งเตือน ( Number.isFinite (อนันต์) ); // เท็จ การแจ้งเตือน( Number.isFinite(2 / 0) ); // เท็จ // สังเกตความแตกต่าง: การแจ้งเตือน( Number.isFinite("123") ); // false เนื่องจาก "123" เป็นประเภทสตริง ไม่ใช่ประเภทตัวเลข การแจ้งเตือน( isFinite("123") ); // จริง เนื่องจาก isFinite แปลงสตริง "123" เป็นตัวเลข 123
ในทางหนึ่ง Number.isNaN
และ Number.isFinite
นั้นง่ายกว่าและตรงไปตรงมามากกว่าฟังก์ชัน isNaN
และ isFinite
ในทางปฏิบัติ isNaN
และ isFinite
ส่วนใหญ่จะถูกใช้ เนื่องจากเขียนได้สั้นกว่า
เปรียบเทียบกับ Object.is
มีเมธอดพิเศษในตัว Object.is
ที่เปรียบเทียบค่าเช่น ===
แต่มีความน่าเชื่อถือมากกว่าสำหรับสองกรณีขอบ:
มันใช้งานได้กับ NaN
: Object.is(NaN, NaN) === true
นั่นเป็นสิ่งที่ดี
ค่า 0
และ -0
แตกต่างกัน: Object.is(0, -0) === false
ในทางเทคนิคนั้นถูกต้องเพราะภายในตัวเลขมีบิตเครื่องหมายที่อาจแตกต่างกันแม้ว่าบิตอื่น ๆ ทั้งหมดจะเป็นศูนย์ก็ตาม
ในกรณีอื่น ๆ ทั้งหมด Object.is(a, b)
จะเหมือนกับ a === b
เราพูดถึง Object.is
ที่นี่ เนื่องจากมักใช้ในข้อกำหนด JavaScript เมื่ออัลกอริทึมภายในจำเป็นต้องเปรียบเทียบสองค่าเพื่อให้เหมือนกันทุกประการ อัลกอริธึมจะใช้ Object.is
(ภายในเรียกว่า SameValue)
การแปลงตัวเลขโดยใช้เครื่องหมายบวก +
หรือ Number()
นั้นเข้มงวด หากค่าไม่ตรงกับตัวเลข จะล้มเหลว:
การแจ้งเตือน( +"100px" ); // แนน
ข้อยกเว้นเพียงอย่างเดียวคือการเว้นวรรคที่จุดเริ่มต้นหรือจุดสิ้นสุดของสตริง เนื่องจากจะถูกละเว้น
แต่ในชีวิตจริง เรามักจะมีค่าเป็นหน่วย เช่น "100px"
หรือ "12pt"
ใน CSS นอกจากนี้ในหลายประเทศ สัญลักษณ์สกุลเงินจะอยู่หลังจำนวนเงิน ดังนั้นเราจึงมี "19€"
และต้องการแยกค่าตัวเลขออกมา
นั่นคือสิ่งที่ parseInt
และ parseFloat
มีไว้เพื่อ
พวกเขา "อ่าน" ตัวเลขจากสตริงจนกระทั่งอ่านไม่ได้ ในกรณีที่มีข้อผิดพลาด ระบบจะส่งคืนหมายเลขที่รวบรวมไว้ ฟังก์ชัน parseInt
ส่งคืนจำนวนเต็ม ในขณะที่ parseFloat
จะส่งกลับตัวเลขทศนิยม:
การแจ้งเตือน( parseInt('100px') ); // 100 การแจ้งเตือน( parseFloat('12.5em') ); // 12.5 การแจ้งเตือน( parseInt('12.3') ); // 12 จะส่งคืนเฉพาะส่วนจำนวนเต็มเท่านั้น การแจ้งเตือน( parseFloat('12.3.4') ); // 12.3 จุดที่สองหยุดการอ่าน
มีบางสถานการณ์ที่ parseInt/parseFloat
จะส่งกลับ NaN
มันเกิดขึ้นเมื่อไม่สามารถอ่านตัวเลขได้:
การแจ้งเตือน( parseInt('a123') ); // NaN สัญลักษณ์แรกหยุดกระบวนการ
อาร์กิวเมนต์ที่สองของ parseInt(str, radix)
ฟังก์ชัน parseInt()
มีพารามิเตอร์ตัวที่สองที่เป็นทางเลือก โดยจะระบุฐานของระบบตัวเลข ดังนั้น parseInt
จึงสามารถแยกวิเคราะห์สตริงของเลขฐานสิบหก เลขฐานสอง และอื่นๆ ได้:
การแจ้งเตือน( parseInt('0xff', 16) ); // 255 การแจ้งเตือน( parseInt('ff', 16) ); // 255 โดยไม่มี 0x ก็ใช้งานได้เช่นกัน การแจ้งเตือน( parseInt('2n9c', 36) ); // 123456
JavaScript มีวัตถุทางคณิตศาสตร์ในตัวซึ่งมีไลบรารีขนาดเล็กของฟังก์ชันทางคณิตศาสตร์และค่าคงที่
ตัวอย่างบางส่วน:
Math.random()
ส่งกลับตัวเลขสุ่มตั้งแต่ 0 ถึง 1 (ไม่รวม 1)
การแจ้งเตือน( Math.random() ); // 0.1234567894322 การแจ้งเตือน( Math.random() ); // 0.5435252343232 การแจ้งเตือน( Math.random() ); // ... (ตัวเลขสุ่มใดๆ)
Math.max(a, b, c...)
และ Math.min(a, b, c...)
ส่งกลับค่ามากที่สุดและน้อยที่สุดจากจำนวนอาร์กิวเมนต์ที่ต้องการ
การแจ้งเตือน( Math.max(3, 5, -10, 0, 1) ); // 5 การแจ้งเตือน( Math.min(1, 2) ); // 1
Math.pow(n, power)
ส่งคืน n
ยกกำลังตามที่กำหนด
การแจ้งเตือน( Math.pow(2, 10) ); // 2 อยู่ในกำลัง 10 = 1,024
มีฟังก์ชันและค่าคงที่เพิ่มเติมในวัตถุ Math
รวมถึงตรีโกณมิติด้วย ซึ่งคุณสามารถพบได้ในเอกสารสำหรับวัตถุทางคณิตศาสตร์
วิธีเขียนตัวเลขที่มีศูนย์หลายตัว:
ผนวก "e"
โดยให้เลขศูนย์นับเป็นตัวเลข ชอบ: 123e6
เหมือนกับ 123
โดยมีศูนย์ 6 ตัว 123000000
จำนวนลบหลัง "e"
ทำให้ตัวเลขถูกหารด้วย 1 โดยมีเลขศูนย์ที่กำหนด เช่น 123e-6
หมายถึง 0.000123
( 123
ล้าน)
สำหรับระบบตัวเลขแบบต่างๆ:
สามารถเขียนตัวเลขได้โดยตรงในระบบฐานสิบหก ( 0x
) ฐานแปด ( 0o
) และไบนารี่ ( 0b
)
parseInt(str, base)
แยกวิเคราะห์สตริง str
เป็นจำนวนเต็มในระบบตัวเลขด้วย base
ที่กำหนด 2 ≤ base ≤ 36
num.toString(base)
แปลงตัวเลขเป็นสตริงในระบบตัวเลขด้วย base
ที่กำหนด
สำหรับการทดสอบจำนวนปกติ:
isNaN(value)
แปลงอาร์กิวเมนต์เป็นตัวเลขแล้วทดสอบว่าเป็น NaN
Number.isNaN(value)
ตรวจสอบว่าอาร์กิวเมนต์เป็นของประเภท number
หรือไม่ และหากเป็นเช่นนั้น ให้ทดสอบว่าเป็น NaN
isFinite(value)
แปลงอาร์กิวเมนต์เป็นตัวเลขแล้วทดสอบว่าไม่เป็น NaN/Infinity/-Infinity
Number.isFinite(value)
ตรวจสอบว่าอาร์กิวเมนต์เป็นของประเภท number
หรือไม่ และหากเป็นเช่นนั้น ให้ทดสอบว่าไม่เป็น NaN/Infinity/-Infinity
สำหรับการแปลงค่าเช่น 12pt
และ 100px
เป็นตัวเลข:
ใช้ parseInt/parseFloat
สำหรับการแปลงแบบ "soft" ซึ่งจะอ่านตัวเลขจากสตริง แล้วส่งคืนค่าที่สามารถอ่านได้ก่อนเกิดข้อผิดพลาด
สำหรับเศษส่วน:
ปัดเศษโดยใช้ Math.floor
, Math.ceil
, Math.trunc
, Math.round
หรือ num.toFixed(precision)
อย่าลืมจำไว้ว่าการสูญเสียความแม่นยำเมื่อทำงานกับเศษส่วน
ฟังก์ชันทางคณิตศาสตร์เพิ่มเติม:
ดูวัตถุคณิตศาสตร์เมื่อคุณต้องการ ห้องสมุดมีขนาดเล็กมากแต่สามารถรองรับความต้องการขั้นพื้นฐานได้
ความสำคัญ: 5
สร้างสคริปต์ที่แจ้งให้ผู้เยี่ยมชมป้อนตัวเลขสองตัวแล้วแสดงผลรวม
เรียกใช้การสาธิต
ป.ล. มี gotcha แบบด้วย
ให้ a = +prompt("หมายเลขแรก?", ""); ให้ b = +prompt("หมายเลขที่สอง?", ""); การแจ้งเตือน (a + b);
หมายเหตุ unary plus +
before prompt
มันจะแปลงค่าเป็นตัวเลขทันที
มิฉะนั้น a
และ b
จะเป็นสตริง ผลรวมของพวกเขาจะเป็นการต่อกัน นั่นคือ: "1" + "2" = "12"
ความสำคัญ: 4
ตามเอกสาร Math.round
และ toFixed
ทั้งสองปัดเป็นจำนวนที่ใกล้ที่สุด: 0..4
นำลงในขณะที่ 5..9
นำขึ้น
ตัวอย่างเช่น:
การแจ้งเตือน ( 1.35.toFixed(1) ); // 1.4
ในตัวอย่างที่คล้ายกันด้านล่าง เหตุใด 6.35
จึงถูกปัดเศษเป็น 6.3
ไม่ใช่ 6.4
การแจ้งเตือน ( 6.35.toFixed(1) ); // 6.3
ปัดเศษ 6.35
อย่างไรให้ถูกต้อง?
ภายในเศษส่วนทศนิยม 6.35
เป็นเลขฐานสองไม่สิ้นสุด เช่นเคยในกรณีเช่นนี้ ข้อมูลดังกล่าวจะถูกจัดเก็บโดยมีการสูญเสียความแม่นยำ
มาดูกัน:
การแจ้งเตือน ( 6.35.toFixed(20) ); // 6.3499999999999964473
การสูญเสียความแม่นยำอาจทำให้จำนวนเพิ่มขึ้นและลดได้ ในกรณีนี้ จำนวนจะน้อยลงเล็กน้อย จึงเป็นเหตุให้ปัดเศษลง
แล้ว 1.35
ล่ะได้เท่าไร?
การแจ้งเตือน ( 1.35.toFixed(20) ); // 1.35000000000000008882
การสูญเสียความแม่นยำทำให้ตัวเลขเพิ่มขึ้นเล็กน้อย ดังนั้นจึงปัดเศษขึ้น
เราจะแก้ไขปัญหาด้วย 6.35
ได้อย่างไรหากเราต้องการปัดเศษให้ถูกต้อง?
เราควรทำให้มันเข้าใกล้จำนวนเต็มก่อนที่จะปัดเศษ:
การแจ้งเตือน( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000
โปรดทราบว่า 63.5
ไม่มีการสูญเสียความแม่นยำเลย นั่นเป็นเพราะว่าส่วนทศนิยม 0.5
จริงๆ แล้วคือ 1/2
เศษส่วนที่หารด้วย 2
ยกกำลังจะแสดงแทนในระบบไบนารี่ทุกประการ ตอนนี้เราสามารถปัดเศษได้:
การแจ้งเตือน( คณิตรอบ(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(ปัดเศษ) -> 6.4
ความสำคัญ: 5
สร้างฟังก์ชัน readNumber
ซึ่งจะแจ้งตัวเลขจนกว่าผู้เยี่ยมชมจะป้อนค่าตัวเลขที่ถูกต้อง
ค่าผลลัพธ์จะต้องส่งคืนเป็นตัวเลข
ผู้เยี่ยมชมสามารถหยุดกระบวนการได้ด้วยการป้อนบรรทัดว่างหรือกด "ยกเลิก" ในกรณีนั้น ฟังก์ชันควรคืนค่า null
เรียกใช้การสาธิต
เปิดแซนด์บ็อกซ์พร้อมการทดสอบ
ฟังก์ชั่น readNumber() { ให้หมายเลข; ทำ { num = prompt("กรุณาใส่ตัวเลข?", 0); } ในขณะที่ ( !isFinite(num) ); ถ้า (num === null || num === '') ส่งคืน null; กลับ+หมายเลข; - alert(`อ่าน: ${readNumber()}`);
วิธีแก้ไขนั้นซับซ้อนกว่าเล็กน้อยซึ่งอาจเป็นเพราะเราต้องจัดการกับบรรทัด null
/ว่าง
ดังนั้นเราจึงยอมรับอินพุตจริงๆ จนกระทั่งมันเป็น "ตัวเลขปกติ" ทั้ง null
(ยกเลิก) และบรรทัดว่างก็สอดคล้องกับเงื่อนไขนั้นเช่นกัน เนื่องจากในรูปแบบตัวเลขจะเป็น 0
หลังจากที่เราหยุดแล้ว เราจำเป็นต้องปฏิบัติต่อ null
และบรรทัดว่างเป็นพิเศษ (ส่งคืน null
) เนื่องจากการแปลงเป็นตัวเลขจะส่งกลับ 0
เปิดโซลูชันพร้อมการทดสอบในแซนด์บ็อกซ์
ความสำคัญ: 4
วงนี้ไม่มีที่สิ้นสุด มันไม่มีวันสิ้นสุด ทำไม
ให้ฉัน = 0; ในขณะที่ (ฉัน != 10) { ฉัน += 0.2; -
นั่นเป็นเพราะว่า i
ไม่เคยจะเท่ากับ 10
รันเพื่อดูค่า ที่แท้จริง ของ i
:
ให้ฉัน = 0; ในขณะที่ (ฉัน < 11) { ฉัน += 0.2; ถ้า (i > 9.8 && ฉัน < 10.2) แจ้งเตือน ( i ); -
ไม่มีสักอันที่ 10
เลย
สิ่งเหล่านี้เกิดขึ้นเนื่องจากการสูญเสียความแม่นยำเมื่อบวกเศษส่วนเช่น 0.2
สรุป: หลีกเลี่ยงการตรวจสอบความเท่าเทียมกันเมื่อทำงานกับเศษส่วนทศนิยม
ความสำคัญ: 2
ฟังก์ชันในตัว Math.random()
สร้างค่าสุ่มตั้งแต่ 0
ถึง 1
(ไม่รวม 1
)
เขียนฟังก์ชัน random(min, max)
เพื่อสร้างตัวเลขทศนิยมแบบสุ่มจาก min
ถึง max
(ไม่รวม max
)
ตัวอย่างผลงาน:
แจ้งเตือน( สุ่ม(1, 5) ); // 1.2345623452 แจ้งเตือน( สุ่ม(1, 5) ); // 3.7894332423 แจ้งเตือน( สุ่ม(1, 5) ); // 4.3435234525
เราจำเป็นต้อง "แมป" ค่าทั้งหมดจากช่วง 0...1 เป็นค่าตั้งแต่ min
ถึง max
ซึ่งสามารถทำได้ในสองขั้นตอน:
หากเราคูณตัวเลขสุ่มจาก 0…1 ด้วย max-min
ดังนั้นช่วงเวลาของค่าที่เป็นไปได้จะเพิ่มขึ้น 0..1
ถึง 0..max-min
ตอนนี้ถ้าเราเพิ่ม min
ช่วงเวลาที่เป็นไปได้จะกลายเป็นจาก min
ถึง max
ฟังก์ชั่น:
ฟังก์ชั่นสุ่ม (ต่ำสุด, สูงสุด) { กลับขั้นต่ำ + Math.random() * (สูงสุด - นาที); - แจ้งเตือน( สุ่ม(1, 5) ); แจ้งเตือน( สุ่ม(1, 5) ); แจ้งเตือน( สุ่ม(1, 5) );
ความสำคัญ: 2
สร้างฟังก์ชัน randomInteger(min, max)
ที่สร้างตัวเลข จำนวนเต็ม สุ่มจาก min
ถึง max
โดยรวมทั้งค่า min
และ max
เท่าที่เป็นไปได้
ตัวเลขใดๆ จากช่วง min..max
จะต้องปรากฏด้วยความน่าจะเป็นเท่ากัน
ตัวอย่างผลงาน:
การแจ้งเตือน( จำนวนเต็มสุ่ม(1, 5) ); // 1 การแจ้งเตือน( จำนวนเต็มสุ่ม(1, 5) ); // 3 การแจ้งเตือน( จำนวนเต็มสุ่ม(1, 5) ); // 5
คุณสามารถใช้วิธีแก้ปัญหาของงานก่อนหน้าเป็นฐานได้
วิธีแก้ปัญหาที่ง่ายที่สุด แต่ผิดคือสร้างค่าจาก min
ถึง max
แล้วปัดเศษ:
ฟังก์ชั่น RandomInteger (ต่ำสุด, สูงสุด) { ให้ rand = min + Math.random() * (max - min); กลับ Math.round (rand); - การแจ้งเตือน( จำนวนเต็มสุ่ม(1, 3) );
ฟังก์ชั่นใช้งานได้ แต่ไม่ถูกต้อง ความน่าจะเป็นที่จะได้ค่าขอบ min
และ max
นั้นน้อยกว่าค่าอื่นสองเท่า
หากคุณเรียกใช้ตัวอย่างข้างต้นหลายครั้ง คุณจะเห็นว่า 2
ปรากฏบ่อยที่สุด
สิ่งนั้นเกิดขึ้นเพราะ Math.round()
ได้รับตัวเลขสุ่มจากช่วง 1..3
และปัดเศษเป็นดังนี้:
ค่าตั้งแต่ 1 ... ถึง 1.4999999999 กลายเป็น 1 ค่าจาก 1.5 ... ถึง 2.4999999999 กลายเป็น 2 ค่าจาก 2.5 ... ถึง 2.9999999999 กลายเป็น 3
ตอนนี้เราเห็นได้อย่างชัดเจนว่า 1
ได้รับค่าน้อยกว่า 2
ถึงสองเท่า และเช่นเดียวกันกับ 3
.
มีวิธีแก้ไขปัญหาที่ถูกต้องมากมายสำหรับงานนี้ หนึ่งในนั้นคือการปรับเส้นขอบช่วงเวลา เพื่อให้แน่ใจว่าช่วงเวลาเดียวกัน เราสามารถสร้างค่าได้ตั้งแต่ 0.5 to 3.5
ซึ่งจะช่วยเพิ่มความน่าจะเป็นที่จำเป็นให้กับขอบ:
ฟังก์ชั่น RandomInteger (ต่ำสุด, สูงสุด) { // ตอนนี้ rand คือจาก (นาที-0.5) ถึง (สูงสุด+0.5) ให้ rand = min - 0.5 + Math.random() * (max - min + 1); กลับ Math.round (rand); - การแจ้งเตือน( จำนวนเต็มสุ่ม(1, 3) );
อีกวิธีหนึ่งคือใช้ Math.floor
สำหรับตัวเลขสุ่มตั้งแต่ min
ถึง max+1
:
ฟังก์ชั่น RandomInteger (ต่ำสุด, สูงสุด) { // ที่นี่ rand คือจากนาทีถึง (สูงสุด +1) ให้ rand = min + Math.random() * (max + 1 - min); กลับ Math.floor (rand); - การแจ้งเตือน( จำนวนเต็มสุ่ม(1, 3) );
ตอนนี้ช่วงเวลาทั้งหมดจะถูกแมปด้วยวิธีนี้:
ค่าตั้งแต่ 1 ... ถึง 1.9999999999 จะกลายเป็น 1 ค่าจาก 2 ... ถึง 2.9999999999 กลายเป็น 2 ค่าจาก 3 ... ถึง 3.9999999999 กลายเป็น 3
ทุกช่วงมีความยาวเท่ากัน ทำให้สุดท้ายมีการกระจายสม่ำเสมอ