[英]Byte Array to Uint64 as a String
讓我們考慮以下情況。
Go例程創建一個字節數組,其中包含8字節Big Endian的Uint64號5577006791947779410
[77, 101, 130, 33, 7, 252, 253, 82]
5577006791947779410
[77, 101, 130, 33, 7, 252, 253, 82]
。
在JavaScript代碼中,我將這些字節作為Uint8Array
接收。 我們知道JavaScript當前不支持Uint64作為安全數字類型,並且不能對大於32位的整數執行按位運算,因此像buf[0] << 56
這樣的東西永遠不會起作用。
那么將這些字節直接解碼為數字字符串"5577006791947779410"
什么?
PS我知道在JavaScript中有很多用於處理大整數的 庫 ,但通常它們很大並且提供了大量的數學運算,這里我不需要。 我正在尋找一個簡單的現代直接解決方案, 只需將BE打包的Uint64和Int64字節解碼為數字字符串。 你有什么想法嗎?
編輯:對於轉換(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]) / 10
的開銷(byte - digits[i]) / 10
和Math.floor(byte / 10)
)。 對於下一個字節,必須添加最后字節數字的移位結果( byte += digits[i] * 256
和digits[i] << 8
)。
該代碼針對簡潔性,簡單性和靈活性進行了優化。 如果您正在處理字符串而不是字節或數字,並且不想使用任何庫,那么轉換性能似乎並不重要。 否則,該函數可以針對性能進行優化:最多可以同時處理四個字節,另外一個只需要替換0x100
和0x80
(另外(在(U)Int64的情況下只剩下兩個字節組) forEach
循環可以被展開。 對十進制數字進行分組可能不會提高性能,因為生成的字符串必須用零填充,因此需要在最終結果中刪除前導零。
另一種方法:在兩個uint32中划分問題以保持計算的可管理性。
考慮更低和更高的uint32( l
和h
)。 h*0x100000000+l
可寫為h*0x100000000+l
。 考慮到十進制,還可以考慮低9位和更高位( ld
和hd
): ld=(h*0x100000000+l)%1000000000
和hd=(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
,但是在評論中詳細說明算法仍然Number.MAX_SAFE_INTEGER
,因為丟失的比特仍為零。
這是我的解決方案。 總體戰略是這樣的:
Uint8Array
中的每個字節(從最高到最低),將運行總數乘以256並將其添加到新字節的值 2 ** 8 == 256
) 關於速記的一些注意事項:
n1[i] || 0
n1[i] || 0
得到n1
第i
位。 如果這超過了i
的結尾,我們將其視為0(想象在它們前面以無限0表示的數字)。 與n2
相同。 added > 9
會產生一個布爾值,它會自動轉換為一個數字(如果added >= 10
話,則為1 added >= 10
,否則為0) i < n1.length || i < n2.length || carry
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 base-10中表示 result.reverse().join('')
將例如[0, 0, 1]
result.reverse().join('')
轉換為[0, 0, 1]
result.reverse().join('')
[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
}
這是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的冪存儲為單個數字,方法是將每個前一個數字加倍並平滑數字。
然后剩下的就是添加我們想要的位,它使用類似的方法來平滑數十。
(答案是反過來的!)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.