js怎么调用wasm_Long.js源码解析
基于現在市面上到處都是 Vue/React 之類的源碼分析文章實在是太多了。(雖然我也寫過 Vite的源碼解析
所以這次來寫點不一樣的。由于微信這邊用的是 protobuf 來進行 rpc 調用。所以有時候需要將 JS 中的 Number 類型轉換為 Long 類型傳給后端。目前用的最多的就是 Long.js 了,然而市面上分析這個庫的文章少的出奇。唯一能搜到的一篇講的也非常的簡略 Long.js源碼分析與學習
其實想要弄懂它的源碼還是非常難的。需要十分了解原碼 反碼 補碼 的相關知識知道計算機進行四則運算的原理以及了解 IEEE754 浮點數標準。不過懂了之后對位運算的理解以及 JS中數字的存儲規則以及為什么 0.1+0.2 = 0.30000000000000004 這種問題都有了完美的答案了。我 fork 下來的倉庫是https://github.com/zhangyuang/long.js-design/blob/master/src/long.js
其中包含了我這篇文章所寫的中文注釋
下一步是寫 Rust 版本的 long.js 來看看能否提升性能,雖然 long.js 本身已經用了 wasm,看起來提升空間不大
簡介
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位有符號數補碼的形式來表示一個64位數。這是因為在 js 的世界中 最大的安全數的范圍是 `-2^53 -1 ~ 2^53-1` 并且在進行位運算的時候 js 會先將數字轉為 32位有符號數補碼在進行運算。
IEEE754
Please read this article for understand IEEE754
32位浮點數存儲規則
64為浮點數存儲規則
為什么最大的安全數字是 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.
根據 IEEE754 標準。64為雙精度浮點數的存儲規則由1位符號位,11位指數位,52位數值位組成。同時IEEE754規定尾數第一位隱含為1不寫,所以一共有53位。為什么超過了 2^53 -1 就是不安全的。因為超出了的數字在二進制的表示基礎上,總有另一個數字的二進制表示跟它一樣。所以無法區分它們之間的關系的正確性。
reference
【算法】解析IEEE 754 標準?www.cnblogs.com源碼解析
fromInt
function fromInt(value, unsigned) {// 32 位數轉 long類型// 對超出32位數的情況以及負數的情況都做了特殊處理// Long { low: 16777216, high: 0, unsigned: false }var obj, cachedObj, cache;if (unsigned) {// 無符號數value >>>= 0; // 無符號右移0保證得到的是無符號數,if (cache = (0 <= value && value < 256)) {// 只緩存0-256之間的結果cachedObj = UINT_CACHE[value];if (cachedObj)return cachedObj;}// 這里|0是因為位運算會轉成32位有符號數補碼在進行計算。// 保證得到的數字的正負值的正確性如果超出 Math.pow(2,31) -1 則為負數obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); if (cache)UINT_CACHE[value] = obj;return obj;} else {// 有符號數// 如果value大于2147483647則得到的結果為負數如 2147483648 Long { low: -2147483648, high: -1, unsigned: false }value |= 0;if (cache = (-128 <= value && value < 128)) {// 只緩存-128到128之間的結果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))// 不合法數字返回有符號0/無符號0return unsigned ? UZERO : ZERO;if (unsigned) {if (value < 0)// 如果是無符號且傳入負數則返回0return UZERO;if (value >= TWO_PWR_64_DBL)// 如果數字大于等于 Math.pow(2,64) 即 64 位無符號數能表示的最大值 Math.pow(2,64)-1// 返回最大的無符號數 即 fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); Long { low: -1, high: -1, unsigned: true }return MAX_UNSIGNED_VALUE;} else {// 有符號數最大值是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(); // 如果是負數則先轉換為正數再取反+1return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); // 分成2個32位有符號數來表示 }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') {// 輸入的無符號標志為number則賦值給radix// For goog.math.long compatibilityradix = unsigned,unsigned = false;} else {unsigned = !! unsigned;}radix = radix || 10; // 默認是10進制if (radix < 2 || 36 < radix)throw RangeError('radix');var p;if ((p = str.indexOf('-')) > 0) // 如果字符串包含 - 則拋錯throw Error('interior hyphen');else if (p === 0) {// 如果是負數,則返回對應正數的取反+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), // 取剩下的長度和8的更小值value = parseInt(str.substring(i, i + size), radix); // 每次處理8位字符串轉換為number類型如果不足8位則取剩下的所有字符if (size < 8) {var power = fromNumber(pow_dbl(radix, size)); //如果剩下的數字不足8位,則先乘以剩下的數字的位數result = result.mul(power).add(fromNumber(value)); // 再把結果加上數字本身} else {// 如果長度大于8則處理// 之前處理的結果先乘以Long { low: 100000000, high: 0, unsigned: false }result = result.mul(radixToPower);result = result.add(fromNumber(value)); // 加上這次的數字}}result.unsigned = unsigned;return result; }add
LongPrototype.add = function add(addend) {// Long 對象相加// 把每個64位對象分成4塊。每16位為一塊// 如果不分塊32位為一塊,當結果溢出時會丟失進位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位。只保留超過16位的進位c00 &= 0xFFFF; // 只保留后16位c16 += a16 + b16; // 同樣重復上述操作。每次對16位進行運算。保留進位,同時本身只保留16位c32 += c16 >>> 16;c16 &= 0xFFFF;c32 += a32 + b32;c48 += c32 >>> 16;c32 &= 0xFFFF;c48 += a48 + b48;c48 &= 0xFFFF;// 低位的前16位左移然后與上低位的后16位,得到一個正確的32位有符號數return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); };multiply
LongPrototype.multiply = function multiply(multiplier) {// Long 類型相乘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())// 被乘數是0則返回0return ZERO;if (this.eq(MIN_VALUE))// 如果被乘數是奇數則返回最小值,否則返回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 對象分為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默認為加上低位后16位相乘的進位c00 &= 0xFFFF; // 只保留后16位c16 += a16 * b00; // 被乘數的后16位與乘數的前16位相乘并且加上之前的進位c32 += c16 >>> 16; // 加上結果的進位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位,再加上前面的進位之后再加上本值。溢出的進位則舍去不考慮。// 因為最終的結果還是 4 chunks組成的對象。所以不需要考慮 a48 * b16 這樣的結果,因為超出了64位的范疇會舍去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,原碼->補碼 }; 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的js怎么调用wasm_Long.js源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nodejs读取服务器json文件,如何
- 下一篇: Java代理模式——静态代理动态代理