亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

數(shù)據(jù)精度問題自查手冊

liangzai_cool / 1864人閱讀

摘要:前言在數(shù)據(jù)敏感的業(yè)務(wù)場景中,常常會碰到數(shù)據(jù)精度問題,尤其在金額顯示占比統(tǒng)計(jì)等地方,該問題尤為顯著。計(jì)算機(jī)原理真香數(shù)值的精度問題,其實(shí)是非?;A(chǔ)的計(jì)算機(jī)原理知識。

前言

在數(shù)據(jù)敏感的業(yè)務(wù)場景中,常常會碰到數(shù)據(jù)精度問題,尤其在金額顯示、占比統(tǒng)計(jì)等地方,該問題尤為顯著。由于數(shù)據(jù)的每一位有效數(shù)字都包含真實(shí)的業(yè)務(wù)語義,一點(diǎn)點(diǎn)偏差甚至可能影響業(yè)務(wù)決策,這讓問題的嚴(yán)重性上升了幾個階梯。

那,什么是精度丟失?

一言以概之,凡是在運(yùn)行過程中,導(dǎo)致數(shù)值存在不可逆轉(zhuǎn)換時,就是精度丟失。

諸如:

人均交易額、占比這類計(jì)算得出的除法獲得的指標(biāo)(分子/分母)時,如果盲目的直接從該結(jié)果去推算分子數(shù)值時,很可能就存在精度丟失

浮點(diǎn)數(shù)計(jì)算結(jié)果,會出現(xiàn)很長尾的小數(shù)

這兩種廣義上來說都是精度丟失,但第一種情況可以通過更改技術(shù)方案等方式進(jìn)行規(guī)避。更多時候,所謂的精度問題,單指第二類問題。而面對這類問題時,如果沒有掌握原理,往往會一知半解,對結(jié)論印象不深,再次碰到問題只能一查再查。

計(jì)算機(jī)原理真香

數(shù)值的精度問題,其實(shí)是非?;A(chǔ)的計(jì)算機(jī)原理知識。通常,js的系統(tǒng)知識書籍(基礎(chǔ)類型章節(jié))一般也會提到,但像我這樣的非科班前端開發(fā),往往在這方面的知識儲備非常薄弱;而且,即使學(xué)習(xí)過了,也會因?yàn)榈谝淮螌W(xué)習(xí)時沒體感,沒有實(shí)際場景去強(qiáng)化認(rèn)知,掌握的也不深刻。

所以,在后續(xù)的業(yè)務(wù)開發(fā)中,有必要重新整理下遇到的問題,從遇到的問題出發(fā),追根溯源,才能更深刻地掌握知識點(diǎn)。

真實(shí)的Number

(本章節(jié)為基礎(chǔ)的規(guī)范介紹,有助于加深認(rèn)知,非必要知識,尤其是存儲形式,大部分問題的解答只需有概念即可。)

有別于其他語言會出現(xiàn)各類int、uint、float,JS語言只有一種數(shù)值類型——Number,它的背后是標(biāo)準(zhǔn)的雙精度浮點(diǎn)數(shù)實(shí)現(xiàn)(其他語言一般稱該類型為double或float64),這也就意味著,前端所有出現(xiàn)的數(shù)值,其實(shí)背后都是小數(shù)。

看一下雙精度浮點(diǎn)數(shù)的內(nèi)存模型(這幅維基百科的示意圖真是每篇精度文章都會引用~):


總共64位,分成了三部分:符號(sign)、指數(shù)(exponent)、尾數(shù)(fraction)。即,最終每一個數(shù)值都可以表示成:(-1)^S * 2^E * M。

存儲形式

這篇文章介紹了一個非常簡單的轉(zhuǎn)換方式,拿一個數(shù)值實(shí)際體驗(yàn)一下過程,例如34.1

第一步,取整數(shù)部分——34,通過除2取余數(shù):

計(jì)算過程 結(jié)果 余數(shù)
34/2 17 0
17/2 8 1
8/2 4 0
4/2 2 0
2/2 1 0
1/2 0 1

第二步,取小數(shù)部分——0.1,通過乘2取整數(shù)。如果結(jié)果大于1,則取1,否則取0:

計(jì)算過程 結(jié)果 整數(shù)
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
... ... ...

第三步,拼接結(jié)果,整數(shù)部分結(jié)果是從下往上取,小數(shù)部分則是從上往下取。結(jié)果為:(34.1)10 = (100010.0_0011_0011_0011...)2

ps:為了閱讀清晰,使用下劃線分隔符~該特性將在Chrome75到來,諸如Rust已經(jīng)具備

第四步,轉(zhuǎn)換為科學(xué)計(jì)數(shù)法(二進(jìn)制版),(34.1)10 = 1.00010_0_0011_0011... * 2(5)10 。到此,已經(jīng)可以獲取到公式中各個值所對應(yīng)的結(jié)果了:

S = 0

E = (5 + 1023)10 = (100_0000_0100)2

M = (00010_0_0011_0011...)2

最終的34.1的內(nèi)存存儲為:0? 100_0000_0100? 00010_0_0011_0011_0011_0011_0011_0011_0011_0011_0011_00 11_0011_01。(我反正是瞎了)

對于這個結(jié)果,還需要幾點(diǎn)補(bǔ)充說明:

為什么指數(shù)E的結(jié)果需要+1023?

指數(shù)部分有11位bit。使用無符號表示,可以表示范圍0~2047,其中0和2047為非規(guī)約形式,有特殊意義(詳見wiki,不做展開了),那剩余的范圍是1~2046;如果使用帶符號表示,可以表示范圍-1024~1023。因?yàn)閷?shí)際指數(shù)是可以存在負(fù)值的,為了避免使用符號表示法,就加入了這個偏移量。

至于,為什么不使用符號?我沒什么太深刻的體感。不過可以肯定的是,目的一定是為了后續(xù)的計(jì)算處理方便。比如:如果無符號,可以直接比較大???

為什么尾數(shù)M的結(jié)果省略了整數(shù)部分?

這是因?yàn)?,既然?shù)值一定可以表示成科學(xué)計(jì)數(shù)法,那尾數(shù)M的整數(shù)部分必然是1。

為什么?如果實(shí)在想不明白,可以參考十進(jìn)制的科學(xué)計(jì)數(shù)法,整數(shù)部分一定是1~9,因?yàn)橐坏┏^9,就會歸入指數(shù),即,整數(shù)部分為1~【進(jìn)制-1】。那在二進(jìn)制的科學(xué)計(jì)數(shù)法中,整數(shù)部分為1~1,則必然是1。

此外,這里還有另一點(diǎn)好處,通過省略整數(shù)部分,這個“1”就不需要占用存儲了,相對的,小數(shù)部分可以多一位有效數(shù)字。

如何表示無限循環(huán)的尾數(shù)部分?

正如上例中的34.1,它的尾數(shù)部分就是無限循環(huán),如果超出了存儲位數(shù),則勢必要進(jìn)行舍入。

實(shí)際上,存在多種舍入規(guī)則:

舍入到最接近

朝+∞方向舍入

朝-∞方向舍入

朝0方向舍入

也不做展開了,具體可以繼續(xù)查閱wiki。默認(rèn)理解下,“0舍1入”的規(guī)則夠用了。

舉一反三

Number.MAX_SAFE_INTEGER

Number類上的一個靜態(tài)屬性,值為9007199254740991。這個數(shù)是怎么來的呢?

因?yàn)镹umber的尾數(shù)有53位,理論上能表示的、精確的最大整數(shù)即為2-1,這也正是MAX_SAFE_INTEGER。超過這個值的數(shù)值,因?yàn)橛行?shù)字有限,Number已經(jīng)無法精確表示了。

然而指數(shù)部分最大值是1023,所以理論上Number能表示的最大值應(yīng)該至少達(dá)到2才對,那這個區(qū)間(2~2)的如何存儲呢?我沒有太深入思考,原理上應(yīng)該也是通過舍入規(guī)則去理解,不過還是不展開了,留個坑位~

題外話:
很多面試題里都包含了大整數(shù)的考點(diǎn)??嫉氖莾商?,第一點(diǎn)是,是否意識到了面試題中存在大整數(shù)問題;第二點(diǎn)是,如何用程序模擬手算過程。

不過我比較好奇的是,假如面試者使用了BigInt來完成大整數(shù)的四則運(yùn)算(跳過第二個考點(diǎn))是不是也算合格?【笑

Number.EPSILON

同樣是Number類上的一個靜態(tài)屬性,值為2.220446049250313e-16。這個數(shù)又是怎么來的?

同樣和尾數(shù)相關(guān),理論上能表示的最小尾數(shù)是1.00000000_00000000_00000000_00000000_00000000_00000000_0001,也就是EPSILON。

能精確表示的十進(jìn)制有效位數(shù)

一般來說,double類型的有效位數(shù),結(jié)論是16位。不過,目前我還沒看到非常嚴(yán)謹(jǐn)?shù)恼f明過程,現(xiàn)有的解釋方式略作搬運(yùn):

    MAX_SAFE_INTEGER是9007199254740991,它的位數(shù)就是16

    EPSILON它能精確到小數(shù)點(diǎn)后15位,再加上整數(shù)位,所以,有效位數(shù)是16

為什么不推薦使用位運(yùn)算

lint規(guī)則中一般是不建議在JS代碼中使用位運(yùn)算的。

第一點(diǎn)是,不便于維護(hù),考慮到前端開發(fā)普遍對位運(yùn)算不感冒;
第二點(diǎn)是,如兩次取反(~~3.11)、或0(3.11 | 0)這種取整操作,其背后,實(shí)際上是將64位的雙精度浮點(diǎn)數(shù)轉(zhuǎn)成了32位整數(shù)。如果對此沒有明確的認(rèn)知,能確保程序運(yùn)行時的入?yún)⒈囟ㄊ?2位整數(shù)范圍內(nèi)的話,就很容易埋坑,不如老老實(shí)實(shí)的使用Math.floorMath.round

const n = 2**32 + 0.1 // 4294967296.1

~~n // 期望是2^32,但其實(shí)結(jié)果是0

Math.floor(n) // 符合預(yù)期
Number的計(jì)算

明白了真實(shí)的Number,很容易就理解了——由于一個小數(shù)無法用二進(jìn)制精準(zhǔn)表示,勢必存在精度丟失,也就很自然地會出現(xiàn)諸如經(jīng)典的“0.1+0.2 ≠ 0.3”問題。但與此同時,我產(chǎn)生了一個疑問,兩個精度丟失的純小數(shù)是否能得出一個精準(zhǔn)表示的數(shù)值?

(由于雙精度浮點(diǎn)數(shù)實(shí)在位數(shù)太多了。。。寫得累,下面都使用單精度浮點(diǎn)數(shù)表意,雙精度的情況可以同理類推。)

嚴(yán)格來說,浮點(diǎn)數(shù)計(jì)算需要經(jīng)過:對階、尾數(shù)求和、規(guī)約化、舍入、溢出判斷(詳細(xì)內(nèi)容,可以參閱此文)。如果嚴(yán)格按照步驟進(jìn)行,有些過于死板,而且其中有更多的概念需要消化,這里僅僅是為了加深體感,所以使用更“小學(xué)”的方式來解決這個問題。

在進(jìn)行具體計(jì)算前,需要先掌握:

如何將十進(jìn)制轉(zhuǎn)為二進(jìn)制,上一章介紹過了

有效數(shù)字位數(shù),單精度浮點(diǎn)數(shù)尾數(shù)部分為23位,相應(yīng)的,能表示的有效位數(shù)為24位(為什么?),上一章也介紹過了

手算加法

0.1 + 0.4

將0.1和0.4轉(zhuǎn)為二進(jìn)制(不需要轉(zhuǎn)為科學(xué)計(jì)數(shù)法,即可跳過對階步驟),結(jié)果是:

0.1 = 0.0_0011_0011_0011_0011_0011_0011_01,保留24位有效數(shù)字,根據(jù)“0舍1入”進(jìn)位

0.4 = 0.0_1100_1100_1100_1100_1100_1101,保留24位有效數(shù)字,根據(jù)“0舍1入”進(jìn)位

可以看到,0.1和0.4都是存在進(jìn)位的,它的存儲值比真實(shí)值都要大,那兩個比真實(shí)值大的數(shù)的是如何恰好相加得出0.5的呢?

核心關(guān)鍵點(diǎn),其實(shí)在于這個**“有效位數(shù)”**,我們手算一下,把這兩個值直接相加,現(xiàn)在位數(shù)已經(jīng)對齊了:

       0.0_0011_0011_0011_0011_0011_0011_01
+      0.0_1100_1100_1100_1100_1100_1101
-----------------------------------------------
       0.1_0000_0000_0000_0000_0000_0000_(01)

0.1就是0.5,實(shí)在是太巧了!誤差正好被排除在有效位數(shù)之外!也就是,兩個丟失精度的數(shù)值計(jì)算后恰好精度復(fù)原了。

好奇心如我,覺得這里應(yīng)該是可以用數(shù)學(xué)方式去證明,無整數(shù)部分的小數(shù)計(jì)算,誤差一定會控制在相對小的范圍之內(nèi)的。否則,如果按照常規(guī)理解,隨著計(jì)算進(jìn)行,誤差會無休止的膨脹下去。

當(dāng)然,這種證明過程肯定很專業(yè),估計(jì)真展示在我面前,我也看不懂。我等普通吃瓜開發(fā),還是只管喊666就成了~

0.1 * 10

掌握了加減法,就自然會對乘法產(chǎn)生新的疑惑(主要是解決精度問題中很常見的辦法是轉(zhuǎn)為整數(shù))。既然,0.1是無法精確表示的,而1和10作為整數(shù)又是可以精確表示的,那這里的結(jié)果“1”是精確的“1”,還是一個非常近似的小數(shù)?如果是精確的,丟失精度的小數(shù)是如何轉(zhuǎn)為精確的整數(shù)的呢?

浮點(diǎn)數(shù)的乘法有特別算法(Booth算法)可以細(xì)講的,不過在此也不做具體展開。

基本原理上來說,就是將乘法簡化為“移位 + 加減法”。在本例中,10可以拆為2 + 2,繼續(xù)手算:

       0.1 * 10 = 0.1 * 2^3 + 0.1 * 2

       0.1100_1100_1100_1100_1100_1101
+      0.0011_0011_0011_0011_0011_0011_01
---------------------------------------------------
       1.0000_0000_0000_0000_0000_0000_(01)

是不是又一次感慨世界的奇妙?和上一例結(jié)果一樣,誤差再一次被命運(yùn)排除在有效位數(shù)之外,amazing~~

不過,需要注意的是,這兩個示例都限定在了無整數(shù)部分的小數(shù)計(jì)算(也可能是整數(shù)部分需要滿足什么條件才可以)。如果整數(shù)部分存在有效數(shù)字,會不同程度的擠壓小數(shù)部分可用的尾數(shù)有效位數(shù),就有可能導(dǎo)致無法出現(xiàn)這些神奇結(jié)果了。

/10 和 *0.1 的區(qū)別

這個區(qū)別可以簡單的進(jìn)行求證。只需提高結(jié)果的精度表示,就可以看到差異:

(6 / 10).toPrecision(17)  // "0.59999999999999998"
(6 * 0.1).toPrecision(17) // "0.60000000000000009"

究其原因,0.1是無法精確表示的,而10是可以精確表示的,所以和一個可以準(zhǔn)確表示的數(shù)進(jìn)行計(jì)算,勢必精度會高于和無法準(zhǔn)確表示的數(shù)進(jìn)行計(jì)算。

這就是典型的誤差累計(jì),當(dāng)結(jié)果是無法精確表示的時候,之前那神奇的誤差清除似乎就沒那么靈驗(yàn)了。所以,如果有必要,計(jì)算過程中,可以有意識的盡量使用整數(shù)。

解決方式 toFixed

這是最基礎(chǔ)的解法。不過需要注意的是,當(dāng)尾數(shù)是5的時候,它的結(jié)果往往不符合預(yù)期。

這篇文章里,舉了個例子:

(1.005).toFixed(2) // 結(jié)果是1.00,而不是1.01
// 文中給出的解釋是將該數(shù)值進(jìn)行更高精度展示,確實(shí)該數(shù)值的四舍五入確實(shí)是1.00
(1.005).toPrecision(17) // "1.0049999999999999"

然而,評論中,被人錘了:

(1.105).toPrecision(17) // "1.1050000000000000"
(1.105).toFixed(2) // 結(jié)果是1.10

這是為什么?

思路上沒有問題,只是,精度還不夠。如果我們按照規(guī)范理解toFixed,那核心在于這一步驟:

Let?n?be an integer for which the exact mathematical value of?n?÷ 10?–?x?is as close to zero as possible. If there are two such?n, pick the larger?n.

套用在這個例子中就是:

n / 100 - 1.105 // n為整數(shù),盡可能讓結(jié)果趨于0,最終計(jì)算誤差取17位精度
n = 110, // -0.0049999999999998934
n = 105, // 0.0050000000000001155

確實(shí)n = 110時,結(jié)果更接近0,也就是toFixed的結(jié)果是1.10。

當(dāng)然,使用取高精度方式去求解也未嘗不可,只是,實(shí)際規(guī)范過程中,可以注意到,這一步計(jì)算會把整數(shù)部分以及小數(shù)點(diǎn)后的n(toFixed參數(shù))位全部歸0,所以如果需要正確的觀測當(dāng)前值,需要toPrecision(17 + n),也就是:

(1.105).toPrecision(19) // 1.104999999999999982
// 也就可以正確推出toFixed(2)的結(jié)果是1.10了
Math.round

這里補(bǔ)充一點(diǎn),一般場景中,如果想獲取四舍五入的整數(shù),往往會使用Math.round。但需要注意,這里依然有不符合預(yù)期的結(jié)果:

Math.round(1.005 * 100) / 100 // 結(jié)果是1,而不是期望的1.1
Math.round(-0.5) // 結(jié)果是0,而不是期望的-1

第一例的問題其實(shí)是1.005無法轉(zhuǎn)為精確的整數(shù)導(dǎo)致的:1.005 * 1000 =?1004.9999999999999。所以只需要額外的多進(jìn)行一次轉(zhuǎn)換即可。

第二例的問題其實(shí)是符合規(guī)范的,Math.round的結(jié)果是取更靠近+∞方向,而不是常規(guī)理解的遠(yuǎn)離0,所以碰到負(fù)數(shù),更保險的做法應(yīng)該是使用絕對值再加符號位。

toPrecision

上文提過雙精度浮點(diǎn)數(shù)能精確表示的位數(shù)是16位。如果toFixed使用時沒有注意整數(shù)部分,也會導(dǎo)致預(yù)期之外的錯誤:

(1234123412341234.3).toFixed(2) // 1234123412341234.25

既然toFixed有種種問題,而Number本身能達(dá)到的精度是16位,那其實(shí),數(shù)值運(yùn)算后的最終結(jié)果只要進(jìn)行Number.parseFloat(num.toPrecision(16))處理即可。

轉(zhuǎn)整數(shù)計(jì)算

toPrecision可以避免絕大部分的小數(shù)點(diǎn)位數(shù)過長的問題。但,這可能導(dǎo)致結(jié)果和業(yè)務(wù)輸入的位數(shù)不一致,例如:

add(0.11, 0.19) => "0.30"
add(0.11, 0.100) => "0.210"

要解決這類問題,一般需要轉(zhuǎn)整數(shù)計(jì)算,不僅可以保證精度,也能輸出符合業(yè)務(wù)預(yù)期的位數(shù)。這也是絕大部分輕量庫的方案,基本原理是:

    求出入?yún)⒌淖畲笪粩?shù)

    轉(zhuǎn)為整數(shù)計(jì)算

    最后輸出結(jié)果時再除去最大位數(shù)

當(dāng)然,這種方案的缺陷是,過程中一般無法顧及超出范圍的大數(shù)。

類庫

一步步了解了各種場景下出現(xiàn)的問題,這時候再去選擇類庫,就有底氣的多,畢竟對于各種問題的解決已初步具備思路,不會只停留在知其然而不知其所以然的境界。而使用成熟類庫的好處是,它考慮的邊界條件更多、邏輯更完備,運(yùn)行時的穩(wěn)定性更高。

我列舉幾個類庫,不過使用不深,就請自行查閱啦~

Mathjs

BigNumber.js

Decimal.js(同一位大師)

Big.js(我不知道這位大師的三個庫具體區(qū)別是什么。。。)

number-precision,輕量級方案






參考

Binary numbers – floating point conversion

JavaScript 浮點(diǎn)數(shù)陷阱及解法

如何避開JavaScript浮點(diǎn)數(shù)計(jì)算精度問題

IEEE 754

從0.1+0.2=0.30000000000000004再看JS中的Number類型

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

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

相關(guān)文章

  • 系統(tǒng)的講解 - PHP 浮點(diǎn)數(shù)高精度運(yùn)算

    摘要:浮點(diǎn)數(shù)類型包括單精度浮點(diǎn)數(shù)和雙精度浮點(diǎn)數(shù)。小結(jié)通過浮點(diǎn)數(shù)精度的問題,了解到浮點(diǎn)數(shù)的小數(shù)用二進(jìn)制的表示。以后,在使用浮點(diǎn)數(shù)運(yùn)算的時候,一定要慎之又慎,細(xì)節(jié)決定成敗。 概述 記錄下,工作中遇到的坑 ... 關(guān)于 PHP 浮點(diǎn)數(shù)運(yùn)算,特別是金融行業(yè)、電子商務(wù)訂單管理、數(shù)據(jù)報(bào)表等相關(guān)業(yè)務(wù),利用浮點(diǎn)數(shù)進(jìn)行加減乘除時,稍不留神運(yùn)算結(jié)果就會出現(xiàn)偏差,輕則損失幾十萬,重則會有信譽(yù)損失,甚至吃上官司,我...

    makeFoxPlay 評論0 收藏0
  • 關(guān)于ADC芯片的選型

    摘要:關(guān)于芯片的選型,還是其他芯片的選型,那都不是隨隨便便就說了算得。芯片成本參差不齊,選的好直接起飛,選的不好,直接破產(chǎn)。 關(guān)于ADC芯片的選型,還是其他芯片的選型,那...

    mgckid 評論0 收藏0
  • 從拿到班車手冊.xls到搜索附近班車地點(diǎn)

    摘要:輾轉(zhuǎn)流傳出班車手冊后發(fā)現(xiàn)搜索實(shí)在是太不方便了,于是有了一個主義,想做一個可以搜索房子地址,找出附近班車點(diǎn)類似大眾點(diǎn)評的定位搜索附近餐館的功能。 起因 七月份要去某廠報(bào)道了,異地租房的時候發(fā)現(xiàn)想租一個有公司班車的地方,卻不知道哪里有班車。輾轉(zhuǎn)流傳出班車手冊后發(fā)現(xiàn)搜索實(shí)在是太不方便了,于是有了一個主義,想做一個可以搜索房子地址,找出附近班車點(diǎn)(類似大眾點(diǎn)評的定位搜索附近餐館的功能)?,F(xiàn)在做...

    jhhfft 評論0 收藏0

發(fā)表評論

0條評論

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