[英]Convert very huge number to base 10 string
一個整數測量4個字節。 在我的例子中,我的數字為1 MB。 如何快速將它們轉換為人類可讀的十進制數?
該數字出現在包含Size
項的uint[]
數組中。
我一直在考慮你的問題。 我沒有編寫解決方案,但這是一種方法:
首先,讓我們假設你有一個2 n位的集合而不失一般性。 (如果你的位數少於2 n ,則用前導零填充位數組,直到你這樣做。顯然,這樣做的時間絕對不會超過數組的大小。)在你的情況下,你說你有一百萬的uint,所以這是2 25位。
我們還假設每個2 k + 1位的集合可以均勻地分成兩個位集合,左右集合,每個集合有2 k位。
因此,每個比特集合或子集合具有“大小”,其是2的精確冪。 最小的集合包含一個位,無法進一步細分。
其次,我們假設您同樣具有十進制形式的數字的不可變表示,並且再次,在不失一般性的情況下,字符串中有2個d十進制數字。 如果少於恰好2 d ,則再次使用前導零填充。 同樣,大小大於1的每個小數集合可以分成兩個集合,每個集合大小一半。
現在我們勾勒出一個遞歸算法:
DigitCollection Convert(BitCollection bits)
{
if (bits.Size == 1)
return bits[0] ? DigitCollection.SingleOne : DigitCollection.SingleZero;
DigitCollection left = Convert(bits.Left);
DigitCollection right = Convert(bits.Right);
DigitCollection power = MakePowerOfTwo(bits.Right.Size);
return Add(Multiply(left, power), right);
}
我們現在需要方法Add,Multiply和MakePowerOfTwo。 正如我們稍后將看到的,我們還需要Subtract和兩個Shift運算符,以便快速乘以10的冪。
加法和減法很容易。 顯然,如果較長的集合包含n個數字,則可以實現加法和減法方法以花費O(n)時間。
FullShift和HalfShift運算符從舊的數字集合中創建新的數字集合,以便於以10的冪進行快速乘法。 如果大小為2 d + 1的數字集合由每個大小為2 d的子集合(X1,X2)組成,則“半移”集合包含2 d + 2項並由((2 d前導零,X1)組成),(X2,2 d尾隨零))。 全班集合包括((X1,X2),(2 d + 1尾隨零))。
這些顯然非常便宜。
乘法是我們遇到大問題的地方 。 假如沒有,我們每兩個DigitCollections相乘恰好與2 d位數一般性。 我們可以使用Karatsuba的算法:
DigitCollection Multiply(DigitCollection x, DigitCollection y)
{
// Assume x and y are the same digit size.
if (x and y are both single digits)
return the trivial solution;
// Assume that z2, z1 and z0 are all the same digit size as x and y.
DigitCollection z2 = Multiply(x.Left, y.Left);
DigitCollection z0 = Multiply(x.Right, y.Right);
DigitCollection z1 = Subtract(
Multiply(Add(x.Left, x.Right), Add(y.Left, y.Right)),
Add(z2, z0));
return Add(Add(FullShift(z2), HalfShift(z1)), z0);
}
這個算法的順序是什么? 假設有n = 2個d位。 什么是O(乘(n))? 我們遞歸三次,每次都有一半的數字問題。 其余的加,減和移位操作都不超過O(n)。 所以我們有一個復發:
T(n) = 3 T(n/2) + O(n)
通過主定理有一個簡單的解決方案:這個算法是O(n 1 / lg 3 ),大約是O(n 1.58 )。
MakePowerOfTwo怎么樣? 鑒於我們已經擁有的東西,這很容易。 我們使用身份:
2 2n + 1 = 2 n x 2 n + 2 n x 2 n
並編寫算法:
DigitCollection MakePowerOfTwo(int p)
{
if (p == 0) return DigitCollection.SingleOne;
DigitCollection power = MakePowerOfTwo(p / 2);
power = Multiply(power, power);
if (p % 2 != 0)
power = Add(power, power);
return power;
}
它由乘法的計算決定,O(n 1.58 )也是如此。
現在我們可以看到轉換的原始調用也由乘法控制。
因此,如果您有2 d二進制數字進行轉換,使用此算法,您可以預期這將需要大約O(2 1.58 d )步驟。 在你的情況下你有2 25位轉換,所以這應該需要大約7,770億計算。
這里的關鍵事實是該算法完全由乘法的成本支配 。 如果你可以將乘法的成本降低到小於O(n 1.58 ),那么你將獲得巨大的勝利。 如果我是你,我將研究改進Karatsuba上的十進制乘法算法。
我不知道這是否更快,但這里是delphi中的一個例子,我很久以前寫過將大的int作為字符串處理(非常快速和臟) - 這是128bit uint但你可以無限延長
Function HexToBinShort(hex:integer):string;
begin
case hex of
0: result:='0000'; //convert each hex digit to binary string
1: result:='0001'; //could do this with high-nybble and low nybble
2: result:='0010'; //of each sequential byte in the array (mask and bit shift)
3: result:='0011'; //ie: binstring:=binstring + HexToBinShort(nybble[i])
4: result:='0100'; //but must count DOWN for i (start with MSB!)
5: result:='0101';
6: result:='0110';
7: result:='0111';
8: result:='1000';
9: result:='1001';
10: result:='1010';
11: result:='1011';
12: result:='1100';
13: result:='1101';
14: result:='1110';
15: result:='1111';
end;
end;
然后取出連接的二進制字符串,每次看到'1'時加上2的冪
Function BinToIntString(binstring:string):string;
var i, j : integer;
var calcHold, calc2 :string;
begin
calc2:=binstring[Length(binstring)]; // first bit is easy 0 or 1
for i := (Length(binstring) - 1) downto 1 do begin
if binstring[i] = '1' then begin
calcHold:=generateCard(Length(binstring)-i);
calc2 := AddDecimalStrings(calcHold, calc2);
end;
end;
result:=calc2;
end;
generateCard用於創建2 ^ i的十進制字符串表示(對於i> 0)
Function generateCard(i:integer):string;
var j : integer;
var outVal : string;
begin
outVal := '2';
if i > 1 then begin
for j := 2 to i do begin
outVal:= MulByTwo(outVal);
end;
end;
result := outVal;
end;
和MulByTwo將十進制字符串乘以2
Function MulByTwo(val:string):string;
var i : integer;
var carry, hold : integer;
var outHold : string;
var outString :string;
var outString2 : string;
begin
outString:= StringOfChar('0', Length(val) + 1);
outString2:= StringOfChar('0', Length(val));
carry :=0;
for i := Length(val) downto 1 do begin
hold := StrToInt(val[i]) * 2 + carry;
if hold >= 10 then begin
carry := 1;
hold := hold - 10;
end else begin
carry := 0;
end;
outHold := IntToStr(hold);
outString[i+1] := outHold[1];
end;
if carry = 1 then begin
outString[1] := '1';
result := outString;
end else begin
for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
result := outString2;
end;
end;
最后 - AddDecimalStrings ......好吧,它添加了兩個十進制字符串:
Function AddDecimalStrings(val1, val2:string):string;
var i,j :integer;
var carry, hold, largest: integer;
var outString, outString2, bigVal, smVal, outHold:string;
begin
if Length(val1) > Length(val2) then begin
largest:= Length(val1);
bigVal := val1;
smVal := StringOfChar('0', largest);
j:=1;
for i := (largest - length(val2) +1) to largest do begin
smVal[i] := val2[j];
j:=j+1;
end;
end else begin
if length(val2) > Length(val1) then begin
largest:=Length(val2);
bigVal:=val2;
smVal := StringOfChar('0', largest);
j:=1;
for i := (largest - length(val1) +1) to largest do begin
smVal[i] := val1[j];
j:=j+1;
end;
end else begin
largest:=length(val1);
bigVal:=val1;
smVal:=val2;
end;
end;
carry:=0;
outString:=StringOfChar('0', largest +1);
outString2:=StringOfChar('0', largest);
for i := largest downto 1 do begin
hold := StrToInt(bigVal[i]) + StrToInt(smVal[i]) + carry;
if hold >=10 then begin
carry:=1;
hold := hold - 10;
end else begin
carry:=0;
end;
outHold:= IntToStr(hold);
outString[i+1]:=outHold[1];
end;
if carry = 1 then begin
outString[1] := '1';
result := outString;
end else begin
for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
result := outString2;
end;
end;
這些函數允許您對幾乎任意大的整數執行基本算術作為字符串。 當然,當數字位數太大而無法索引數組時,你會碰到另一面牆。
這是一個除以二,順便說一下(對於另一種方式很有用......)。 我這里不處理奇數。
Function DivByTwo(val:string):string;
var i : integer;
var hold : integer;
var outHold : string;
var outString, outString2 :string;
begin
outString:=StringOfChar('0',Length(val));
for i := Length(val) downto 1 do begin
if StrToInt(val[i]) mod 2 = 0 then begin
hold:= Math.Floor(StrToInt(val[i]) / 2);
outHold:= IntToStr(hold);
outString[i]:=outHold[1];
end else begin
hold:= Math.Floor((StrToInt(val[i]) - 1) / 2);
outHold:=IntToStr(hold);
outString[i]:= outHold[1];
if i <> Length(val) then begin
hold:= StrToInt(outString[i+1]) + 5;
outHold:= IntToStr(hold);
outString[i+1] := outHold[1];
end;
end;
end;
outString2:=StringOfChar('0',Length(val)-1);
if (outString[1] = '0') and (length(outString) > 1) then begin
for i := 1 to length(outString2) do outString2[i]:=outString[i+1];
result:=outString2;
end else begin
result:=outString;
end;
end;
編輯:我剛用一個900萬位長的二進制字符串嘗試了這個,它很慢! 真的,這並不奇怪。 這是完全未經優化的代碼,有很多低調的果實可供選擇以加快速度。 盡管如此,我還是忍不住覺得這是你可能想要在完全優化的裝配中編寫的問題的種類(或規模)。 個別操作很小但是必須多次完成 - 這些咒語要求組裝。 多線程當然也可以在這里使用。
您可以通過一次執行多個數字來節省一些時間。 如果你這樣做,比如說,每次100,000,它可能至少比10次快一點。
請注意,它仍然可能非常緩慢,但它會節省你一些時間。
可以想象你可以使它遞歸,並加速它更多 - 獲得數字的粗略平方根,向下舍入到最接近的10. div和mod的指數,並將結果發送回相同的功能。 請注意,我不確定你如何有效地運行那個大小的div或mod,但是如果你能弄明白(並且不會耗盡內存),它仍然必然會更加節省時間而不是一次將它分成一個數字。
替代版本:放棄小數 - 因為數字太大而無法對任何真正的人類讀者有意義 - 並以十六進制渲染。 技術上仍然是人類可讀的,但你可以一次渲染一個字節,為自己省去了很多心痛。
感謝你的一切,我想通了,主要是基於j的想法的方式......,誰建議通過加2的冪每次有一個數轉換為基於10個號碼1
。 但我使用基於1000000000000000000(10 ^ 18)的系統而不是基於10的(人類十進制系統)。 所以每個數字不僅有10個可能性(0 ... 9),實際上10 ^ 18! 這適合我們轉換的64位數字.ToString()
這是最有效的方式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.