您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“前端大數(shù)的運(yùn)算及相關(guān)知識(shí)有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“前端大數(shù)的運(yùn)算及相關(guān)知識(shí)有哪些”吧!
前段時(shí)間我在公司的項(xiàng)目中負(fù)責(zé)的是權(quán)限管理這一塊的需求。需求的大概內(nèi)容就是系統(tǒng)的管理員可以在用戶管理界面對(duì)用戶和用戶扮演的角色進(jìn)行增刪改查的操作,然后當(dāng)用戶進(jìn)入主應(yīng)用時(shí),前端會(huì)請(qǐng)求到一個(gè)表示用戶權(quán)限的數(shù)組usr_permission,前端通過usr_permission來判斷用戶是否擁有某項(xiàng)權(quán)限。
這個(gè)usr_permission是一個(gè)長度為16的大數(shù)字符串?dāng)?shù)組,如下所示:
const usr_permission = [ "17310727576501632001", "1081919648897631175", "4607248419625398332", "18158795172266376960", "18428747250223005711", "17294384420617192448", "216384094707056832", "13902625308286185532", "275821367043", "0", "0", "0", "0", "0", "0", "0", ]
數(shù)組中的每一個(gè)元素可以轉(zhuǎn)成64位的二進(jìn)制數(shù),二進(jìn)制數(shù)中的每一位通過0和1表示一種權(quán)限,這樣每一個(gè)元素可以表示64種權(quán)限,整個(gè)usr_permission就可以表示16*64=1024種權(quán)限。后端之所以要對(duì)usr_permission進(jìn)行壓縮,是因?yàn)楹蠖瞬捎玫氖俏⒎?wù)架構(gòu),各個(gè)模塊在通信的過程中通過在請(qǐng)求頭中加入usr_permission來做權(quán)限的認(rèn)證。
數(shù)組usr_permission的第0個(gè)元素表示第[0, 63]號(hào)的權(quán)限,第1個(gè)元素表示第[64, 127]號(hào)的權(quán)限,以此類推。比如現(xiàn)在我們要查找第220號(hào)權(quán)限:
const permission = 220 // 查看銷售出庫 const usr_permission = [ "17310727576501632001", "1081919648897631175", "4607248419625398332", "18158795172266376960", "18428747250223005711", "17294384420617192448", "216384094707056832", "13902625308286185532", "275821367043", "0", "0", "0", "0", "0", "0", "0", ] // "18158795172266376960" 表示第193號(hào)~第256號(hào)權(quán)限 // 1111 1100 0000 0000 1111 1111 1111 1111 1111 0000 0000 0011 1111 1111 0000 0000 // 220 % 64 = 28 // 0000 0000 0000 0000 0000 0000 0000 1111 1100 0000 0000 1111 1111 1111 1111 1111 // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 // ------------------------------------------------------------------------------- // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
從usr_permission中我們得知第220號(hào)權(quán)限由第3個(gè)元素"18158795172266376960"表示。
我們將"18158795172266376960"轉(zhuǎn)成二進(jìn)制得到1111 1100 0000 0000 1111 1111 1111 1111 1111 0000 0000 0011 1111 1111 0000 0000。
將220除以64得到余數(shù)28,也就是說二進(jìn)制數(shù)1111 1100 0000 0000 1111 1111 1111 1111 1111 0000 0000 0011 1111 1111 0000 0000從右數(shù)的第28位表示第220號(hào)權(quán)限。
我們可以將二進(jìn)制數(shù)1111 1100 0000 0000 1111 1111 1111 1111 1111 0000 0000 0011 1111 1111 0000 0000右移28位,將表示第220號(hào)權(quán)限的位數(shù)推到最低位。
然后將二進(jìn)制數(shù)與1進(jìn)行按位與操作,如果當(dāng)前用戶擁有第220號(hào)權(quán)限,則最后得到的結(jié)果為1,反之為0。
以上就是前端查找權(quán)限的大致過程,那么這個(gè)代碼要怎么寫呢?在編寫代碼之前,我們先來復(fù)習(xí)一下JavaScript大數(shù)相關(guān)的知識(shí),了解編寫代碼的過程中會(huì)遇到什么問題。
在計(jì)算機(jī)組成原理這門課里我們學(xué)過,在以IEEE 754為標(biāo)準(zhǔn)的浮點(diǎn)運(yùn)算中,有兩種浮點(diǎn)數(shù)值表示方式,一種是單精度(32位),還有一種是雙精度(64位)。
在IEEE 754標(biāo)準(zhǔn)中,一個(gè)數(shù)字被表示成 +1.0001x2^3
這種形式。比如說在單精度(32位)表示法中,有1位用來表示數(shù)字的正負(fù)(符號(hào)位),8位用來表示2的冪次方(指數(shù)偏移值E,需要減去一個(gè)固定的數(shù)字得到指數(shù)e),23位表示1后面的小數(shù)位(尾數(shù))。
比如0 1000 0010 0001 0000 0000 0000 0000 000,第1位0表示它是正數(shù),第[2, 9]位1000 0010轉(zhuǎn)換成十進(jìn)制就是130,我們需要減去一個(gè)常數(shù)127得到3,也就是這個(gè)數(shù)字需要乘以2的三次方,第[10, 32]位則表示1.0001 0000 0000 0000 0000 000,那么這個(gè)數(shù)字表示的就是二級(jí)制中的 +1.0001*2^3
,轉(zhuǎn)換成十進(jìn)制也就是8.5。
同理,雙精度(64位)也是一樣的表現(xiàn)形式,只是在64位中有11位用來表示2的冪次方,52位用來表示小數(shù)位。
JavaScript 就是采用IEEE754 標(biāo)準(zhǔn)定義的64 位浮點(diǎn)格式表示數(shù)字。在64位浮點(diǎn)格式中,有52位可以表示小數(shù)點(diǎn)后面的數(shù)字,加上小數(shù)點(diǎn)前面的1,就有53位可以用來表示數(shù)字,也就是說64位浮點(diǎn)可以表示的最大的數(shù)字是 2^53-1
,超過 2^53-1
的數(shù)字就會(huì)發(fā)生精度丟失。因?yàn)?^53用64位浮點(diǎn)格式表示就變成了這樣:
符號(hào)位:0 指數(shù):53 尾數(shù):1.000000...000 (小數(shù)點(diǎn)后一共52個(gè)0)
小數(shù)點(diǎn)后面的第53個(gè)0已經(jīng)被丟棄了,那么 2^53+1
的64位浮點(diǎn)格式就會(huì)變得和 2^53
一樣。一個(gè)浮點(diǎn)格式可以表示多個(gè)數(shù)字,說明這個(gè)數(shù)字是不安全的。所以在JavaScript中,最大的安全數(shù)是 2^53-1
,這樣就保證了一個(gè)浮點(diǎn)格式對(duì)應(yīng)一個(gè)數(shù)字。
有一道很常見的前端面試題,就是問你為什么JavaScript中0.1+0.2為什么不等于0.3?0.1轉(zhuǎn)換成二進(jìn)制是0.0 0011 0011 0011 0011 0011 0011 ... (0011循環(huán)),0.2轉(zhuǎn)換成二進(jìn)制是0.0011 0011 0011 0011 0011 0011 0011 ... (0011循環(huán)),用64位浮點(diǎn)格式表示如下:
// 0.1 e = -4; m = 1.1001100110011001100110011001100110011001100110011010 (52位) // 0.2 e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位)
然后把它們相加:
e = -4; m = 1.1001100110011001100110011001100110011001100110011010 (52位) + e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位) // 0.1和0.2指數(shù)不一致,需要進(jìn)行對(duì)階操作 // 對(duì)階操作,會(huì)產(chǎn)生精度丟失 // 之所以選0.1進(jìn)行對(duì)階操作是因?yàn)橛乙茙淼木葋G失遠(yuǎn)遠(yuǎn)小于左移帶來的溢出 e = -3; m = 0.1100110011001100110011001100110011001100110011001101 (52位) + e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位) e = -3; m = 10.0110011001100110011001100110011001100110011001100111 (52位) // 發(fā)生精度丟失 e = -2; m = 1.00110011001100110011001100110011001100110011001100111 (53位)
我們看到已經(jīng)溢出來了(超過了52位),那么這個(gè)時(shí)候我們就要做四舍五入了,那怎么舍入才能與原來的數(shù)最接近呢?比如1.101要保留2位小數(shù),那么結(jié)果有可能是 1.10 和 1.11 ,這個(gè)時(shí)候兩個(gè)都是一樣近,我們?nèi)∧囊粋€(gè)呢?規(guī)則是保留偶數(shù)的那一個(gè),在這里就是保留 1.10。
回到我們之前的就是取m=1.0011001100110011001100110011001100110011001100110100 (52位)
然后我們得到最終的二進(jìn)制數(shù):
1.0011001100110011001100110011001100110011001100110100 * 2 ^ -2
=0.010011001100110011001100110011001100110011001100110100
轉(zhuǎn)換成十進(jìn)制就是0.30000000000000004,所以,所以0.1 + 0.2 的最終結(jié)果是0.30000000000000004。
通過前面的講解,我們清晰地認(rèn)識(shí)到在以前,JavaScript是沒有辦法對(duì)大于 2^53-1
的數(shù)字進(jìn)行處理的。不過后來,JavaScript提供了內(nèi)置對(duì)象BigInt來處理大數(shù)。 BigInt
可以表示任意大的整數(shù)??梢杂迷谝粋€(gè)整數(shù)字面量后面加 n
的方式定義一個(gè) BigInt
,如: 10n
,或者調(diào)用函數(shù) BigInt()
。
const theBiggestInt = 9007199254740991n; const alsoHuge = BigInt(9007199254740991); // ? 9007199254740991n const hugeString = BigInt("9007199254740991"); // ? 9007199254740991n typeof 1n === 'bigint'; // true typeof BigInt('1') === 'bigint'; // true 0n === 0 // ? false 0n == 0 // ? true
用BigInt實(shí)現(xiàn)的權(quán)限查找代碼如下:
hasPermission(permission: Permission) { const usr_permissions = this.userInfo.usr_permissions const arr_index = Math.floor(permission / 64) const bit_index = permission % 64 if (usr_permissions && usr_permissions.length > arr_index) { if ((BigInt(usr_permissions[arr_index]) >> BigInt(bit_index)) & 1n) { return true } } return false }
但是BigInt存在兼容性問題:
根據(jù)我司用戶使用瀏覽器版本數(shù)據(jù)的分析,得到如下餅狀圖:
不兼容BigInt瀏覽器的比例占到12.4%
解決兼容性的問題,一種方式是如果希望在項(xiàng)目中繼續(xù)使用BigInt,那么需要Babel的一些插件進(jìn)行轉(zhuǎn)換。這些插件需要調(diào)用一些方法去檢測運(yùn)算符什么時(shí)候被用于BigInt,這將導(dǎo)致不可接受的性能損失,而且在很多情況下是行不通的。另外一種方法就是找一些封裝大數(shù)運(yùn)算方法的第三方庫,使用它們的語法做大數(shù)運(yùn)算。
很多第三方庫可以用來做大數(shù)運(yùn)算,大體的思路就是定義一個(gè)數(shù)據(jù)結(jié)構(gòu)來存放大數(shù)的正負(fù)及數(shù)值,分別算出每一位的結(jié)果再存儲(chǔ)到數(shù)據(jù)結(jié)構(gòu)中。
// yarn add jsbn @types/jsbn import { BigInteger } from 'jsbn' hasPermission(permission: Permission) { const usr_permissions = this.userInfo.usr_permissions const arr_index = Math.floor(permission / 64) const bit_index = permission % 64 if (usr_permissions && usr_permissions.length > arr_index) { if ( new BigInteger(usr_permissions[arr_index]) .shiftRight(bit_index) .and(new BigInteger('1')) .toString() !== '0' ) { return true } } return false }
// yarn add jsbi import JSBI from 'jsbi' hasPermission(permission: Permission) { // 開發(fā)環(huán)境不受權(quán)限限制 if (__DEVELOPMENT__) { return true } const usr_permissions = this.userInfo.usr_permissions const arr_index = Math.floor(permission / 64) const bit_index = permission % 64 if (usr_permissions && usr_permissions.length > arr_index) { const a = JSBI.BigInt(usr_permissions[arr_index]) const b = JSBI.BigInt(bit_index) const c = JSBI.signedRightShift(a, b) const d = JSBI.BigInt(1) const e = JSBI.bitwiseAnd(c, d) if (e.toString() !== '0') { return true } } return false }
后來,一位同事提到了一種新的權(quán)限查找的解決方案:前端獲取到數(shù)組usr_permission以后,將usr_permission的所有元素轉(zhuǎn)成二進(jìn)制,并進(jìn)行字符串拼接,得到一個(gè)表示用戶所有權(quán)限的字符串permissions。當(dāng)需要查找權(quán)限時(shí),查找permissions對(duì)應(yīng)的位數(shù)即可。這樣相當(dāng)于在用戶進(jìn)入系統(tǒng)時(shí)就將所有的權(quán)限都算好,而不是用一次算一次。
在中學(xué)時(shí),我們學(xué)到的將十進(jìn)制轉(zhuǎn)成二進(jìn)制的方法是輾轉(zhuǎn)相除法,這里有一種新思路:
比如我們要用5個(gè)二進(jìn)制位表示11這個(gè)數(shù)
我們需要先定義一個(gè)長度為5,由2的倍數(shù)組成的數(shù)組[16, 8, 4, 2, 1],然后將11與數(shù)組中的元素挨個(gè)比較
11 < 16, 所以得到[0, x, x, x, x]
11 >= 8,所以得到[0, 1, x, x, x],11 - 8 = 3
3 < 4,所以得到[0, 1, 0, x, x]
3 >= 2,所以得到[0, 1, 0, 1, x],3 - 2 = 1
1>= 1,所以得到[0, 1, 0, 1, 1],1 - 1 = 0,結(jié)束
所以用5位二進(jìn)制數(shù)表示11的結(jié)果就是01011
根據(jù)上面的思路可以得到的代碼如下,這里用big.js這個(gè)包去實(shí)現(xiàn):
import Big from 'big.js' import _ from 'lodash' permissions = '' // 最后生成的權(quán)限字符串 // 生成長度為64,由2的倍數(shù)組成的數(shù)組 generateBinaryArray(bits: number) { const arr: any[] = [] _.each(_.range(bits), (index) => { arr.unshift(Big(2).pow(index)) }) return arr } // 將usr_permission中單個(gè)元素轉(zhuǎn)成二進(jìn)制 translatePermission(binaryArray: any[], permission: string) { let bigPermission = Big(permission) const permissionBinaryArray: number[] = [] _.each(binaryArray, (v, i) => { if (bigPermission.gte(binaryArray[i])) { bigPermission = bigPermission.minus(binaryArray[i]) permissionBinaryArray.unshift(1) } else { permissionBinaryArray.unshift(0) } }) return permissionBinaryArray.join('') } // 將usr_permission中所有元素的二進(jìn)制形式進(jìn)行拼接 generatePermissionString() { const usr_permissions = this.userInfo.usr_permissions let str = '' const binaryArray = this.generateBinaryArray(64) _.each(usr_permissions, (permission, index) => { str = `${str}${this.translatePermission(binaryArray, permission)}` }) this.permissions = str } // 判斷時(shí)候擁有某項(xiàng)權(quán)限 hasPermission(permission: Permission) { if (!this.permissions) { return false } return this.permissions[permission] === '1' }
到此,相信大家對(duì)“前端大數(shù)的運(yùn)算及相關(guān)知識(shí)有哪些”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。