2017-08-02 169 views
8

讓我們考慮以下情況。Byte Array to Uint64 as a String

在GO例程創建一個字節數組,其中在8個字節大端[77, 101, 130, 33, 7, 252, 253, 82]包一答:645577006791947779410

在JavaScript代碼中,我收到這些字節爲Uint8Array。我們知道JavaScript目前不支持Uint64作爲安全的數字類型,並且不能對大於32位的整數執行按位操作,所以像buf[0] << 56這樣的東西將不會工作。

那麼什麼是這些字節直接解碼爲數字字符串"5577006791947779410"

P.S.我知道有plentyof 與JavaScript中的大整數的工作,但通常他們是巨大的,並提供大量的數學運算,我不需要在這裏。我正在尋找一個簡單的現代直接解決方案只是解碼BE包裝Uint64Int64字節到數字字符串。你有什麼想法?

回答

9

編輯:轉換(U)int64我現在肯定會推薦@ LS_DEV的解決方案。只有在有未知或更大的字節數時,我纔會使用我的解決方案。

我開始https://stackoverflow.com/a/21668344/3872370和修改了它:

function Int64ToString(bytes, isSigned) { 
 
    const isNegative = isSigned && bytes.length > 0 && bytes[0] >= 0x80; 
 
    const digits = []; 
 
    bytes.forEach((byte, j) => { 
 
    if(isNegative) 
 
     byte = 0x100 - (j == bytes.length - 1 ? 0 : 1) - byte; 
 
    for(let i = 0; byte > 0 || i < digits.length; i++) { 
 
     byte += (digits[i] || 0) * 0x100; 
 
     digits[i] = byte % 10; 
 
     byte = (byte - digits[i])/10; 
 
    } 
 
    }); 
 
    return (isNegative ? '-' : '') + digits.reverse().join(''); 
 
} 
 

 
const tests = [ 
 
    { 
 
    inp: [77, 101, 130, 33, 7, 252, 253, 82], 
 
    signed: false, 
 
    expectation: '5577006791947779410' 
 
    }, 
 
    { 
 
    inp: [255, 255, 255, 255, 255, 255, 255, 255], 
 
    signed: true, 
 
    expectation: '-1' 
 
    }, 
 
]; 
 

 
tests.forEach(test => { 
 
    const result = Int64ToString(test.inp, test.signed); 
 
    console.log(`${result} ${result !== test.expectation ? '!' : ''}=== ${test.expectation}`); 
 
});

起初符號被通過,如果最高位設置(bytes[0] > 128)檢查計算。對於負數,這些位必須否定(255 - byte),並且必須將1添加到數字中(因此256代替最後一個字節的255)。

forEach循環的基本思想是將每個字節拆分爲十進制數字(byte % 10並計算下一個數字的開銷(byte - digits[i])/10Math.floor(byte/10))。對於下一個字節,必須添加最後字節數字的移位結果(byte += digits[i] * 256digits[i] << 8)。

該代碼針對簡短,簡單和靈活性進行了優化。如果您使用的是字符串而不是字節或數字,並且不想使用任何庫,則看起來轉換性能並不重要。否則,該功能可以針對性能進行優化:最多可以同時處理四個字節,另外只需要替換0x1000x80(在(U)Int64的情況下只剩下兩個字節組),則forEach循環可以被展開。對十進制數字進行分組可能不會提高性能,因爲生成的字符串必須用零填充,因此需要在最終結果中刪除前導零。

+0

謝謝你的回覆!這對'uint64'似乎很有效。我需要做些什麼修改才能使它與'int64'一起工作呢?我在這裏創建了一個操場:https://jsfiddle.net/wcqLj1qg/。 – VisioN

+1

我已經更新了我的答案,並將其上傳到https://codepen.io/stephtr/pen/brBvxr 需要的修改是(顯然)添加減號,否定位和減1。 – Stephan

+0

可以使它稍微更緊湊通過將'byte> 0'更改爲'byte',因爲'byte'總是正的。另外'j == bytes.length - 1? 0:1'可以簡單地寫成'j!= bytes.length - 1',因爲布爾將被強制轉換爲數字:) – csander

2

這是我的解決方案。一般的策略是這樣的:

  • 如果數字是負數,否定它使用2的補數,並在年底加負號早在
  • 表示任意大小的數字爲0的數字LE陣列9
  • 對於在Uint8Array(從最多到最少顯著)的每個字節,乘以256運行總數,並添加給它的新的字節
  • 的值乘以256的數,加倍8倍(因爲2 ** 8 == 256
  • 要添加兩個數字,請使用基本s chool算法:
    • 開始與至少顯著數字
    • 添加兩個數
    • 的相應位數所得的數字是國防部10的總和;搭載的是1,如果總和爲10以上,否則爲0
    • 繼續添加相應的數字進位,直到我們增加最顯著數字,提的是0

關於速記的幾個注意事項:

  • n1[i] || 0得到的n1i個位數。如果這已經過了i的末尾,我們將其視爲0(想象數字在他們面前以無限0表示)。與n2相同。
  • added > 9產生布爾值,其被自動地轉換爲數字(1如果added >= 10,否則爲0)
  • i < n1.length || i < n2.length || carry檢查是否有更多的數字在任一加數的或進位仍然是非零
  • String(b).split('').map(Number).reverse()轉換,例如100'100',然後['1', '0', '0'],然後[1, 0, 0],然後[0, 0, 1]所以它在LE 10爲底的
  • result.reverse().join('')轉換,例如表示[0, 0, 1][1, 0, 0],然後'100'

代碼:

function add(n1, n2) { 
    const sum = [] 
    let carry = 0 
    for (let i = 0; i < n1.length || i < n2.length || carry; i++) { 
     const added = (n1[i] || 0) + (n2[i] || 0) + carry 
     sum[i] = added % 10 
     carry = added > 9 //floor(added/10) 
    } 
    return sum 
} 
function times256(n1) { 
    for (let i = 8; i; i--) n1 = add(n1, n1) 
    return n1 
} 
function toString(buffer) { 
    const isNegative = buffer[0] & 128 //check if high bit is set 
    if (isNegative) { //convert to positive, using 2's complement 
     buffer = buffer.map(b => ~b) //invert all bits 
     let i = buffer.length - 1 
     while (buffer[i] === 255) { //add 1 to the number, carrying if necessary 
      buffer[i] = 0 
      i-- 
     } 
     buffer[i]++ 
    } 
    const result = buffer.reduce((sum, b) => 
     add(
      times256(sum), //multiply sum by 256 
      String(b).split('').map(Number).reverse() //then add b 
     ), 
     [] 
    ) 
    const stringResult = result.reverse().join('') 
    if (isNegative) return '-' + stringResult 
    else return stringResult 
} 
+0

非常感謝您的回覆。你的代碼和解釋非常棒。現在我想知道哪種解決方案更好:您的或[Stephan的](https://stackoverflow.com/a/45505770/1249581)。他的解決方案更短,只包含2個循環,您的策略更加詳細和清晰。我們可能需要一些perf檢查。 – VisioN

+0

是的,我相信他的速度可能更快,但我發現這更容易概念化。 – csander

2

但這在UInt64版本 - 我無法想象,一個交換是困難:

<!DOCTYPE html> 
 
<html> 
 

 
<body> 
 
<span id='out1'></span> 
 
<br> 
 
<span id='out2'></span> 
 
<br> 
 
<span id='out3'></span> 
 
</body> 
 

 
<script> 
 
fnl=''; 
 
be=[77, 101, 130, 33, 7, 252, 253, 82]; 
 

 
function paddedBinary(n) { 
 
pad=''; 
 
sv=128; 
 
while (sv>n) {pad+='0';sv/=2;} 
 
return pad+n.toString(2); 
 
} 
 

 
for (let i=0;i<8;i++) 
 
fnl+=paddedBinary(be[i]); 
 

 
out1.textContent=fnl; 
 

 
dec=new Array(64); 
 
for (let i=0;i<64;i++) dec[i]=new Array(21).fill(0); 
 

 
function make2s() { 
 
dec[0][0]=1; 
 
for (let i=1;i<64;i++) { 
 
for (let j=0;j<21;j++) 
 
dec[i][j]=2*dec[i-1][j]; 
 
for (let j=0;j<21;j++) 
 
if (dec[i][j]>9) { 
 
dec[i][j]-=10; 
 
dec[i][j+1]++; 
 
} 
 
} 
 
} 
 

 
function int64add(v1,v2) { 
 
var res=new Array(21).fill(0); 
 
for (let i=0;i<21;i++) 
 
res[i]=v1[i]+v2[i]; 
 
for (let i=0;i<21;i++) 
 
if (res[i]>9) { 
 
res[i]-=10; 
 
res[i+1]++; 
 
} 
 
return res; 
 
} 
 

 
make2s(); 
 
for (let i=0;i<64;i++) 
 
out2.textContent+=dec[i]+' :: '; 
 

 
cv=new Array(21).fill(0); 
 
for (let i=0;i<fnl.length;i++) 
 
if (fnl[i]=='1') cv=int64add(cv,dec[63-i]); 
 

 
out3.textContent=cv; 
 

 
</script> 
 
</html>

paddedBinary()函數返回'完整'8位二進制數,所以我們可以創建'fnl'作爲BigEndian的64位字符串。由於JavaScript不會執行完整的64位算術運算,因此我創建了dec[]數組,以便將每個2的冪數存儲爲單個數字,將每個前一位數字加倍並平滑十位數。

然後剩下的就是添加我們想要的位,它使用類似的方法來平滑數十。

(和給出的答案是相反的!)

+0

謝謝你的回覆! – VisioN

2

另一種方法:兩UINT32鴻溝問題,以保持計算管理。

考慮越來越高的uint32(lh)。完整的編號可寫爲h*0x100000000+l。考慮十進制,人們也可以考慮低9位數字和高位數字(ldhd):ld=(h*0x100000000+l)%1000000000hd=(h*0x100000000+l)/1000000000。藉助一些算術和代數運算符的屬性,可以將這些操作分解爲安全的「半」64位操作,並在結尾處組成字符串。

function int64_to_str(a, signed) { 
 
    const negative = signed && a[0] >= 128; 
 
    const H = 0x100000000, D = 1000000000; 
 
    let h = a[3] + a[2] * 0x100 + a[1] * 0x10000 + a[0]*0x1000000; 
 
    let l = a[7] + a[6] * 0x100 + a[5] * 0x10000 + a[4]*0x1000000; 
 
    if(negative) { 
 
    h = H - 1 - h; 
 
    l = H - l; 
 
    } 
 
    const hd = Math.floor(h * H/D + l/D); 
 
    const ld = (((h % D) * (H % D)) % D + l) % D; 
 
    const ldStr = ld + ''; 
 
    return (negative ? '-' : '') + 
 
     (hd != 0 ? hd + '0'.repeat(9 - ldStr.length) : '') + ldStr; 
 
} 
 

 
let result = int64_to_str([77, 101, 130, 33, 7, 252, 253, 82], false); 
 
let expectation = '5577006791947779410'; 
 
console.log(result + ' ' + (result === expectation ? '===' : '!==') + ' ' + expectation); 
 

 
result = int64_to_str([255, 255, 255, 255, 255, 255, 255, 255], true); 
 
expectation = '-1'; 
 
console.log(result + ' ' + (result === expectation ? '===' : '!==') + ' ' + expectation);

如該算法的工作,即使(h % D) * (H % D)可以獲得比Number.MAX_SAFE_INTEGER大一些,因爲丟失比特但仍不爲零的意見詳細說明。

+1

儘管這將是處理固定大小整數的更好方法,但在某些情況下它將無法正常工作。我認爲應該刪除'Math.trunc(ld/D)'部分,因爲第一個加法器在數學上已經包含該部分。有了更正,我仍然不確定它是否正常工作。 'h * H'可能會導致數字超出'Number.MAX_SAFE_INTEGER'。通過用「D」和截斷除以,精度的損失應該消失。但是,我更擔心'(h%D)*(H%D)',因爲'D *(H%D)'也太大,這次需要全部的精度。 – Stephan

+0

@stephan'(h%D)*(H%D)',這是一個好點!我可以使用3 uint22,但不是今天... –

+0

我做了一點嘗試,似乎在乘法運算中引入的誤差很幸運地被計算模量時引入的誤差所補償(因爲'D'和'H'可以被整除由0x800)。沒有必要將它分成三組。如果你不想依賴那個行爲,你可以使用'((((h%D)*((H%D)/ 0x800))%D)* 0x800)%D'而不是'(h%D )*(H%D)%D'(因爲'D *(H%D)/ 0x800'小於'Number.MAX_SAFE_INTEGER')。 – Stephan