js怎么调用wasm_Long.js源码解析
基于現(xiàn)在市面上到處都是 Vue/React 之類(lèi)的源碼分析文章實(shí)在是太多了。(雖然我也寫(xiě)過(guò) Vite的源碼解析
所以這次來(lái)寫(xiě)點(diǎn)不一樣的。由于微信這邊用的是 protobuf 來(lái)進(jìn)行 rpc 調(diào)用。所以有時(shí)候需要將 JS 中的 Number 類(lèi)型轉(zhuǎn)換為 Long 類(lèi)型傳給后端。目前用的最多的就是 Long.js 了,然而市面上分析這個(gè)庫(kù)的文章少的出奇。唯一能搜到的一篇講的也非常的簡(jiǎn)略 Long.js源碼分析與學(xué)習(xí)
其實(shí)想要弄懂它的源碼還是非常難的。需要十分了解原碼 反碼 補(bǔ)碼 的相關(guān)知識(shí)知道計(jì)算機(jī)進(jìn)行四則運(yùn)算的原理以及了解 IEEE754 浮點(diǎn)數(shù)標(biāo)準(zhǔn)。不過(guò)懂了之后對(duì)位運(yùn)算的理解以及 JS中數(shù)字的存儲(chǔ)規(guī)則以及為什么 0.1+0.2 = 0.30000000000000004 這種問(wèn)題都有了完美的答案了。我 fork 下來(lái)的倉(cāng)庫(kù)是https://github.com/zhangyuang/long.js-design/blob/master/src/long.js
其中包含了我這篇文章所寫(xiě)的中文注釋
下一步是寫(xiě) Rust 版本的 long.js 來(lái)看看能否提升性能,雖然 long.js 本身已經(jīng)用了 wasm,看起來(lái)提升空間不大
簡(jiǎn)介
A Long class for representing a 64 bit two's-complement integer value derived from theClosure Libraryfor stand-alone use and extended with unsigned support.顧名思義。long.js 使用了 高位(high) 低位 (low) 分別是32位有符號(hào)數(shù)補(bǔ)碼的形式來(lái)表示一個(gè)64位數(shù)。這是因?yàn)樵?js 的世界中 最大的安全數(shù)的范圍是 `-2^53 -1 ~ 2^53-1` 并且在進(jìn)行位運(yùn)算的時(shí)候 js 會(huì)先將數(shù)字轉(zhuǎn)為 32位有符號(hào)數(shù)補(bǔ)碼在進(jìn)行運(yùn)算。
IEEE754
Please read this article for understand IEEE754
32位浮點(diǎn)數(shù)存儲(chǔ)規(guī)則
64為浮點(diǎn)數(shù)存儲(chǔ)規(guī)則
為什么最大的安全數(shù)字是 2^53 - 1
As IEEE754 says, double type variable has 1 sign, 11 exponent, 52 fraction。
Due to Number 1 is the value beginning default, so the exponent max value is 53. why MAX_SAFE_INTEGER is 2^53 - 1 because if number is more than 2^53 such as 2^53 and 2^53 + 1 whose fraction will be same.
根據(jù) IEEE754 標(biāo)準(zhǔn)。64為雙精度浮點(diǎn)數(shù)的存儲(chǔ)規(guī)則由1位符號(hào)位,11位指數(shù)位,52位數(shù)值位組成。同時(shí)IEEE754規(guī)定尾數(shù)第一位隱含為1不寫(xiě),所以一共有53位。為什么超過(guò)了 2^53 -1 就是不安全的。因?yàn)槌隽说臄?shù)字在二進(jìn)制的表示基礎(chǔ)上,總有另一個(gè)數(shù)字的二進(jìn)制表示跟它一樣。所以無(wú)法區(qū)分它們之間的關(guān)系的正確性。
reference
【算法】解析IEEE 754 標(biāo)準(zhǔn)?www.cnblogs.com源碼解析
fromInt
function fromInt(value, unsigned) {// 32 位數(shù)轉(zhuǎn) long類(lèi)型// 對(duì)超出32位數(shù)的情況以及負(fù)數(shù)的情況都做了特殊處理// Long { low: 16777216, high: 0, unsigned: false }var obj, cachedObj, cache;if (unsigned) {// 無(wú)符號(hào)數(shù)value >>>= 0; // 無(wú)符號(hào)右移0保證得到的是無(wú)符號(hào)數(shù),if (cache = (0 <= value && value < 256)) {// 只緩存0-256之間的結(jié)果cachedObj = UINT_CACHE[value];if (cachedObj)return cachedObj;}// 這里|0是因?yàn)槲贿\(yùn)算會(huì)轉(zhuǎn)成32位有符號(hào)數(shù)補(bǔ)碼在進(jìn)行計(jì)算。// 保證得到的數(shù)字的正負(fù)值的正確性如果超出 Math.pow(2,31) -1 則為負(fù)數(shù)obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); if (cache)UINT_CACHE[value] = obj;return obj;} else {// 有符號(hào)數(shù)// 如果value大于2147483647則得到的結(jié)果為負(fù)數(shù)如 2147483648 Long { low: -2147483648, high: -1, unsigned: false }value |= 0;if (cache = (-128 <= value && value < 128)) {// 只緩存-128到128之間的結(jié)果cachedObj = INT_CACHE[value];if (cachedObj)return cachedObj;}obj = fromBits(value, value < 0 ? -1 : 0, false);if (cache)INT_CACHE[value] = obj;return obj;} }fromNumber
function fromNumber(value, unsigned) {if (isNaN(value))// 不合法數(shù)字返回有符號(hào)0/無(wú)符號(hào)0return unsigned ? UZERO : ZERO;if (unsigned) {if (value < 0)// 如果是無(wú)符號(hào)且傳入負(fù)數(shù)則返回0return UZERO;if (value >= TWO_PWR_64_DBL)// 如果數(shù)字大于等于 Math.pow(2,64) 即 64 位無(wú)符號(hào)數(shù)能表示的最大值 Math.pow(2,64)-1// 返回最大的無(wú)符號(hào)數(shù) 即 fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); Long { low: -1, high: -1, unsigned: true }return MAX_UNSIGNED_VALUE;} else {// 有符號(hào)數(shù)最大值是Math.pow(2,63)-1最小值是-Math.pow(2,63)if (value <= -TWO_PWR_63_DBL)return MIN_VALUE; // fromBits(0, 0x80000000|0, false); Long {low: 0, high: -2147483648, unsigned: false}if (value >= TWO_PWR_63_DBL - 1) // 這里改成 -1 更容易理解return MAX_VALUE; // fromBits(0xFFFFFFFF, 0x7FFFFFFF|0, false); Long {low: -1, high: 2147483647, unsigned: false}}if (value < 0)return fromNumber(-value, unsigned).neg(); // 如果是負(fù)數(shù)則先轉(zhuǎn)換為正數(shù)再取反+1return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); // 分成2個(gè)32位有符號(hào)數(shù)來(lái)表示 }fromString
function fromString(str, unsigned, radix) {if (str.length === 0)throw Error('empty string');if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") // 非法字符串返回0return ZERO;if (typeof unsigned === 'number') {// 輸入的無(wú)符號(hào)標(biāo)志為number則賦值給radix// For goog.math.long compatibilityradix = unsigned,unsigned = false;} else {unsigned = !! unsigned;}radix = radix || 10; // 默認(rèn)是10進(jìn)制if (radix < 2 || 36 < radix)throw RangeError('radix');var p;if ((p = str.indexOf('-')) > 0) // 如果字符串包含 - 則拋錯(cuò)throw Error('interior hyphen');else if (p === 0) {// 如果是負(fù)數(shù),則返回對(duì)應(yīng)正數(shù)的取反+1值return fromString(str.substring(1), unsigned, radix).neg();}// Do several (8) digits each time through the loop, so as to// minimize the calls to the very expensive emulated div.var radixToPower = fromNumber(pow_dbl(radix, 8)); // Math.pow(radix, 8)var result = ZERO;for (var i = 0; i < str.length; i += 8) {var size = Math.min(8, str.length - i), // 取剩下的長(zhǎng)度和8的更小值value = parseInt(str.substring(i, i + size), radix); // 每次處理8位字符串轉(zhuǎn)換為number類(lèi)型如果不足8位則取剩下的所有字符if (size < 8) {var power = fromNumber(pow_dbl(radix, size)); //如果剩下的數(shù)字不足8位,則先乘以剩下的數(shù)字的位數(shù)result = result.mul(power).add(fromNumber(value)); // 再把結(jié)果加上數(shù)字本身} else {// 如果長(zhǎng)度大于8則處理// 之前處理的結(jié)果先乘以Long { low: 100000000, high: 0, unsigned: false }result = result.mul(radixToPower);result = result.add(fromNumber(value)); // 加上這次的數(shù)字}}result.unsigned = unsigned;return result; }add
LongPrototype.add = function add(addend) {// Long 對(duì)象相加// 把每個(gè)64位對(duì)象分成4塊。每16位為一塊// 如果不分塊32位為一塊,當(dāng)結(jié)果溢出時(shí)會(huì)丟失進(jìn)位if (!isLong(addend))addend = fromValue(addend);// Divide each number into 4 chunks of 16 bits, and then sum the chunks.var a48 = this.high >>> 16; // 得到高位的前16位var a32 = this.high & 0xFFFF; // 得到高位的后16位var a16 = this.low >>> 16; // 得到低位的前16位var a00 = this.low & 0xFFFF; // 得到低位的后16位var b48 = addend.high >>> 16;var b32 = addend.high & 0xFFFF;var b16 = addend.low >>> 16;var b00 = addend.low & 0xFFFF;var c48 = 0, c32 = 0, c16 = 0, c00 = 0;c00 += a00 + b00;c16 += c00 >>> 16; // 低位后16位相加,然后右移16位。只保留超過(guò)16位的進(jìn)位c00 &= 0xFFFF; // 只保留后16位c16 += a16 + b16; // 同樣重復(fù)上述操作。每次對(duì)16位進(jìn)行運(yùn)算。保留進(jìn)位,同時(shí)本身只保留16位c32 += c16 >>> 16;c16 &= 0xFFFF;c32 += a32 + b32;c48 += c32 >>> 16;c32 &= 0xFFFF;c48 += a48 + b48;c48 &= 0xFFFF;// 低位的前16位左移然后與上低位的后16位,得到一個(gè)正確的32位有符號(hào)數(shù)return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); };multiply
LongPrototype.multiply = function multiply(multiplier) {// Long 類(lèi)型相乘if (this.isZero())return ZERO;if (!isLong(multiplier))multiplier = fromValue(multiplier);// use wasm support if presentif (wasm) {// 使用wasm提升性能var low = wasm["mul"](this.low,this.high,multiplier.low,multiplier.high);return fromBits(low, wasm["get_high"](), this.unsigned);}if (multiplier.isZero())// 被乘數(shù)是0則返回0return ZERO;if (this.eq(MIN_VALUE))// 如果被乘數(shù)是奇數(shù)則返回最小值,否則返回0return multiplier.isOdd() ? MIN_VALUE : ZERO;if (multiplier.eq(MIN_VALUE))return this.isOdd() ? MIN_VALUE : ZERO;if (this.isNegative()) {if (multiplier.isNegative())return this.neg().mul(multiplier.neg());elsereturn this.neg().mul(multiplier).neg();} else if (multiplier.isNegative())return this.mul(multiplier.neg()).neg();// If both longs are small, use float multiplicationif (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24))return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned);// Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.// We can skip products that would overflow.// 同樣把 Long 對(duì)象分為4塊var a48 = this.high >>> 16; // 高位的前16位var a32 = this.high & 0xFFFF; // 高位的后16位var a16 = this.low >>> 16;var a00 = this.low & 0xFFFF;var b48 = multiplier.high >>> 16;var b32 = multiplier.high & 0xFFFF;var b16 = multiplier.low >>> 16;var b00 = multiplier.low & 0xFFFF;var c48 = 0, c32 = 0, c16 = 0, c00 = 0;c00 += a00 * b00;c16 += c00 >>> 16; // c16默認(rèn)為加上低位后16位相乘的進(jìn)位c00 &= 0xFFFF; // 只保留后16位c16 += a16 * b00; // 被乘數(shù)的后16位與乘數(shù)的前16位相乘并且加上之前的進(jìn)位c32 += c16 >>> 16; // 加上結(jié)果的進(jìn)位c16 &= 0xFFFF;c16 += a00 * b16;c32 += c16 >>> 16;c16 &= 0xFFFF;c32 += a32 * b00;c48 += c32 >>> 16;c32 &= 0xFFFF;c32 += a16 * b16;c48 += c32 >>> 16;c32 &= 0xFFFF;c32 += a00 * b32;c48 += c32 >>> 16;c32 &= 0xFFFF;// 高位的前16位,再加上前面的進(jìn)位之后再加上本值。溢出的進(jìn)位則舍去不考慮。// 因?yàn)樽罱K的結(jié)果還是 4 chunks組成的對(duì)象。所以不需要考慮 a48 * b16 這樣的結(jié)果,因?yàn)槌隽?4位的范疇會(huì)舍去c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;c48 &= 0xFFFF;return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); };neg
LongPrototype.negate = function negate() {if (!this.unsigned && this.eq(MIN_VALUE))return MIN_VALUE;return this.not().add(ONE); // 取反+1,原碼->補(bǔ)碼 }; 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的js怎么调用wasm_Long.js源码解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: nodejs读取服务器json文件,如何
- 下一篇: new一个数组