摘要:目標在中,有一個實現了在中存儲型的對象,具體代碼可以戳此。下面,我們通過簡單講解一下這個庫的具體實現來看看如何在中實現一個型。如果你了解了這個實現原理,那么與之類似的,在中實現一個型或者其他類型的方法也是類似的。
背景
由于在項目中使用到了WebSocket的自定義二進制協議,需要將二進制轉為后端服務中定義的Long型。而在JavaScript中的Number類型由于自身原因,并不能完全表示Long型的數字,因此需要我們通過其他的方式來對Long型值進行存儲。
目標在GitHub中,有一個實現了在JavaScript中存儲Long型的對象,具體代碼可以戳此。下面,我們通過簡單講解一下這個庫的具體實現來看看如何在JavaScript中實現一個Long型。如果你了解了這個實現原理,那么與之類似的,在JavaScript中實現一個Long Long型或者其他類型的方法也是類似的。
具體實現其實,Long的實現很簡單,我們現在只要回歸到計算機的本質即可。在計算機中,其實存儲的都是01字符串。例如,Int占4個字節(我們以32位操作系統為例),而Long則占8個字節。
我們在存儲中只需要將數據通過二進制進行存儲,然后在操作中對二進制進行操作即可。
下面我們簡單的來介紹一下Long的各個代表操作和思想。
大致步驟 數據存儲在Long型對象中,我們采用了高32位和低32位,以及加上一個符號位判斷的值,用來進行數據的存儲,具體格式如下:
function Long(low, high, unsigned) { this.low = low | 0; this.high = high | 0; this.unsigned = !!unsigned; }
通過對高低位的存儲,從而讓兩個Number來同時表示一個Long型的高位和低位,從而滿足了數值的長度要求。
轉換為Long型我們目前只介紹一個通過字符串來講數據從String型轉換為Long型,其他的轉換例如從Number轉換為Long型是類似的,我們就不過多贅述了。
先看實現函數:
function fromString(str, unsigned, radix) { // 處理異常情況 if (str.length === 0) throw Error("empty string"); //處理為0的情況 if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") return ZERO; //處理只有兩個參數的情況 if (typeof unsigned === "number") { // For goog.math.long compatibility radix = unsigned, unsigned = false; } else { unsigned = !! unsigned; } radix = radix || 10; if (radix < 2 || 36 < radix) throw RangeError("radix"); var p; if ((p = str.indexOf("-")) > 0) throw Error("interior hyphen"); else if (p === 0) { // 轉為正值處理 return fromString(str.substring(1), unsigned, radix).neg(); } // 從最高位分8位處理一次,如果長度超過8位,則先處理高位,然后將高位直接乘以進制的8次方,再處理低后8位,循環到最后8位為止 var result = ZERO; for (var i = 0; i < str.length; i += 8) { var size = Math.min(8, str.length - i), value = parseInt(str.substring(i, i + size), radix); if (size < 8) { var power = fromNumber(pow_dbl(radix, size)); result = result.mul(power).add(fromNumber(value)); } else { result = result.mul(radixToPower); result = result.add(fromNumber(value)); } } result.unsigned = unsigned; return result; }
下面我們簡單的說下這個函數的實現:
對數據進行異常處理,排除一些邊界條件。
如果字符串為一個帶"-"號的值,則轉換為正值進行處理。
如果字符串為一個常規的Long型值,則先從最前面的8位開始處理,將其通過指定的進制轉換為Long型的值。
處理接下來的8位,并且將之前的結果乘以進制數的8次方,即數字高地位的合并。例如:18 = 1 * 10^1 + 8。
循環上面的操作,直到剩余的字符串長度小于8為止,即可結束,得到轉換之后的Long型。
轉換為字符串Long型轉換為字符串的方式,與字符串轉換為Long型的步驟差不多,差不多是一個相反的過程。
LongPrototype.toString = function toString(radix) { radix = radix || 10; if (radix < 2 || 36 < radix) throw RangeError("radix"); if (this.isZero()) return "0"; //如果是負值,Unsigned型的Long值永遠不會為負值 if (this.isNegative()) { if (this.eq(MIN_VALUE)) { // We need to change the Long value before it can be negated, so we remove // the bottom-most digit in this base and then recurse to do the rest. var radixLong = fromNumber(radix), div = this.div(radixLong), rem1 = div.mul(radixLong).sub(this); return div.toString(radix) + rem1.toInt().toString(radix); } else return "-" + this.neg().toString(radix); } //每次處理6位,處理方式與字符串轉換過來是類似的,和數學中十進制轉換為N進制方法相同——相除法 // Do several (6) digits each time through the loop, so as to // minimize the calls to the very expensive emulated div. var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned), rem = this; var result = ""; while (true) { var remDiv = rem.div(radixToPower), intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0, digits = intval.toString(radix); rem = remDiv; if (rem.isZero()) return digits + result; else { while (digits.length < 6) digits = "0" + digits; result = "" + digits + result; } } };
上面這個函數的實現步驟正好相反:
處理各種邊界條件
如果Long型為一個負值,則轉換為正值進行處理,如果Long型為0x80000000時,則對它進行了多帶帶處理。
在處理正值Long型為字符串時,操作方法與我們數學中教的轉換進制的相除法類似,具體操作為:先除以需要轉換的進制數,得到結果和余數,將結果重新作為被除數相除直到被除數為0,再將余數拼接起來即可。例如:18(10進制)轉換為8進制時,操作是:18 = 2 * 8 + 2; 2 = 0 * 8 + 2;,因此結果為0x22。只是,在此函數中,一次相除的是進制數的6次方,其余步驟是類似的。
通過上面的操作得到字符串后返回即可。
Long型相加在知道了Long型的存儲本質是使用高低各32位以后,Long型的運算其實就已經了解了。我們只需要針對特定的操作進行相對應的二進制操作,那么我們就能夠得到相對應的結果,下面的實例是Long型相加的操作,我們簡單了解下:
LongPrototype.add = function add(addend) { if (!isLong(addend)) addend = fromValue(addend); // 將每個數字分成4個16比特的塊,然后將這些塊加起來 var a48 = this.high >>> 16; var a32 = this.high & 0xFFFF; var a16 = this.low >>> 16; var a00 = this.low & 0xFFFF; 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; c00 &= 0xFFFF; c16 += a16 + b16; c32 += c16 >>> 16; c16 &= 0xFFFF; c32 += a32 + b32; c48 += c32 >>> 16; c32 &= 0xFFFF; c48 += a48 + b48; c48 &= 0xFFFF; return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); };
通過上面的操作我們就可以知道,Long型的四則運算等操作其實都是通過二進制和位運算來實現的。并沒有我們想象中的那么神秘。
總結其實,通過閱讀Long.js庫的源碼你就會發現,在JavaScript中實現一個Long型并不難,也許還是一個聽簡單的事情,不過重要的是我們可能想象不到這種的實現方式。因此,這個也證明了我們在思考一個問題問題的同時,我們也應該多從事情的本質來考慮,這樣就有可能得到解決方案。
附錄我在Long.js的代碼中添加了一些中文的注釋,如果有需要可以到我folk的倉庫進行閱讀學習。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83258.html
摘要:以和為例,說明中的數字數據如何轉換為二進制數據。對象用來表示通用的固定長度的原始二進制數據緩沖區。中的數字數據如何轉換為二進制數據對和有了一個大概的了解,下面讓我們來看下它是如何進行二進制數據操作的。 概述 本文主要通過對JavaScript中數字數據與二進制數據之間的轉換,讓讀者能夠了解在JavaScript中如何對數字類型(包括但不限于Number類型)進行處理。 二進制數據在日常...
摘要:的理解和區別代表有符號,整數在內存中存儲的二進制位的最高位為符號位,表示負數,表示正數。那接下來我們來學習數據在所開辟的內存空間時如何存儲的。請看下面例子為什么內存中存儲的是補碼對于整數來說數據存放內存中其實存放的是補碼。 ...
摘要:在類型系統部分中定義如下類型表示對象,如對象表示例化后的類型類型初始化函數表示在初始化時調用的用來初始化類型的函數,如構造函數表示構造對象需要的函數,如。 背景 寫這篇文章的原因是目前在看《Python源碼剖析》[1],但是這本書的作者陳儒老師剖析源碼的目的好像不是太明確,所以看上去是為了剖析源碼而剖析源碼,導致的結果是這本書里面的分析思路不太清楚(可能是我的理解問題),而且驗證想法...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 3944·2021-09-22 10:02
閱讀 3372·2019-08-30 15:52
閱讀 3064·2019-08-30 12:51
閱讀 760·2019-08-30 11:08
閱讀 2069·2019-08-29 15:18
閱讀 3110·2019-08-29 12:13
閱讀 3598·2019-08-29 11:29
閱讀 1877·2019-08-29 11:13