成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

「干貨」細說 Javascript 中的浮點數(shù)精度丟失問題(內(nèi)附好課推薦)

senntyou / 2422人閱讀

摘要:前言最近,朋友問了我這樣一個問題在中的運算結(jié)果,為什么是這樣的雖然我告訴他說,這是由于浮點數(shù)精度問題導致的。由于可以用階碼移動小數(shù)點,因此稱為浮點數(shù)。它的實現(xiàn)遵循標準,使用位精度來表示浮點數(shù)。

前言

最近,朋友 L 問了我這樣一個問題:在 chrome 中的運算結(jié)果,為什么是這樣的?

0.55 * 100 // 55.00000000000001
0.56 * 100 // 56.00000000000001
0.57 * 100 // 56.99999999999999
0.58 * 100 // 57.99999999999999
0.59 * 100 // 59
0.60 * 100 // 60

雖然我告訴他說,這是由于浮點數(shù)精度問題導致的。但他還是不太明白,為何有的結(jié)果輸出整數(shù),有的是以 ...001 的小數(shù)結(jié)尾,有的卻是以 ...999 的小數(shù)結(jié)尾,跟預想中的有差異。

這其實牽涉到了計算機原理的知識,真要解釋清楚什么是浮點數(shù),恐怕得分好幾個章節(jié)了。想深入了解的同學,可以前往 這篇文章 細讀。今天我們僅討論浮點數(shù)運算結(jié)果的成因,以及如何實現(xiàn)我們期望的結(jié)果。

浮點數(shù)與 IEEE 754

在解釋什么是浮點數(shù)之前,讓我們先從較為簡單的小數(shù)點說起。

小數(shù)點,在數(shù)制中代表一種對齊方式。比如要比較 1000 和 200 哪個比較大,該怎么做呢?必須把他們右對齊:

1000
 200

發(fā)現(xiàn) 1 比 0(前面補零)大,所以 1000 比較大。那么如果要比較 1000 和 200.01 呢?這時候就不是右對齊了,而應該是以小數(shù)點對齊:

1000
 200.01

小數(shù)點的位置,在進制表示中是至關重要的。位置差一位整體就要差進制倍(十進制就是十倍)。在計算機中也是這樣,雖然計算機使用二進制,但在處理非整數(shù)時,也需要考慮小數(shù)點的位置問題。無法對齊小數(shù)點,就無法做加減法比較這樣的操作。

接下來的一個重要概念:在計算機中的小數(shù)有兩種,定點 和 浮點

定點的意思是,小數(shù)點固定在 32 位中的某個位置,前面的是整數(shù),后面的是小數(shù)。小數(shù)點具體固定在哪里,可以自己在程序中指定。定點數(shù)的優(yōu)點是很簡單,大部分運算實現(xiàn)起來和整數(shù)一樣或者略有變化,但是缺點則是表示范圍太小,精度很差,不能充分運用存儲單元。

浮點數(shù)就是設計來克服這個缺點的,它相當于一個定點數(shù)加上一個階碼,階碼表示將這個定點數(shù)的小數(shù)點移動若干位。由于可以用階碼移動小數(shù)點,因此稱為浮點數(shù)。我們在寫程序時,用到小數(shù)的地方,用 float 類型表示,可以方便快速地對小數(shù)進行運算。

浮點數(shù)在 Javascript 中的存儲,與其他語言如 Java 和 Python 不同。所有數(shù)字(包括整數(shù)和小數(shù))都只有一種類型 — Number。它的實現(xiàn)遵循 IEEE 754 標準,使用64位精度來表示浮點數(shù)。它是目前最廣泛使用的格式,該格式用 64 位二進制表示像下面這樣:

從上圖中可以看出,這 64 位分為三個部分:

符號位:1 位用于標志位。用來表示一個數(shù)是正數(shù)還是負數(shù)

指數(shù)位:11 位用于指數(shù)。這允許指數(shù)最大到 1024

尾數(shù)位:剩下的 52 位代表的是尾數(shù),超出的部分自動進一舍零

精度丟哪兒去了?

問:要把小數(shù)裝入計算機,總共分幾步?

答:3 步。
第一步:轉(zhuǎn)換成二進制
第二步:用二進制科學計算法表示
第三步:表示成 IEEE 754 形式
但第一步和第三步都有可能 丟失精度。

十進制是給人看的。但在進行運算之前,必須先轉(zhuǎn)換為計算機能處理的二進制。最后,當運算完畢后,再將結(jié)果轉(zhuǎn)換回十進制,繼續(xù)給人看。精度就丟失于這兩次轉(zhuǎn)換的過程中。

十進制轉(zhuǎn)二進制

接下來,就具體說說轉(zhuǎn)換的過程。來看一個簡單的例子:

如何將十進制的 168.45 轉(zhuǎn)換為二進制?

讓我們拆為兩個部分來解析:

1、整數(shù)部分。它的轉(zhuǎn)換方法是,除 2 取余法。即每次將整數(shù)部分除以 2,余數(shù)為該位權上的數(shù),而商繼續(xù)除以 2,余數(shù)又為上一個位權上的數(shù),這個步驟一直持續(xù)下去,直到商為 0 為止,最后讀數(shù)時候,從最后一個余數(shù)讀起,一直到最前面的一個余數(shù)。

所以整數(shù)部分 168 的轉(zhuǎn)換過程如下:

第一步,將 168 除以 2,商 84,余數(shù)為 0。

第二步,將商 84 除以 2,商 42 余數(shù)為 0。

第三步,將商 42 除以 2,商 21 余數(shù)為 0。

第四步,將商 21 除以 2,商 10 余數(shù)為 1。

第五步,將商 10 除以 2,商 5 余數(shù)為 0。

第六步,將商 5 除以 2,商 2 余數(shù)為 1。

第七步,將商 2 除以 2,商 1 余數(shù)為 0。

第八步,將商 1 除以 2,商 0 余數(shù)為 1。

第九步,讀數(shù)。因為最后一位是經(jīng)過多次除以 2 才得到的,因此它是最高位。讀數(shù)的時候,從最后的余數(shù)向前讀,即 10101000。

2、小數(shù)部分。它的轉(zhuǎn)換方法是,乘 2 取整法。即將小數(shù)部分乘以 2,然后取整數(shù)部分,剩下的小數(shù)部分繼續(xù)乘以 2,然后再取整數(shù)部分,剩下的小數(shù)部分又乘以 2,一直取到小數(shù)部分為 0 為止。如果永遠不能為零,就同十進制數(shù)的四舍五入一樣,按照要求保留多少位小數(shù)時,就根據(jù)后面一位是 0 還是 1 進行取舍。如果是 0 就舍掉,如果是 1 則入一位,換句話說就是,0 舍 1 入。讀數(shù)的時候,要從前面的整數(shù)開始,讀到后面的整數(shù)。

所以小數(shù)部分 0.45 (保留到小數(shù)點第四位)的轉(zhuǎn)換過程如下:

第一步,將 0.45 乘以 2,得 0.9,則整數(shù)部分為 0,小數(shù)部分為 0.9。

第二步, 將小數(shù)部分 0.9 乘以 2,得 1.8,則整數(shù)部分為 1,小數(shù)部分為 0.8。

第三步, 將小數(shù)部分 0.8 乘以 2,得 1.6,則整數(shù)部分為 1,小數(shù)部分為 0.6。

第四步,將小數(shù)部分 0.6 乘以 2,得 1.2,則整數(shù)部分為 1,小數(shù)部分為 0.2。

第五步,將小數(shù)部分 0.2 乘以 2,得 0.4,則整數(shù)部分為 0,小數(shù)部分為 0.4。

第六步,將小數(shù)部分 0.4 乘以 2,得 0.8,則整數(shù)部分為 0,小數(shù)部分為 0.8。

...

可以看到,從第六步開始,將無限循環(huán)第三、四、五步,一直乘下去,最后不可能得到小數(shù)部分為 0。因此,這個時候只好學習十進制的方法進行四舍五入了。但是二進制只有 0 和 1 兩個,于是就出現(xiàn) 0 舍 1 入的 “口訣” 了,這也是計算機在轉(zhuǎn)換中會產(chǎn)生誤差的根本原因。但是由于保留位數(shù)很多,精度很高,所以可以忽略不計。

這樣,我們就可以得出十進制數(shù) 168.45 轉(zhuǎn)換為二進制的結(jié)果,約等于 10101000.0111。

二進制轉(zhuǎn)十進制

它的轉(zhuǎn)換方法相對簡單些,按權相加法。就是將二進制每位上的數(shù)乘以權,然后相加之和即是十進制數(shù)。其中有兩個注意點:要知道二進制每位的權值,要能求出每位的值。

所以,將剛才的二進制 10101000.0111 轉(zhuǎn)換為十進制,得到的結(jié)果就是 168.4375,再四舍五入一下,即 168.45。

解決方案

正如本文開頭所提到的,在 JavaScript 中進行浮點數(shù)的運算,會有不少奇葩的問題。在明白了產(chǎn)生問題的根本原因之后,當然是想辦法解決啦~

一個簡單粗暴的建議是,使用像 mathjs 這樣的庫。它的 API 也挺簡單的:

// load math.js
const math = require("mathjs")

// functions and constants
math.round(math.e, 3)             // 2.718
math.atan2(3, -3) / math.pi       // 0.75

// expressions
math.eval("12 / (2.3 + 0.7)")     // 4
math.eval("12.7 cm to inch")      // 5 inch
math.eval("sin(45 deg) ^ 2")      // 0.5

// chaining
math.chain(3)
    .add(4)
    .multiply(2)
    .done()  // 14

但如果在工程中,沒有太多需要進行運算的場景的話,就不建議這么做了。畢竟引入三方庫也是有成本的,無論是學習 API,還是引入庫之后,帶來打包后的文件體積增積。

那么,不引入庫該怎么處理浮點數(shù)呢?

可以從需求出發(fā)。例如,本文開頭的例子??梢圆孪氲?,需求可能是要把小數(shù)轉(zhuǎn)為百分比,通常會保留兩位小數(shù)。而在一些對數(shù)字較為敏感的業(yè)務場景中,可能并不希望對數(shù)字進行四舍五入,所以 toFixed() 方法就沒法用了。

一種思路是,將小數(shù)點像右多移動 n 位,取整后再除以 (10 * n)。比如這樣:

0.58 * 10000 / 100 // => 58

ok,搞定~

特別需要注意的是,在需要四舍五入的場景下,我們會習慣用到內(nèi)置方法 toFixed(),但它存在一些問題:

1.35.toFixed(1) // 1.4 正確
1.335.toFixed(2) // 1.33  錯誤
1.3335.toFixed(3) // 1.333 錯誤
1.33335.toFixed(4) // 1.3334 正確
1.333335.toFixed(5)  // 1.33333 錯誤
1.3333335.toFixed(6) // 1.333333 錯誤

另外,它的返回結(jié)果類型是 String。不能直接拿來做運算,因為計算機會認為是 字符串拼接。

總結(jié)

計算機在做運算的時候,會分三個步驟。其中,將十進制轉(zhuǎn)為二進制,再將二進制轉(zhuǎn)為十進制的時候,都會產(chǎn)生精度丟失。

使用庫,是最簡單粗暴的解決方案。但如果使用不頻繁,還是要根據(jù)需求,手動解決。在使用內(nèi)置方法 toFixed() 的時候,要特別注意它的返回類型,不要直接拿來做運算。

好課推薦

近期公眾號后臺有多位讀者留言,金三銀四求職卻頻頻遇阻,詢問有沒有什么體系性、針對性的內(nèi)容可以看看。

最近我正好在 gitChat 上看到了,來自百度的大佬 LucasHC侯策) 的系列課程《前端開發(fā) 核心知識進階》,因為拜讀過大佬寫的書 《React 狀態(tài)管理與同構實戰(zhàn)》,所以就買了這門課程。

這門課程 共 50 講,從 36 個熱門主題 切入講解高頻面試題,以及會深度剖析底層原理,干貨滿滿,甚至還有不少大佬自己作為 “BAT” 面試官多年的 “私房題”,以及面試時遇到的 “經(jīng)典題”,非常實用了。

而且剛好現(xiàn)在在搞 特價 69 元,特價到5月7號結(jié)束,沒幾天了。掃描下圖二維碼就可以學習,需要的拿走不謝。

PS:歡迎關注我的公眾號 “超哥前端小棧”,交流更多的想法與技術。

文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103820.html

相關文章

  • 深度剖析0.1 +0.2===0.30000000000000004的原因

    摘要:吐槽一句,大二的專業(yè)課數(shù)字邏輯電路終于用在工作上了。,整數(shù)位為,且精度只到十分位,因此是。如果是不限精度的話,轉(zhuǎn)換后的二進制數(shù)應該是無限循環(huán)。再看一下百科給出的標準因此,的類型,最高的位是符號位,接著的位是指數(shù),剩下的位為有效數(shù)字。 showImg(https://segmentfault.com/img/remote/1460000011902479?w=600&h=600); 用一...

    haobowd 評論0 收藏0
  • 為什么0.1+0.2不等于0.3

    摘要:又如,對于,結(jié)果其實并不是,但是最接近真實結(jié)果的數(shù),比其它任何浮點數(shù)都更接近。許多語言也就直接顯示結(jié)果為了,而不展示一個浮點數(shù)的真實結(jié)果了。小結(jié)本文主要介紹了浮點數(shù)計算問題,簡單回答了為什么以及怎么辦兩個問題為什么不等于。 原文地址:為什么0.1+0.2不等于0.3 先看兩個簡單但詭異的代碼: 0.1 + 0.2 > 0.3 // true 0.1 * 0.1 = 0.01000000...

    Profeel 評論0 收藏0
  • JS中如何理解浮點數(shù)?

    摘要:本文通過介紹的二進制存儲標準來理解浮點數(shù)運算精度問題,和理解對象的等屬性值是如何取值的,最后介紹了一些常用的浮點數(shù)精度運算解決方案。浮點數(shù)精度運算解決方案關于浮點數(shù)運算精度丟失的問題,不同場景可以有不同的解決方案。 本文由云+社區(qū)發(fā)表 相信大家在平常的 JavaScript 開發(fā)中,都有遇到過浮點數(shù)運算精度誤差的問題,比如 console.log(0.1+0.2===0.3)// fa...

    bang590 評論0 收藏0
  • JS魔法堂:徹底理解0.1 + 0.2 === 0.30000000000000004的背后

    摘要:也就是說不僅是會產(chǎn)生這種問題,只要是采用的浮點數(shù)編碼方式來表示浮點數(shù)時,則會產(chǎn)生這類問題。到這里我們都理解只要采取的浮點數(shù)編碼的語言均會出現(xiàn)上述問題,只是它們的標準類庫已經(jīng)為我們提供了解決方案而已。 Brief 一天有個朋友問我JS中計算0.7 * 180怎么會等于125.99999999998,坑也太多了吧!那時我猜測是二進制表示數(shù)值時發(fā)生round-off error所導致,但并不...

    JerryWangSAP 評論0 收藏0
  • 從一個 bug 看 javascript精度丟失問題

    摘要:就像一些無理數(shù)不能有限表示,如圓周率,等。遵循規(guī)范,采用雙精度存儲,占用。參考中不會失去精度的最大值數(shù)字精度丟失的一些典型問題 問題描述 后端返回 { spaceObject: { objectId: 1049564069045993472 } } 前端模版,使用的是 atpl 模版 前端獲取 objectId 的方式,const objectId = $(#test).da...

    NusterCache 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<