国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

senntyou / 2385人閱讀

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

前言

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

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

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

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

浮點數與 IEEE 754

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

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

1000
 200

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

1000
 200.01

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

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

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

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

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

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

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

指數位:11 位用于指數。這允許指數最大到 1024

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

精度丟哪兒去了?

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

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

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

十進制轉二進制

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

如何將十進制的 168.45 轉換為二進制?

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

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

所以整數部分 168 的轉換過程如下:

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

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

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

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

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

第六步,將商 5 除以 2,商 2 余數為 1。

第七步,將商 2 除以 2,商 1 余數為 0。

第八步,將商 1 除以 2,商 0 余數為 1。

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

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

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

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

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

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

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

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

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

...

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

這樣,我們就可以得出十進制數 168.45 轉換為二進制的結果,約等于 10101000.0111。

二進制轉十進制

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

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

解決方案

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

一個簡單粗暴的建議是,使用像 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,還是引入庫之后,帶來打包后的文件體積增積。

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

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

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

0.58 * 10000 / 100 // => 58

ok,搞定~

特別需要注意的是,在需要四舍五入的場景下,我們會習慣用到內置方法 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 錯誤

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

總結

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

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

好課推薦

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

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

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

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103820.html

相關文章

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

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

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

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

    Profeel 評論0 收藏0
  • JS中如何理解浮點數

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

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

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

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

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

    NusterCache 評論0 收藏0

發表評論

0條評論

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