เหตุผล: JS ประมวลผลการเพิ่มการลบการคูณและการแบ่งทศนิยมตาม 2. บนพื้นฐานของ ARG1 ความแม่นยำของ Arg2 จะถูกขยายหรือขยายตัวแบบผกผันดังนั้นสถานการณ์ต่อไปนี้จะเกิดขึ้น
ปัญหาของการเพิ่มการลบการคูณและการแบ่งจุดทศนิยมใน JavaScript (JS) เป็นข้อผิดพลาดใน JS เช่น 0.3*1 = 0.299999999999 ฯลฯ รายการอัลกอริทึม JS สี่รายการต่อไปนี้
ฟังก์ชั่น accdiv (arg1, arg2) {var t1 = 0, t2 = 0, r1, r2; ลอง {t1 = arg1.toString (). แยก (".") [1] .length} catch (e) {} ลอง {t2 = arg2.toString (). แยก (".") [1] .length} catch (e) {} ด้วย (คณิตศาสตร์) {r1 = number (arg1.tostring r2 = number (arg2.toString (). แทนที่ (".", ")) return accmul ((r1/r2), pow (10, t2-t1)); }} // ฟังก์ชั่นหลายฟังก์ชั่น accmul (arg1, arg2) {var m = 0, s1 = arg1.toString (), s2 = arg2.toString (); ลอง {m+= s1.split (".") [1] .length} catch (e) {} ลอง {m+= s2.split (".") [1] .length} catch (e) {} หมายเลขกลับ (s1.replace (",", ") Accadd (Arg1, Arg2) {var r1, r2, m; ลอง {r1 = arg1.toString (). split (".") [1] .length} catch (e) {r1 = 0} ลอง {r2 = arg2.toString (). แยก (".") [1] .length (arg1*m+arg2*m)/m} // ฟังก์ชั่นการลบ subtr (arg1, arg2) {var r1, r2, m, n; ลอง {r1 = arg1.toString (). split (".") [1] .length} catch (e) {r1 = 0} ลอง {r2 = arg2.toString (). แยก (".") [1] .length n = (r1> = r2)? r1: r2; return ((arg1*m-arg2*m)/m) .tofixed (n); -มาวิเคราะห์การสูญเสียความแม่นยำดิจิตอลใน JavaScript
1. ปัญหาทั่วไปของการสูญเสียความแม่นยำของ JS Digital
1. เพิ่มหมายเลขจุดลอยตัวง่ายสองหมายเลข
0.1 + 0.2! = 0.3 // จริง
ลูกไฟ
นี่ไม่ใช่ปัญหา Firebug จริง ๆ คุณสามารถลองด้วยการแจ้งเตือน (ฮ่าฮ่าล้อเล่น)
ตรวจสอบผลการคำนวณของ Java
ลองดูที่ Python
2. การทำงานจำนวนเต็มขนาดใหญ่
99999999999999999 == 100000000000000001 //?
ลูกไฟ
ตัวเลข 16 หลักและ 17 หลักนั้นเท่ากันจริง ๆ แล้วมันไม่มีเหตุผล
ยังชอบ
var x = 9007199254740992x + 1 == x //?
ดูผลลัพธ์
มุมมองทั้งสามถูกทำลายอีกครั้ง
3. Tofixed จะไม่กลม (Chrome)
1.335.Tofixed (2) // 1.33
ลูกไฟ
มีกรณีของราคาที่ไม่สอดคล้องกันใน Chrome และเบราว์เซอร์อื่น ๆ ออนไลน์และเป็นเพราะปัญหาความเข้ากันได้ของ Tofixed
2. เหตุผลสำหรับความถูกต้องของการสูญเสียหมายเลข JS
ตัวเลขบางตัวไม่สามารถแสดงได้อย่างชัดเจนโดยการใช้งานไบนารีคอมพิวเตอร์และขีด จำกัด บิต เช่นเดียวกับตัวเลขที่ไม่มีเหตุผลบางอย่างไม่สามารถแสดงได้อย่างชัดเจนเช่น PI 3.1415926 ... , 1.3333 ... ฯลฯ JS ติดตามข้อมูลจำเพาะ IEEE 754 ดังที่แสดง
ความสำคัญ
หมายเลขจุดลอยตัวเช่น
0.1 >> 0.0001 1001 1001 1001 … (1001 LOOP Infinite) 0.2 >> 0.0011 0011 0011 0011 … (0011 Infinite Loop)
ในเวลานี้คุณสามารถปัดเศษได้โดยการเลียนแบบระบบทศนิยม แต่มีระบบไบนารีเพียงสองระบบเท่านั้น: 0 และ 1 ดังนั้นจึงกลายเป็น 0 และปัดเศษด้วย 1 นี่คือเหตุผลพื้นฐานว่าทำไมตัวเลขลอยตัวในคอมพิวเตอร์จึงมีข้อผิดพลาดและความแม่นยำที่หายไป
การสูญเสียความแม่นยำของจำนวนเต็มขนาดใหญ่นั้นเหมือนกับหมายเลขจุดลอยตัว บิต Mantissa สูงสุดคือ 52 บิตดังนั้นจำนวนเต็มสูงสุดที่สามารถแสดงได้อย่างถูกต้องใน JS คือ Math.pow (2, 53) ซึ่งคือ 9007199254740992
มากกว่า 9007199254740992 อาจสูญเสียความแม่นยำ
9007199254740992 >> 1000000000000000 ... 000 // ทั้งหมด 53 09007199254740992 + 1 >> 10000000000000000 ... 001 // ในตอนกลาง 52 0900719254740992 + 2
ในความเป็นจริง
9007199254740992 + 1 // หายไป 9007199254740992 + 2 // ไม่หายไป 9007199254740992 + 3 // หายไป 900719254740992 + 4 // ไม่หายไป
ผลลัพธ์จะแสดงในรูป
ดังที่ได้กล่าวไว้ข้างต้นเราสามารถรู้ได้ว่าตัวเลขที่ จำกัด ดูเหมือนไม่มีที่สิ้นสุดในการเป็นตัวแทนไบนารีของคอมพิวเตอร์ เนื่องจากขีด จำกัด บิตเก็บข้อมูลมี "หนีไป" และการสูญเสียความแม่นยำเกิดขึ้น
สำหรับการวิเคราะห์เชิงลึกเพิ่มเติมคุณสามารถอ่านบทความนี้ (ยาวและมีกลิ่นเหม็น): สิ่งที่นักวิทยาศาสตร์คอมพิวเตอร์ทุกคนควรรู้เกี่ยวกับเลขคณิตลอย
3. โซลูชัน
สำหรับจำนวนเต็มความน่าจะเป็นของปัญหาในส่วนหน้าอาจค่อนข้างต่ำ ท้ายที่สุดธุรกิจเพียงไม่กี่แห่งจำเป็นต้องใช้จำนวนเต็มที่มีขนาดใหญ่มาก ตราบใดที่ผลการคำนวณไม่เกิน MATH.POW (2, 53) ความแม่นยำจะไม่หายไป
สำหรับทศนิยมยังมีโอกาสมากมายที่จะเกิดปัญหากับส่วนหน้าโดยเฉพาะอย่างยิ่งในเว็บไซต์อีคอมเมิร์ซบางแห่งที่เกี่ยวข้องกับข้อมูลเช่นจำนวนเงิน วิธีแก้ปัญหา: ใส่ทศนิยมลงในจำนวนเต็ม (หลายตัวหลายตัว) จากนั้นหดกลับไปที่หลายตัวเดิม (หารหลายตัว)
// 0.1 + 0.2 (0.1*10 + 0.2*10) / 10 == 0.3 // true
ต่อไปนี้เป็นวัตถุที่ฉันเขียนเพื่อบล็อกการสูญเสียความแม่นยำของการเพิ่มการลบการคูณและการแบ่งทศนิยม แน่นอนว่าจำนวนเต็มที่ถูกแปลงไม่เกิน 9007199254740992
/*** floatobj มีสี่วิธี: การเพิ่มการลบการคูณและการแบ่งซึ่งสามารถมั่นใจได้ว่าการคำนวณจำนวนจุดลอยตัวจะไม่สูญเสียความแม่นยำ ** เรารู้ว่าการคำนวณจำนวนจุดลอยตัวจะมีการสูญเสียความแม่นยำ (หรือข้อผิดพลาดในการปัดเศษ) สาเหตุที่แท้จริงคือตัวเลขบางอย่างไม่สามารถแสดงออกได้ในไบนารีและการใช้บิต จำกัด* ต่อไปนี้เป็นตัวแทนไบนารีที่สอดคล้องกับทศนิยมทศนิยม* 0.1 >> 0.0001 1001 1001 1001 1001 … (1001 ลูปอนันต์)* 0.2 >> 0.0011 0011 0011 0011 ตัวอย่างเช่น JavaScript ใช้ประเภทตัวเลขที่เก็บ 64 บิตดังนั้นประเภทที่เกินกว่าจะถูกทิ้ง ส่วนที่หายไปคือส่วนที่หายไปอย่างแม่นยำ ** ** วิธี *** เพิ่ม / ลบ / คูณ / หาร ** ** คำอธิบาย *** 0.1 + 0.2 == 0.3000000000000004 (0.0000000000000004 เพิ่มเติม)* 0.2 + 0.4 == 0.6000000000000001 (0.0000000000001)* 19.9999999999999999999999999999999999999999 (0.000000000000002 LINGE) ** floatobj.add (0.1, 0.2) >> 0.3*floatoBj.multiply (19.9, 100) >> 1990 **/var floatobj = fload () {/** พิจารณาว่า obj เป็นจำนวนเต็ม*/ฟังก์ชัน Isinteger หมายเลขจุดลงในจำนวนเต็มและส่งคืนจำนวนเต็มและหลาย ตัวอย่างเช่น 3.14 >> 314 หลายตัวคือ 100* @param floatnum {number} ทศนิยม* @return {Object}* {times: 100, num: 314}*/function tointeger (floatnum) {var ret = {times: 1, num: 0} ถ้า floatnum + '' var dotpos = strfi.indexof ('.') var len = strfi.substr (dotpos + 1) .lengthvar times = math.pow (10, len) var intnum = parseint (floatnum* times + 0.5, 10) และการดำเนินการหารเพื่อให้แน่ใจว่าความแม่นยำนั้นไม่ได้สูญหายไป* ความคิด: ขยายทศนิยมเป็นจำนวนเต็ม (ตัวคูณ), ดำเนินการทางคณิตศาสตร์จากนั้นลดลงไปที่ทศนิยม (แบ่งออก) ** @param a {number} หมายเลขการดำเนินการ 1* @param b {number} หมายเลขการดำเนินการ สถานที่ทศนิยม*@param op {String} ประเภทการดำเนินการ, ด้วยการเพิ่ม, การลบ, การคูณและการแบ่ง (เพิ่ม/ลบ/คูณ/หาร) **/ฟังก์ชันการทำงาน (a, b, ตัวเลข, op) {var o1 = tointeger (a) var o2 = tointeger (b) var n1 = o1.numvar n2 = o2.num o2.timesvar max = t1> t2? t1: t2var result = nullswitch (op) {case 'add': ถ้า (t1 === t2) {// สองตำแหน่งทศนิยมเป็นผลลัพธ์เดียวกัน = n1 + n2} อื่นถ้า (t1> t2) {// o1 สถานที่ทศนิยมที่มากกว่า o2result = n1 + n2 * (t1 / t2) (t2 / t1) + n2} ส่งคืนผลลัพธ์ / maxcase 'ลบ': ถ้า (t1 === t2) {result = n1 - n2} อื่นถ้า (t1> t2) {result = n1 - n2 * (t1 / t2)} {result = n1 * (t2 / t1) - n2} * T2) Return ResultCase 'Divide': result = (N1 / N2) * (T2 / T1) ผลตอบแทน}} // สี่อินเทอร์เฟซของการเพิ่ม, การลบ, การคูณและฟังก์ชั่นการหาร (A, B, ตัวเลข) {การดำเนินการกลับ (A, B, Digits, 'เพิ่ม') ตัวเลข) {การดำเนินการส่งคืน (a, b, ตัวเลข, 'ทวีคูณ')} ฟังก์ชั่นหาร (a, b, ตัวเลข) {การดำเนินการส่งคืน (A, B, ตัวเลข, 'หาร')} // ExportSreturn {เพิ่ม: เพิ่ม, ลบ: ลบ, คูณ: คูณ, หาร: หาร}} ();การแก้ไขของ Tofixed มีดังนี้
// tofixed fix function tofixed (num, s) {var times = math.pow (10, s) var des = num * times + 0.5des = parseint (des, 10) / timesreturn des + ''}