javascript
javascript进制转换_JavaScript 加减危机——为什么会出现这样的结果?
在日常工作計(jì)算中,我們?nèi)缏谋”?#xff0c;但是 JavaScript 總能給我們這樣那樣的 surprise~
如果小伙伴給出內(nèi)心的結(jié)果:
那么小伙伴會(huì)被事實(shí)狠狠地扇臉:
console.log(0.1 + 0.2); // 0.30000000000000004console.log(1 - 0.9); // 0.09999999999999998為什么會(huì)出現(xiàn)這種情況呢?咱們一探究竟!
三 問題復(fù)現(xiàn)
返回目錄
下面,我們會(huì)通過探討 IEEE 754 標(biāo)準(zhǔn),以及 JavaScript 加減的計(jì)算過程,來復(fù)現(xiàn)問題。
3.1 根源:IEEE 754 標(biāo)準(zhǔn)
返回目錄
JavaScript 里面的數(shù)字采用 IEEE 754 標(biāo)準(zhǔn)的 64 位雙精度浮點(diǎn)數(shù)。該規(guī)范定義了浮點(diǎn)數(shù)的格式,對(duì)于 64 位的浮點(diǎn)數(shù)在內(nèi)存中表示,最高的 1 位是符號(hào)為,接著的 11 位是指數(shù),剩下的 52 位為有效數(shù)字,具體:
- 第 0 位:符號(hào)位。用 s 表示,0 表示為正數(shù),1 表示為負(fù)數(shù);
- 第 1 - 11 位:存儲(chǔ)指數(shù)部分。用 e 表示;
- 第 12 - 63 位:存儲(chǔ)小數(shù)部分(即有效數(shù)字)。用 f 表示。
符號(hào)位決定一個(gè)數(shù)的正負(fù),指數(shù)部分決定數(shù)值的大小,小數(shù)部分決定數(shù)值的精度。
IEEE 754 規(guī)定,有效數(shù)字第一位默認(rèn)總是 1,不保存在 64 位浮點(diǎn)數(shù)之中。
也就是說,有效數(shù)字總是 1.XX......XX的形式,其中 XX......XX 的部分保存在 64 位浮點(diǎn)數(shù)之中,最長(zhǎng)可能為 52 位。
因此,JavaScript 提供的有效數(shù)字最長(zhǎng)為 53 個(gè)二進(jìn)制位(64 位浮點(diǎn)的后 52 位 + 有效數(shù)字第一位的 1)。
3.2 復(fù)現(xiàn):計(jì)算過程
返回目錄
通過 JavaScript 計(jì)算 0.1 + 0.2 時(shí),會(huì)發(fā)生什么?
1、 將 0.1 和 0.2 換成二進(jìn)制表示:
0.1 -> 0.0001100110011001...(無限)0.2 -> 0.0011001100110011...(無限浮點(diǎn)數(shù)用二進(jìn)制表達(dá)式是無窮的
我自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的web前端學(xué)習(xí)干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關(guān)注我的頭條號(hào)并在后臺(tái)私信我:前端,即可免費(fèi)獲取。
2、 因?yàn)?IEEE 754 標(biāo)準(zhǔn)的 64 位雙精度浮點(diǎn)數(shù)的小數(shù)部分最多支持 53 位二進(jìn)制位,所以兩者相加之后得到二進(jìn)制為:
0.0100110011001100110011001100110011001100110011001100因?yàn)楦↑c(diǎn)數(shù)小數(shù)位的限制,這個(gè)二進(jìn)制數(shù)字被截?cái)嗔?#xff0c;用這個(gè)二進(jìn)制數(shù)轉(zhuǎn)換成十進(jìn)制,就成了 0.30000000000000004,從而在進(jìn)行算數(shù)計(jì)算時(shí)產(chǎn)生誤差。
3.3 擴(kuò)展:數(shù)字安全
返回目錄
在看完上面小數(shù)的計(jì)算不精確后,jsliang 覺得有必要再聊聊整數(shù),因?yàn)檎麛?shù)同樣存在一些問題:
console.log(19571992547450991);// 19571992547450990console.log(19571992547450991 === 19571992547450994);// true是不是很驚奇!
因?yàn)?JavaScript 中 Number 類型統(tǒng)一按浮點(diǎn)數(shù)處理,整數(shù)也不能逃避這個(gè)問題:
// 最大值const MaxNumber = Math.pow(2, 53) - 1;console.log(MaxNumber); // 9007199254740991console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991// 最小值const MinNumber = -(Math.pow(2, 53) - 1);console.log(MinNumber); // -9007199254740991console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991即整數(shù)的安全范圍是: [-9007199254740991, 9007199254740991]。
超過這個(gè)范圍的,就存在被舍去的精度問題。
當(dāng)然,這個(gè)問題并不僅僅存在于 JavaScript 中,幾乎所有采用了 IEEE-745 標(biāo)準(zhǔn)的編程語言,都會(huì)有這個(gè)問題,只不過在很多其他語言中已經(jīng)封裝好了方法來避免精度的問題。
- PHP Float 浮點(diǎn)型 - Manual
- Java 您的小數(shù)點(diǎn)到哪里去了? - Brian Goetz
而因?yàn)?JavaScript 是一門弱類型的語言,從設(shè)計(jì)思想上就沒有對(duì)浮點(diǎn)數(shù)有個(gè)嚴(yán)格的數(shù)據(jù)類型,所以精度誤差的問題就顯得格外突出。
到此為止,我們可以看到 JavaScript 在處理數(shù)字類型的操作時(shí),可能會(huì)產(chǎn)生一些問題。
事實(shí)上,工作中還真會(huì)有問題!
某天我處理了一個(gè)工作表格的計(jì)算,然后第二天被告知線上有問題,之后被產(chǎn)品小姐姐問話:
- 為什么小學(xué)生都能做出的小數(shù)計(jì)算,你們計(jì)算機(jī)算不了呢?
默哀三秒,產(chǎn)生上面的找到探索,最終找到下面的解決方案。
四 解決問題
返回目錄
下面嘗試通過各種方式來解決浮點(diǎn)數(shù)計(jì)算的問題。
4.1 toFixed()
返回目錄
toFixed() 方法使用定點(diǎn)表示法來格式化一個(gè)數(shù)值。
- 《toFixed - MDN》
語法:numObj.toFixed(digits)
參數(shù):digits。小數(shù)點(diǎn)后數(shù)字的個(gè)數(shù);介于 0 到 20(包括)之間,實(shí)現(xiàn)環(huán)境可能支持更大范圍。如果忽略該參數(shù),則默認(rèn)為 0。
const num = 12345.6789;num.toFixed(); // '12346':進(jìn)行四舍五入,不包括小數(shù)部分。num.toFixed(1); // '12345.7':進(jìn)行四舍五入,保留小數(shù)點(diǎn)后 1 個(gè)數(shù)字。num.toFixed(6); // '12345.678900':保留小數(shù)點(diǎn)后 6 個(gè)數(shù)字,長(zhǎng)度不足時(shí)用 0 填充。(1.23e+20).toFixed(2); // 123000000000000000000.00 科學(xué)計(jì)數(shù)法變成正常數(shù)字類型toFixed() 得出的結(jié)果是 String 類型,記得轉(zhuǎn)換 Number 類型。
toFixed() 方法使用定點(diǎn)表示法來格式化一個(gè)數(shù),會(huì)對(duì)結(jié)果進(jìn)行四舍五入。
通過 toFixed() 我們可以解決一些問題:
原加減乘數(shù):
console.log(1.0 - 0.9);// 0.09999999999999998console.log(0.3 / 0.1);// 2.9999999999999996console.log(9.7 * 100);// 969.9999999999999console.log(2.22 + 0.1);// 2.3200000000000003使用 toFixed():
// 公式:parseFloat((數(shù)學(xué)表達(dá)式).toFixed(digits));// toFixed() 精度參數(shù)須在 0 與20 之間parseFloat((1.0 - 0.9).toFixed(10));// 0.1 parseFloat((0.3 / 0.1).toFixed(10));// 3 parseFloat((9.7 * 100).toFixed(10));// 970parseFloat((2.22 + 0.1).toFixed(10));// 2.32那么,講到這里,問題來了:
- parseFloat(1.005.toFixed(2))
會(huì)得到什么呢,你的反應(yīng)是不是 1.01 ?
然而并不是,結(jié)果是:1。
這么說的話,enm...摔!o(╥﹏╥)o
toFixed() 被證明了也不是最保險(xiǎn)的解決方式。
4.2 手寫簡(jiǎn)易加減乘除
返回目錄
既然 JavaScript 自帶的方法不能自救,那么我們只能換個(gè)思路:
- 將 JavaScript 的小數(shù)部分轉(zhuǎn)成字符串進(jìn)行計(jì)算
在這份代碼中,我們先通過石錘乘法的計(jì)算,通過將數(shù)字轉(zhuǎn)成整數(shù)進(jìn)行計(jì)算,從而產(chǎn)生了 安全 的數(shù)據(jù)。
JavaScript 整數(shù)運(yùn)算會(huì)不會(huì)出問題呢?
乘法計(jì)算好后,假設(shè)乘法已經(jīng)沒問題,然后通過乘法推出 加法、減法 以及 除法 這三則運(yùn)算。
最后,通過乘法和除法做出四舍五入的規(guī)則。
JavaScript Math.round() 產(chǎn)生的數(shù)字會(huì)不會(huì)有問題呢、
這樣,我們就搞定了兩個(gè)數(shù)的加減乘除和四舍五入(保留指定的長(zhǎng)度),那么,里面會(huì)不會(huì)有問題呢?
如果有,請(qǐng)例舉出來。
如果沒有,那么你能不能依據(jù)上面兩個(gè)數(shù)的加減乘除,實(shí)現(xiàn)三個(gè)數(shù)甚至多個(gè)數(shù)的加減乘除?
五 現(xiàn)成框架
返回目錄
這么重要的計(jì)算,如果自己寫的話你總會(huì)感覺惶惶不安,感覺充滿著危機(jī)。
所以很多時(shí)候,我們可以使用大佬們寫好的 JavaScript 計(jì)算庫,因?yàn)檫@些問題大佬已經(jīng)幫我們進(jìn)行了大量的測(cè)試了,大大減少了我們手寫存在的問題,所以我們可以調(diào)用別人寫好的類庫。
下面推薦幾款不錯(cuò)的類庫:
- Math.js。
Math.js 是一個(gè)用于 JavaScript 和 Node.js 的擴(kuò)展數(shù)學(xué)庫。
它具有支持符號(hào)計(jì)算的靈活表達(dá)式解析器,大量?jī)?nèi)置函數(shù)和常量,并提供了集成的解決方案來處理不同的數(shù)據(jù)類型,例如數(shù)字,大數(shù),復(fù)數(shù),分?jǐn)?shù),單位和矩陣。
強(qiáng)大且易于使用。
- decimal.js
JavaScript 的任意精度的十進(jìn)制類型。
- big.js
一個(gè)小型,快速,易于使用的庫,用于任意精度的十進(jìn)制算術(shù)運(yùn)算。
- bignumber.js
一個(gè)用于任意精度算術(shù)的 JavaScript 庫。
最后的最后,值得一提的是:如果對(duì)數(shù)字的計(jì)算非常嚴(yán)格,或許你可以將參數(shù)丟給后端,讓后端進(jìn)行計(jì)算,再返回給你結(jié)果。
例如涉及到比特幣、商城商品價(jià)格等的計(jì)算~
作者:jsliang
鏈接:https://juejin.im/post/5ddc7fa66fb9a07ad665b1f0
總結(jié)
以上是生活随笔為你收集整理的javascript进制转换_JavaScript 加减危机——为什么会出现这样的结果?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么打开网络访问 计算机共享,电脑只要打
- 下一篇: springboot urlresour