- JavaScript重難點實例精講
- 周雄
- 1871字
- 2020-10-30 15:51:53
1.2.4 浮點型運算
在JavaScript中,整數和浮點數都屬于Number類型,它們都統一采用64位浮點數進行存儲。
雖然它們存儲數據的方式是一致的,但是在進行數值運算時,卻會表現出明顯的差異性。整數參與運算時,得到的結果往往會和我們所想的一樣,例如下面的代碼。
// 加法 1 + 2 = 3 7 + 1 = 8 // 減法 15 - 12 = 3 3 - 2 = 1 // 乘法 7 * 180 = 1260 97 * 100 = 9700 // 除法 3 / 1 = 3 69 / 10 = 6.9
而對于浮點型運算,有時卻會出現一些意想不到的結果,如下面的代碼所示。
// 加法 0.1 + 0.2 = 0.30000000000000004 0.7 + 0.1 = 0.7999999999999999 // 減法 1.5 - 1.2 = 0.30000000000000004 0.3 - 0.2 = 0.09999999999999998 // 乘法 0.7 * 180 = 125.99999999999999 9.7 * 100 = 969.9999999999999 // 除法 0.3 / 0.1 = 2.9999999999999996 0.69 / 10 = 0.06899999999999999
得到這樣的結果,大家是不是覺得很奇怪呢?0.1 + 0.2為什么不是等于0.3,而是等于0.30000000000000004呢?接下來我們一探究竟。
1. 問題原因
首先我們來看看一個浮點型數在計算機中的表示,它總共長度是64位,其中最高位為符號位,接下來的11位為指數位,最后的52位為小數位,即有效數字的部分。
· 第0位:符號位sign表示數的正負,0表示正數,1表示負數。
· 第1位到第11位:存儲指數部分,用e表示。
· 第12位到第63位:存儲小數部分(即有效數字),用f表示,如圖1-1所示。

圖1-1
因為浮點型數使用64位存儲時,最多只能存儲52位的小數位,對于一些存在無限循環的小數位浮點數,會截取前52位,從而丟失精度,所以會出現上面實例中的結果。
2. 計算過程
接下來以0.1 + 0.2 = 0.30000000000000004的運算為例,看看為什么會得到這個計算結果。
首先將各個浮點數的小數位按照“乘2取整,順序排列”的方法轉換成二進制表示。
具體做法是用2乘以十進制小數,得到積,將積的整數部分取出;然后再用2乘以余下的小數部分,又得到一個積;再將積的整數部分取出,如此推進,直到積中的小數部分為零為止。
然后把取出的整數部分按順序排列起來,先取的整數作為二進制小數的高位有效位,后取的整數作為低位有效位,得到最終結果。
0.1轉換為二進制表示的計算過程如下。
0.1 * 2 = 0.2 //取出整數部分0 0.2 * 2 = 0.4 //取出整數部分0 0.4 * 2 = 0.8 //取出整數部分0 0.8 * 2 = 1.6 //取出整數部分1 0.6 * 2 = 1.2 //取出整數部分1 0.2 * 2 = 0.4 //取出整數部分0 0.4 * 2 = 0.8 //取出整數部分0 0.8 * 2 = 1.6 //取出整數部分1 0.6 * 2 = 1.2 //取出整數部分1
1.2取出整數部分1后,剩余小數為0.2,與這一輪運算的第一位相同,表示這將是一個無限循環的計算過程。
0.2 * 2 = 0.4 //取出整數部分0 0.4 * 2 = 0.8 //取出整數部分0 0.8 * 2 = 1.6 //取出整數部分1 0.6 * 2 = 1.2 //取出整數部分1 ...
因此0.1轉換成二進制表示為0.0 0011 0011 0011 0011 0011 0011……(無限循環)。
同理對0.2進行二進制的轉換,計算過程與上面類似,直接從0.2開始,相比于0.1,少了第一位的0,其余位數完全相同,結果為0.0011 0011 0011 0011 0011 0011……(無限循環)。
將0.1與0.2相加,然后轉換成52位精度的浮點型表示。
0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 (0.1) + 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 (0.2) = 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100
得到的結果為0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100,轉換成十進制值為0.30000000000000004。
3. 解決方案
通過上面詳細的講解,相信大家已經了解了對浮點數進行運算時會存在的問題,那么我們該如何解決呢?
這里提供一種方法,主要思路是將浮點數先乘以一定的數值轉換為整數,通過整數進行運算,然后將結果除以相同的數值轉換成浮點數后返回。
下面提供一套用于做浮點數加減乘除運算的代碼。
const operationObj = { /** * 處理傳入的參數,不管傳入的是數組還是以逗號分隔的參數都處理為數組 * @param args * @returns {*} */ getParam(args) { return Array.prototype.concat.apply([], args); }, /** * 獲取每個數的乘數因子,根據小數位數計算 * 1.首先判斷是否有小數點,如果沒有,則返回1; * 2.有小數點時,將小數位數的長度作為Math.pow()函數的參數進行計算 * 例如2的乘數因子為1,2.01的乘數因子為100 * @param x * @returns {number} */ multiplier(x) { let parts = x.toString().split('.'); return parts.length < 2 ? 1 : Math.pow(10, parts[1].length); }, /** * 獲取多個數據中最大的乘數因子 * 例如1.3的乘數因子為10,2.13的乘數因子為100 * 則1.3和2.13的最大乘數因子為100 * @returns {*} */ correctionFactor() { let args = Array.prototype.slice.call(arguments); let argArr = this.getParam(args); return argArr.reduce((accum, next) => { let num = this.multiplier(next); return Math.max(accum, num); }, 1); }, /** * 加法運算 * @param args * @returns {number} */ add(...args) { let calArr = this.getParam(args); // 獲取參與運算值的最大乘數因子 let corrFactor = this.correctionFactor(calArr); let sum = calArr.reduce((accum, curr) => { // 將浮點數乘以最大乘數因子,轉換為整數參與運算 return accum + Math.round(curr * corrFactor); }, 0); // 除以最大乘數因子 return sum / corrFactor; }, /** * 減法運算 * @param args * @returns {number} */ subtract(...args) { let calArr = this.getParam(args); let corrFactor = this.correctionFactor(calArr); let di? = calArr.reduce((accum, curr, curIndex) => { // reduce()函數在未傳入初始值時,curIndex從1開始,第一位參與運算的值需要 // 乘以最大乘數因子 if (curIndex === 1) { return Math.round(accum * corrFactor) - Math.round(curr * corrFactor); } // accum作為上一次運算的結果,就無須再乘以最大因子 return Math.round(accum) - Math.round(curr * corrFactor); }); // 除以最大乘數因子 return di? / corrFactor; }, /** * 乘法運算 * @param args * @returns {*} */ multiply(...args) { let calArr = this.getParam(args); let corrFactor = this.correctionFactor(calArr); calArr = calArr.map((item) => { // 乘以最大乘數因子 return item * corrFactor; }); let multi = calArr.reduce((accum, curr) => { return Math.round(accum) * Math.round(curr); }, 1); // 除以最大乘數因子 return multi / Math.pow(corrFactor, calArr.length); }, /** * 除法運算 * @param args * @returns {*} */ divide(...args) { let calArr = this.getParam(args); let quotient = calArr.reduce((accum, curr) => { let corrFactor = this.correctionFactor(accum, curr); // 同時轉換為整數參與運算 return Math.round(accum * corrFactor) / Math.round(curr * corrFactor); }); return quotient; } };
接下來我們通過以下這些代碼對加減乘除運算分別做測試,運算結果和我們期望的一致。
console.log(operationObj.add(0.1, 0.7)); // 0.8 console.log(operationObj.subtract(0.3, 0.2)); // 0.1 console.log(operationObj.multiply(0.7, 180)); // 126 console.log(operationObj.divide(0.3, 0.1)); // 3
- Python編程自學手冊
- Learning Cython Programming(Second Edition)
- Mastering Adobe Captivate 2017(Fourth Edition)
- MongoDB for Java Developers
- 微信公眾平臺開發:從零基礎到ThinkPHP5高性能框架實踐
- Windows Forensics Cookbook
- RabbitMQ Cookbook
- 深入理解Elasticsearch(原書第3版)
- Getting Started with Hazelcast(Second Edition)
- Learning Concurrency in Kotlin
- C和C++游戲趣味編程
- WordPress Search Engine Optimization(Second Edition)
- MongoDB Cookbook
- Getting Started with Windows Server Security
- 基于JavaScript的WebGIS開發